1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <memory>
18 #include <queue>
19 #include <string>
20 #include <unordered_map>
21 #include <vector>
22
23 #include <android-base/file.h>
24 #include <android-base/logging.h>
25 #include <android-base/stringprintf.h>
26
27 #include "SampleDisplayer.h"
28 #include "command.h"
29 #include "event_selection_set.h"
30 #include "record.h"
31 #include "record_file.h"
32 #include "tracing.h"
33 #include "utils.h"
34
35 namespace simpleperf {
36 namespace {
37
38 using android::base::StringPrintf;
39
40 struct SampleInfo {
41 uint64_t timestamp; // the time when the kernel generates the sample
42 uint64_t runtime_in_ns; // the runtime of the thread in the sample
SampleInfosimpleperf::__anon1b6e9ba40110::SampleInfo43 SampleInfo(uint64_t timestamp = 0, uint64_t runtime_in_ns = 0)
44 : timestamp(timestamp), runtime_in_ns(runtime_in_ns) {}
45 };
46
47 struct SpinInfo {
48 uint64_t spinloop_count = 0;
49 double max_rate = 0;
50 uint64_t max_rate_start_timestamp = 0;
51 uint64_t max_rate_end_timestamp = 0;
52 std::queue<SampleInfo> samples_in_check_period;
53 uint64_t runtime_in_check_period = 0;
54 };
55
56 struct ThreadInfo {
57 pid_t process_id = 0;
58 pid_t thread_id = 0;
59 std::string name;
60 uint64_t total_runtime_in_ns = 0;
61 SpinInfo spin_info;
62 };
63
64 struct ProcessInfo {
65 pid_t process_id = 0;
66 std::string name;
67 uint64_t total_runtime_in_ns = 0;
68 std::vector<const ThreadInfo*> threads;
69 };
70
71 class TraceSchedCommand : public Command {
72 public:
TraceSchedCommand()73 TraceSchedCommand()
74 : Command("trace-sched", "Trace system-wide process runtime events.",
75 // clang-format off
76 "Records system-wide sched:sched_stat_runtime events, reports runtime taken\n"
77 "by each process during recording, and optionally warns about processes which\n"
78 "may have spinloops.\n"
79 "Usage: simpleperf trace-sched [options]\n"
80 "--duration time_in_sec Monitor for time_in_sec seconds. Here time_in_sec may\n"
81 " be any positive floating point number. Default is 10.\n"
82 "--check-spinloop check_period_in_sec\n"
83 " Give warning for threads which may be spinning. A thread is\n"
84 " thought of spinning on the CPU, when it takes more than\n"
85 " [spin-rate] * [check_period] cpu time in any [check_period].\n"
86 " [spin-rate] can be set by --spin-rate. Default check_period is 1 sec.\n"
87 "--spin-rate spin-rate Default is 0.8. Vaild range is (0, 1].\n"
88 "--show-threads Show runtime of each thread.\n"
89 "--record-file file_path Read records from file_path.\n"
90 // clang-format on
91 ),
92 duration_in_sec_(10.0),
93 spinloop_check_period_in_sec_(1.0),
94 spinloop_check_rate_(0.8),
95 show_threads_(false) {}
96
97 bool Run(const std::vector<std::string>& args);
98
99 private:
100 bool ParseOptions(const std::vector<std::string>& args);
101 bool RecordSchedEvents(const std::string& record_file_path);
102 bool ParseSchedEvents(const std::string& record_file_path);
103 void ProcessRecord(Record& record);
104 void ProcessSampleRecord(const SampleRecord& record);
105 std::vector<ProcessInfo> BuildProcessInfo();
106 void ReportProcessInfo(const std::vector<ProcessInfo>& processes);
107
108 double duration_in_sec_;
109 double spinloop_check_period_in_sec_;
110 double spinloop_check_rate_;
111 bool show_threads_;
112 std::string record_file_;
113
114 StringTracingFieldPlace tracing_field_comm_;
115 TracingFieldPlace tracing_field_runtime_;
116 std::unordered_map<pid_t, ThreadInfo> thread_map_;
117 };
118
Run(const std::vector<std::string> & args)119 bool TraceSchedCommand::Run(const std::vector<std::string>& args) {
120 if (!ParseOptions(args)) {
121 return false;
122 }
123 TemporaryFile tmp_file;
124 if (record_file_.empty()) {
125 if (!RecordSchedEvents(tmp_file.path)) {
126 return false;
127 }
128 record_file_ = tmp_file.path;
129 }
130 if (!ParseSchedEvents(record_file_)) {
131 return false;
132 }
133 std::vector<ProcessInfo> processes = BuildProcessInfo();
134 ReportProcessInfo(processes);
135 return true;
136 }
137
ParseOptions(const std::vector<std::string> & args)138 bool TraceSchedCommand::ParseOptions(const std::vector<std::string>& args) {
139 size_t i;
140 for (i = 0; i < args.size(); ++i) {
141 if (args[i] == "--duration") {
142 if (!GetDoubleOption(args, &i, &duration_in_sec_, 1e-9)) {
143 return false;
144 }
145 } else if (args[i] == "--check-spinloop") {
146 if (!GetDoubleOption(args, &i, &spinloop_check_period_in_sec_, 1e-9)) {
147 return false;
148 }
149 } else if (args[i] == "--spin-rate") {
150 if (!GetDoubleOption(args, &i, &spinloop_check_rate_, 1e-9, 1.0)) {
151 return false;
152 }
153 } else if (args[i] == "--show-threads") {
154 show_threads_ = true;
155 } else if (args[i] == "--record-file") {
156 if (!NextArgumentOrError(args, &i)) {
157 return false;
158 }
159 record_file_ = args[i];
160 } else {
161 ReportUnknownOption(args, i);
162 return false;
163 }
164 }
165 return true;
166 }
167
RecordSchedEvents(const std::string & record_file_path)168 bool TraceSchedCommand::RecordSchedEvents(const std::string& record_file_path) {
169 if (!IsRoot()) {
170 LOG(ERROR) << "Need root privilege to trace system wide events.\n";
171 return false;
172 }
173 std::unique_ptr<Command> record_cmd = CreateCommandInstance("record");
174 CHECK(record_cmd);
175 std::vector<std::string> record_args = {"-e",
176 "sched:sched_stat_runtime",
177 "-a",
178 "--duration",
179 std::to_string(duration_in_sec_),
180 "-o",
181 record_file_path};
182 if (IsSettingClockIdSupported()) {
183 record_args.push_back("--clockid");
184 record_args.push_back("monotonic");
185 }
186 return record_cmd->Run(record_args);
187 }
188
ParseSchedEvents(const std::string & record_file_path)189 bool TraceSchedCommand::ParseSchedEvents(const std::string& record_file_path) {
190 std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(record_file_path);
191 if (!reader) {
192 return false;
193 }
194 const EventType* event = FindEventTypeByName("sched:sched_stat_runtime");
195 std::vector<EventAttrWithId> attrs = reader->AttrSection();
196 if (attrs.size() != 1u || attrs[0].attr->type != event->type ||
197 attrs[0].attr->config != event->config) {
198 LOG(ERROR) << "sched:sched_stat_runtime isn't recorded in " << record_file_path;
199 return false;
200 }
201
202 auto callback = [this](std::unique_ptr<Record> record) {
203 ProcessRecord(*record);
204 return true;
205 };
206 return reader->ReadDataSection(callback);
207 }
208
ProcessRecord(Record & record)209 void TraceSchedCommand::ProcessRecord(Record& record) {
210 switch (record.type()) {
211 case PERF_RECORD_SAMPLE: {
212 ProcessSampleRecord(*static_cast<SampleRecord*>(&record));
213 break;
214 }
215 case PERF_RECORD_COMM: {
216 const CommRecord& r = *static_cast<const CommRecord*>(&record);
217 auto& thread = thread_map_[r.data->tid];
218 thread.process_id = r.data->pid;
219 thread.thread_id = r.data->tid;
220 thread.name = r.comm;
221 break;
222 }
223 case PERF_RECORD_FORK: {
224 const ForkRecord& r = *static_cast<const ForkRecord*>(&record);
225 auto& parent_thread = thread_map_[r.data->ptid];
226 auto& child_thread = thread_map_[r.data->tid];
227 parent_thread.process_id = r.data->ppid;
228 parent_thread.thread_id = r.data->ptid;
229 child_thread.process_id = r.data->pid;
230 child_thread.thread_id = r.data->tid;
231 child_thread.name = parent_thread.name;
232 break;
233 }
234 case PERF_RECORD_TRACING_DATA:
235 case SIMPLE_PERF_RECORD_TRACING_DATA: {
236 const TracingDataRecord& r = *static_cast<const TracingDataRecord*>(&record);
237 Tracing tracing(std::vector<char>(r.data, r.data + r.data_size));
238 const EventType* event = FindEventTypeByName("sched:sched_stat_runtime");
239 CHECK(event != nullptr);
240 TracingFormat format = tracing.GetTracingFormatHavingId(event->config);
241 format.GetField("comm", tracing_field_comm_);
242 format.GetField("runtime", tracing_field_runtime_);
243 break;
244 }
245 }
246 }
247
ProcessSampleRecord(const SampleRecord & record)248 void TraceSchedCommand::ProcessSampleRecord(const SampleRecord& record) {
249 std::string thread_name = tracing_field_comm_.ReadFromData(record.raw_data.data);
250 uint64_t runtime = tracing_field_runtime_.ReadFromData(record.raw_data.data);
251 ThreadInfo& thread = thread_map_[record.tid_data.tid];
252 thread.process_id = record.tid_data.pid;
253 thread.thread_id = record.tid_data.tid;
254 thread.name = thread_name;
255 thread.total_runtime_in_ns += runtime;
256 SpinInfo& spin_info = thread.spin_info;
257 spin_info.runtime_in_check_period += runtime;
258 spin_info.samples_in_check_period.push(SampleInfo(record.Timestamp(), runtime));
259
260 // Check spin loop.
261 if (thread.spin_info.samples_in_check_period.size() == 1u) {
262 return;
263 }
264 uint64_t start_timestamp = spin_info.samples_in_check_period.front().timestamp;
265 uint64_t time_period_in_ns = record.Timestamp() - start_timestamp;
266 if (time_period_in_ns < spinloop_check_period_in_sec_ * 1e9) {
267 return;
268 }
269 if (thread.spin_info.runtime_in_check_period > time_period_in_ns * spinloop_check_rate_) {
270 // Detect a spin loop.
271 thread.spin_info.spinloop_count++;
272 double rate = std::min(
273 1.0, static_cast<double>(thread.spin_info.runtime_in_check_period) / time_period_in_ns);
274 if (rate > thread.spin_info.max_rate) {
275 thread.spin_info.max_rate = rate;
276 thread.spin_info.max_rate_start_timestamp = start_timestamp;
277 thread.spin_info.max_rate_end_timestamp = record.Timestamp();
278 // Clear samples to avoid overlapped spin loop periods.
279 std::queue<SampleInfo> empty_q;
280 std::swap(thread.spin_info.samples_in_check_period, empty_q);
281 thread.spin_info.runtime_in_check_period = 0;
282 } else {
283 thread.spin_info.runtime_in_check_period -=
284 spin_info.samples_in_check_period.front().runtime_in_ns;
285 thread.spin_info.samples_in_check_period.pop();
286 }
287 }
288 }
289
BuildProcessInfo()290 std::vector<ProcessInfo> TraceSchedCommand::BuildProcessInfo() {
291 std::unordered_map<pid_t, ProcessInfo> process_map;
292 for (auto& pair : thread_map_) {
293 const ThreadInfo& thread = pair.second;
294 // No need to report simpleperf.
295 if (thread.name == "simpleperf") {
296 continue;
297 }
298 ProcessInfo& process = process_map[thread.process_id];
299 process.process_id = thread.process_id;
300 if (thread.process_id == thread.thread_id) {
301 process.name = thread.name;
302 }
303 process.total_runtime_in_ns += thread.total_runtime_in_ns;
304 process.threads.push_back(&thread);
305 }
306 std::vector<ProcessInfo> processes;
307 for (auto& pair : process_map) {
308 processes.push_back(pair.second);
309 }
310 auto sort_process = [](const ProcessInfo& p1, const ProcessInfo& p2) {
311 return p1.total_runtime_in_ns > p2.total_runtime_in_ns;
312 };
313 auto sort_thread = [](const ThreadInfo* t1, const ThreadInfo* t2) {
314 return t1->total_runtime_in_ns > t2->total_runtime_in_ns;
315 };
316 std::sort(processes.begin(), processes.end(), sort_process);
317 for (auto& process : processes) {
318 std::sort(process.threads.begin(), process.threads.end(), sort_thread);
319 }
320 return processes;
321 }
322
ReportProcessInfo(const std::vector<ProcessInfo> & processes)323 void TraceSchedCommand::ReportProcessInfo(const std::vector<ProcessInfo>& processes) {
324 uint64_t total_runtime_in_ns = 0u;
325 for (auto& process : processes) {
326 total_runtime_in_ns += process.total_runtime_in_ns;
327 }
328 printf("Total Runtime: %.3f ms\n", total_runtime_in_ns / 1e6);
329 struct ReportEntry {
330 bool is_process = false;
331 uint64_t runtime_in_ns = 0;
332 double percentage = 0;
333 pid_t pid = 0;
334 std::string name;
335 };
336 std::vector<ReportEntry> entries;
337 for (auto& process : processes) {
338 ReportEntry entry;
339 entry.is_process = true;
340 entry.runtime_in_ns = process.total_runtime_in_ns;
341 entry.pid = process.process_id;
342 entry.name = process.name;
343 entry.percentage = 0.0;
344 if (total_runtime_in_ns != 0u) {
345 entry.percentage = 100.0 * process.total_runtime_in_ns / total_runtime_in_ns;
346 }
347 // Omit processes taken too small percentage.
348 if (entry.percentage < 0.01) {
349 continue;
350 }
351 entries.push_back(entry);
352 if (show_threads_) {
353 for (auto& thread : process.threads) {
354 ReportEntry entry;
355 entry.is_process = false;
356 entry.runtime_in_ns = thread->total_runtime_in_ns;
357 entry.pid = thread->thread_id;
358 entry.name = thread->name;
359 entry.percentage = 0.0;
360 if (total_runtime_in_ns != 0u) {
361 entry.percentage = 100.0 * thread->total_runtime_in_ns / total_runtime_in_ns;
362 }
363 // Omit threads taken too small percentage.
364 if (entry.percentage < 0.01) {
365 continue;
366 }
367 entries.push_back(entry);
368 }
369 }
370 }
371
372 SampleDisplayer<ReportEntry, uint64_t> displayer;
373 if (show_threads_) {
374 displayer.AddDisplayFunction("Type", [](const ReportEntry* entry) -> std::string {
375 return entry->is_process ? "Process" : "Thread";
376 });
377 }
378 displayer.AddDisplayFunction("Runtime", [](const ReportEntry* entry) {
379 return StringPrintf("%.3f ms", entry->runtime_in_ns / 1e6);
380 });
381 displayer.AddDisplayFunction("Percentage", [](const ReportEntry* entry) {
382 return StringPrintf("%.2f%%", entry->percentage);
383 });
384 displayer.AddDisplayFunction(
385 "Pid", [](const ReportEntry* entry) { return StringPrintf("%d", entry->pid); });
386 displayer.AddDisplayFunction("Name", [](const ReportEntry* entry) { return entry->name; });
387 for (auto& entry : entries) {
388 displayer.AdjustWidth(&entry);
389 }
390 displayer.PrintNames(stdout);
391 for (auto& entry : entries) {
392 displayer.PrintSample(stdout, &entry);
393 }
394
395 for (auto& process : processes) {
396 for (auto& thread : process.threads) {
397 if (thread->spin_info.spinloop_count != 0u) {
398 double percentage = 100.0 * thread->spin_info.max_rate;
399 double duration_in_ns =
400 thread->spin_info.max_rate_end_timestamp - thread->spin_info.max_rate_start_timestamp;
401 double running_time_in_ns = duration_in_ns * thread->spin_info.max_rate;
402 printf("Detect %" PRIu64
403 " spin loops in process %s (%d) thread %s (%d),\n"
404 "max rate at [%.6f s - %.6f s], taken %.3f ms / %.3f ms (%.2f%%).\n",
405 thread->spin_info.spinloop_count, process.name.c_str(), process.process_id,
406 thread->name.c_str(), thread->thread_id,
407 thread->spin_info.max_rate_start_timestamp / 1e9,
408 thread->spin_info.max_rate_end_timestamp / 1e9, running_time_in_ns / 1e6,
409 duration_in_ns / 1e6, percentage);
410 }
411 }
412 }
413 }
414
415 } // namespace
416
RegisterTraceSchedCommand()417 void RegisterTraceSchedCommand() {
418 RegisterCommand("trace-sched", [] { return std::unique_ptr<Command>(new TraceSchedCommand()); });
419 }
420
421 } // namespace simpleperf
422