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::__anon8cf007b80111::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   bool 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   const EventAttrIds& 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) { return ProcessRecord(*record); };
203   return reader->ReadDataSection(callback);
204 }
205 
ProcessRecord(Record & record)206 bool TraceSchedCommand::ProcessRecord(Record& record) {
207   switch (record.type()) {
208     case PERF_RECORD_SAMPLE: {
209       ProcessSampleRecord(*static_cast<SampleRecord*>(&record));
210       break;
211     }
212     case PERF_RECORD_COMM: {
213       const CommRecord& r = *static_cast<const CommRecord*>(&record);
214       auto& thread = thread_map_[r.data->tid];
215       thread.process_id = r.data->pid;
216       thread.thread_id = r.data->tid;
217       thread.name = r.comm;
218       break;
219     }
220     case PERF_RECORD_FORK: {
221       const ForkRecord& r = *static_cast<const ForkRecord*>(&record);
222       auto& parent_thread = thread_map_[r.data->ptid];
223       auto& child_thread = thread_map_[r.data->tid];
224       parent_thread.process_id = r.data->ppid;
225       parent_thread.thread_id = r.data->ptid;
226       child_thread.process_id = r.data->pid;
227       child_thread.thread_id = r.data->tid;
228       child_thread.name = parent_thread.name;
229       break;
230     }
231     case PERF_RECORD_TRACING_DATA:
232     case SIMPLE_PERF_RECORD_TRACING_DATA: {
233       const TracingDataRecord& r = *static_cast<const TracingDataRecord*>(&record);
234       auto tracing = Tracing::Create(std::vector<char>(r.data, r.data + r.data_size));
235       if (!tracing) {
236         return false;
237       }
238       const EventType* event = FindEventTypeByName("sched:sched_stat_runtime");
239       CHECK(event != nullptr);
240       std::optional<TracingFormat> format = tracing->GetTracingFormatHavingId(event->config);
241       if (!format.has_value()) {
242         return false;
243       }
244       format.value().GetField("comm", tracing_field_comm_);
245       format.value().GetField("runtime", tracing_field_runtime_);
246       break;
247     }
248   }
249   return true;
250 }
251 
ProcessSampleRecord(const SampleRecord & record)252 void TraceSchedCommand::ProcessSampleRecord(const SampleRecord& record) {
253   std::string thread_name = tracing_field_comm_.ReadFromData(record.raw_data.data);
254   uint64_t runtime = tracing_field_runtime_.ReadFromData(record.raw_data.data);
255   ThreadInfo& thread = thread_map_[record.tid_data.tid];
256   thread.process_id = record.tid_data.pid;
257   thread.thread_id = record.tid_data.tid;
258   thread.name = thread_name;
259   thread.total_runtime_in_ns += runtime;
260   SpinInfo& spin_info = thread.spin_info;
261   spin_info.runtime_in_check_period += runtime;
262   spin_info.samples_in_check_period.push(SampleInfo(record.Timestamp(), runtime));
263 
264   // Check spin loop.
265   if (thread.spin_info.samples_in_check_period.size() == 1u) {
266     return;
267   }
268   uint64_t start_timestamp = spin_info.samples_in_check_period.front().timestamp;
269   uint64_t time_period_in_ns = record.Timestamp() - start_timestamp;
270   if (time_period_in_ns < spinloop_check_period_in_sec_ * 1e9) {
271     return;
272   }
273   if (thread.spin_info.runtime_in_check_period > time_period_in_ns * spinloop_check_rate_) {
274     // Detect a spin loop.
275     thread.spin_info.spinloop_count++;
276     double rate = std::min(
277         1.0, static_cast<double>(thread.spin_info.runtime_in_check_period) / time_period_in_ns);
278     if (rate > thread.spin_info.max_rate) {
279       thread.spin_info.max_rate = rate;
280       thread.spin_info.max_rate_start_timestamp = start_timestamp;
281       thread.spin_info.max_rate_end_timestamp = record.Timestamp();
282       // Clear samples to avoid overlapped spin loop periods.
283       std::queue<SampleInfo> empty_q;
284       std::swap(thread.spin_info.samples_in_check_period, empty_q);
285       thread.spin_info.runtime_in_check_period = 0;
286     } else {
287       thread.spin_info.runtime_in_check_period -=
288           spin_info.samples_in_check_period.front().runtime_in_ns;
289       thread.spin_info.samples_in_check_period.pop();
290     }
291   }
292 }
293 
BuildProcessInfo()294 std::vector<ProcessInfo> TraceSchedCommand::BuildProcessInfo() {
295   std::unordered_map<pid_t, ProcessInfo> process_map;
296   for (auto& pair : thread_map_) {
297     const ThreadInfo& thread = pair.second;
298     // No need to report simpleperf.
299     if (thread.name == "simpleperf") {
300       continue;
301     }
302     ProcessInfo& process = process_map[thread.process_id];
303     process.process_id = thread.process_id;
304     if (thread.process_id == thread.thread_id) {
305       process.name = thread.name;
306     }
307     process.total_runtime_in_ns += thread.total_runtime_in_ns;
308     process.threads.push_back(&thread);
309   }
310   std::vector<ProcessInfo> processes;
311   for (auto& pair : process_map) {
312     processes.push_back(pair.second);
313   }
314   auto sort_process = [](const ProcessInfo& p1, const ProcessInfo& p2) {
315     return p1.total_runtime_in_ns > p2.total_runtime_in_ns;
316   };
317   auto sort_thread = [](const ThreadInfo* t1, const ThreadInfo* t2) {
318     return t1->total_runtime_in_ns > t2->total_runtime_in_ns;
319   };
320   std::sort(processes.begin(), processes.end(), sort_process);
321   for (auto& process : processes) {
322     std::sort(process.threads.begin(), process.threads.end(), sort_thread);
323   }
324   return processes;
325 }
326 
ReportProcessInfo(const std::vector<ProcessInfo> & processes)327 void TraceSchedCommand::ReportProcessInfo(const std::vector<ProcessInfo>& processes) {
328   uint64_t total_runtime_in_ns = 0u;
329   for (auto& process : processes) {
330     total_runtime_in_ns += process.total_runtime_in_ns;
331   }
332   printf("Total Runtime: %.3f ms\n", total_runtime_in_ns / 1e6);
333   struct ReportEntry {
334     bool is_process = false;
335     uint64_t runtime_in_ns = 0;
336     double percentage = 0;
337     pid_t pid = 0;
338     std::string name;
339   };
340   std::vector<ReportEntry> entries;
341   for (auto& process : processes) {
342     ReportEntry entry;
343     entry.is_process = true;
344     entry.runtime_in_ns = process.total_runtime_in_ns;
345     entry.pid = process.process_id;
346     entry.name = process.name;
347     entry.percentage = 0.0;
348     if (total_runtime_in_ns != 0u) {
349       entry.percentage = 100.0 * process.total_runtime_in_ns / total_runtime_in_ns;
350     }
351     // Omit processes taken too small percentage.
352     if (entry.percentage < 0.01) {
353       continue;
354     }
355     entries.push_back(entry);
356     if (show_threads_) {
357       for (auto& thread : process.threads) {
358         ReportEntry entry;
359         entry.is_process = false;
360         entry.runtime_in_ns = thread->total_runtime_in_ns;
361         entry.pid = thread->thread_id;
362         entry.name = thread->name;
363         entry.percentage = 0.0;
364         if (total_runtime_in_ns != 0u) {
365           entry.percentage = 100.0 * thread->total_runtime_in_ns / total_runtime_in_ns;
366         }
367         // Omit threads taken too small percentage.
368         if (entry.percentage < 0.01) {
369           continue;
370         }
371         entries.push_back(entry);
372       }
373     }
374   }
375 
376   SampleDisplayer<ReportEntry, uint64_t> displayer;
377   if (show_threads_) {
378     displayer.AddDisplayFunction("Type", [](const ReportEntry* entry) -> std::string {
379       return entry->is_process ? "Process" : "Thread";
380     });
381   }
382   displayer.AddDisplayFunction("Runtime", [](const ReportEntry* entry) {
383     return StringPrintf("%.3f ms", entry->runtime_in_ns / 1e6);
384   });
385   displayer.AddDisplayFunction("Percentage", [](const ReportEntry* entry) {
386     return StringPrintf("%.2f%%", entry->percentage);
387   });
388   displayer.AddDisplayFunction(
389       "Pid", [](const ReportEntry* entry) { return StringPrintf("%d", entry->pid); });
390   displayer.AddDisplayFunction("Name", [](const ReportEntry* entry) { return entry->name; });
391   for (auto& entry : entries) {
392     displayer.AdjustWidth(&entry);
393   }
394   displayer.PrintNames(stdout);
395   for (auto& entry : entries) {
396     displayer.PrintSample(stdout, &entry);
397   }
398 
399   for (auto& process : processes) {
400     for (auto& thread : process.threads) {
401       if (thread->spin_info.spinloop_count != 0u) {
402         double percentage = 100.0 * thread->spin_info.max_rate;
403         double duration_in_ns =
404             thread->spin_info.max_rate_end_timestamp - thread->spin_info.max_rate_start_timestamp;
405         double running_time_in_ns = duration_in_ns * thread->spin_info.max_rate;
406         printf("Detect %" PRIu64
407                " spin loops in process %s (%d) thread %s (%d),\n"
408                "max rate at [%.6f s - %.6f s], taken %.3f ms / %.3f ms (%.2f%%).\n",
409                thread->spin_info.spinloop_count, process.name.c_str(), process.process_id,
410                thread->name.c_str(), thread->thread_id,
411                thread->spin_info.max_rate_start_timestamp / 1e9,
412                thread->spin_info.max_rate_end_timestamp / 1e9, running_time_in_ns / 1e6,
413                duration_in_ns / 1e6, percentage);
414       }
415     }
416   }
417 }
418 
419 }  // namespace
420 
RegisterTraceSchedCommand()421 void RegisterTraceSchedCommand() {
422   RegisterCommand("trace-sched", [] { return std::unique_ptr<Command>(new TraceSchedCommand()); });
423 }
424 
425 }  // namespace simpleperf
426