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 <algorithm>
18 #include <memory>
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/parseint.h>
26 #include <android-base/stringprintf.h>
27 #include <android-base/strings.h>
28 
29 #include "CallChainJoiner.h"
30 #include "command.h"
31 #include "environment.h"
32 #include "OfflineUnwinder.h"
33 #include "perf_regs.h"
34 #include "record_file.h"
35 #include "thread_tree.h"
36 #include "utils.h"
37 #include "workload.h"
38 
39 using namespace simpleperf;
40 
41 // Cache size used by CallChainJoiner to cache call chains in memory.
42 constexpr size_t DEFAULT_CALL_CHAIN_JOINER_CACHE_SIZE = 8 * 1024 * 1024;
43 
44 struct MemStat {
45   std::string vm_peak;
46   std::string vm_size;
47   std::string vm_hwm;
48   std::string vm_rss;
49 
ToStringMemStat50   std::string ToString() const {
51     return android::base::StringPrintf("VmPeak:%s;VmSize:%s;VmHWM:%s;VmRSS:%s", vm_peak.c_str(),
52                                        vm_size.c_str(), vm_hwm.c_str(), vm_rss.c_str());
53   }
54 };
55 
GetMemStat(MemStat * stat)56 static bool GetMemStat(MemStat* stat) {
57   std::string s;
58   if (!android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/status", getpid()),
59                                                                    &s)) {
60     PLOG(ERROR) << "Failed to read process status";
61     return false;
62   }
63   std::vector<std::string> lines = android::base::Split(s, "\n");
64   for (auto& line : lines) {
65     if (android::base::StartsWith(line, "VmPeak:")) {
66       stat->vm_peak = android::base::Trim(line.substr(strlen("VmPeak:")));
67     } else if (android::base::StartsWith(line, "VmSize:")) {
68       stat->vm_size = android::base::Trim(line.substr(strlen("VmSize:")));
69     } else if (android::base::StartsWith(line, "VmHWM:")) {
70       stat->vm_hwm = android::base::Trim(line.substr(strlen("VmHWM:")));
71     } else if (android::base::StartsWith(line, "VmRSS:")) {
72       stat->vm_rss = android::base::Trim(line.substr(strlen("VmRSS:")));
73     }
74   }
75   return true;
76 }
77 
78 class DebugUnwindCommand : public Command {
79  public:
DebugUnwindCommand()80   DebugUnwindCommand()
81       : Command("debug-unwind", "Debug/test offline unwinding.",
82                 // clang-format off
83 "Usage: simpleperf debug-unwind [options]\n"
84 "       Given a perf.data generated with \"-g --no-unwind\", it converts\n"
85 "       regs/stack data of samples into callchains, and write result into\n"
86 "       a new perf.data. The new perf.data can be passed to\n"
87 "       unwind_result_reporter.py to generate a text report.\n"
88 "-i <file>  The path of record file generated with \"-g --no-unwind\".\n"
89 "           Default is perf.data.\n"
90 "-o <file>  The path ot write new perf.data. Default is perf.data.debug.\n"
91 "--symfs <dir>  Look for files with symbols relative to this directory.\n"
92 "--time time    Only unwind samples recorded at selected time.\n"
93                 // clang-format on
94                ),
95           input_filename_("perf.data"),
96           output_filename_("perf.data.debug"),
97           offline_unwinder_(OfflineUnwinder::Create(true)),
98           callchain_joiner_(DEFAULT_CALL_CHAIN_JOINER_CACHE_SIZE, 1, true),
99           selected_time_(0) {
100   }
101 
102   bool Run(const std::vector<std::string>& args);
103 
104  private:
105   bool ParseOptions(const std::vector<std::string>& args);
106   bool UnwindRecordFile();
107   bool ProcessRecord(Record* record);
108   void CollectHitFileInfo(const SampleRecord& r, const std::vector<uint64_t>& ips);
109   bool JoinCallChains();
110   bool WriteFeatureSections();
111   void PrintStat();
112 
113   struct Stat {
114     // For testing unwinding performance.
115     uint64_t unwinding_sample_count = 0u;
116     uint64_t total_unwinding_time_in_ns = 0u;
117     uint64_t max_unwinding_time_in_ns = 0u;
118 
119     // For memory consumption.
120     MemStat mem_before_unwinding;
121     MemStat mem_after_unwinding;
122   };
123 
124   std::string input_filename_;
125   std::string output_filename_;
126   std::unique_ptr<RecordFileReader> reader_;
127   std::unique_ptr<RecordFileWriter> writer_;
128   ThreadTree thread_tree_;
129   std::unique_ptr<OfflineUnwinder> offline_unwinder_;
130   CallChainJoiner callchain_joiner_;
131   Stat stat_;
132   uint64_t selected_time_;
133 };
134 
Run(const std::vector<std::string> & args)135 bool DebugUnwindCommand::Run(const std::vector<std::string>& args) {
136   // 1. Parse options.
137   if (!ParseOptions(args)) {
138     return false;
139   }
140   ScopedTempFiles scoped_temp_files(android::base::Dirname(output_filename_));
141 
142   // 2. Read input perf.data, and generate new perf.data.
143   if (!UnwindRecordFile()) {
144     return false;
145   }
146 
147   // 3. Show stat of unwinding.
148   PrintStat();
149   return true;
150 }
151 
ParseOptions(const std::vector<std::string> & args)152 bool DebugUnwindCommand::ParseOptions(const std::vector<std::string>& args) {
153   for (size_t i = 0; i < args.size(); ++i) {
154     if (args[i] == "-i") {
155       if (!NextArgumentOrError(args, &i)) {
156         return false;
157       }
158       input_filename_ = args[i];
159     } else if (args[i] == "-o") {
160       if (!NextArgumentOrError(args, &i)) {
161         return false;
162       }
163       output_filename_ = args[i];
164     } else if (args[i] == "--symfs") {
165       if (!NextArgumentOrError(args, &i)) {
166         return false;
167       }
168       if (!Dso::SetSymFsDir(args[i])) {
169         return false;
170       }
171     } else if (args[i] == "--time") {
172       if (!GetUintOption(args, &i, &selected_time_)) {
173         return false;
174       }
175     } else {
176       ReportUnknownOption(args, i);
177       return false;
178     }
179   }
180   return true;
181 }
182 
UnwindRecordFile()183 bool DebugUnwindCommand::UnwindRecordFile() {
184   // 1. Check input file.
185   reader_ = RecordFileReader::CreateInstance(input_filename_);
186   if (!reader_) {
187     return false;
188   }
189   reader_->LoadBuildIdAndFileFeatures(thread_tree_);
190   std::string record_cmd = android::base::Join(reader_->ReadCmdlineFeature(), " ");
191   if (record_cmd.find("--no-unwind") == std::string::npos ||
192       (record_cmd.find("-g") == std::string::npos &&
193           record_cmd.find("--call-graph dwarf") == std::string::npos)) {
194     LOG(ERROR) << input_filename_ << " isn't recorded with \"-g --no-unwind\"";
195     return false;
196   }
197   ScopedCurrentArch scoped_arch(GetArchType(reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH)));
198 
199   // 2. Copy attr section.
200   writer_ = RecordFileWriter::CreateInstance(output_filename_);
201   if (!writer_ || !writer_->WriteAttrSection(reader_->AttrSection())) {
202     return false;
203   }
204 
205   // 3. Process records in data section.
206   if (!GetMemStat(&stat_.mem_before_unwinding)) {
207     return false;
208   }
209   auto callback = [this](std::unique_ptr<Record> record) {
210     return ProcessRecord(record.get());
211   };
212   if (!reader_->ReadDataSection(callback)) {
213     return false;
214   }
215   if (!JoinCallChains()) {
216     return false;
217   }
218   if (!GetMemStat(&stat_.mem_after_unwinding)) {
219     return false;
220   }
221 
222   // 4. Write feature sections.
223   return WriteFeatureSections();
224 }
225 
ProcessRecord(Record * record)226 bool DebugUnwindCommand::ProcessRecord(Record* record) {
227   if (record->type() == PERF_RECORD_SAMPLE) {
228     auto& r = *static_cast<SampleRecord*>(record);
229     if (selected_time_ != 0u && r.Timestamp() != selected_time_) {
230       return true;
231     }
232     uint64_t need_type = PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER;
233     if ((r.sample_type & need_type) == need_type && r.regs_user_data.reg_mask != 0 &&
234         r.GetValidStackSize() > 0) {
235       ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
236       RegSet regs(r.regs_user_data.abi, r.regs_user_data.reg_mask, r.regs_user_data.regs);
237       std::vector<uint64_t> ips;
238       std::vector<uint64_t> sps;
239       if (!offline_unwinder_->UnwindCallChain(*thread, regs, r.stack_user_data.data,
240                                              r.GetValidStackSize(), &ips, &sps)) {
241         return false;
242       }
243 
244       const UnwindingResult& unwinding_result = offline_unwinder_->GetUnwindingResult();
245       stat_.unwinding_sample_count++;
246       stat_.total_unwinding_time_in_ns += unwinding_result.used_time;
247       stat_.max_unwinding_time_in_ns = std::max(stat_.max_unwinding_time_in_ns,
248                                                 unwinding_result.used_time);
249       if (!writer_->WriteRecord(UnwindingResultRecord(r.time_data.time, unwinding_result))) {
250         return false;
251       }
252       // We want to keep both reg/stack data and callchain of a sample. However, storing both
253       // can exceed the size limit of a SampleRecord. So instead we store one sample with reg/stack
254       // data and one sample with callchain.
255       if (!writer_->WriteRecord(r)) {
256         return false;
257       }
258       r.ReplaceRegAndStackWithCallChain(ips);
259       if (!callchain_joiner_.AddCallChain(r.tid_data.pid, r.tid_data.tid,
260                                           CallChainJoiner::ORIGINAL_OFFLINE, ips, sps)) {
261         return false;
262       }
263       CollectHitFileInfo(r, ips);
264     }
265   } else {
266     thread_tree_.Update(*record);
267   }
268   return writer_->WriteRecord(*record);
269 }
270 
CollectHitFileInfo(const SampleRecord & r,const std::vector<uint64_t> & ips)271 void DebugUnwindCommand::CollectHitFileInfo(const SampleRecord& r,
272                                             const std::vector<uint64_t>& ips) {
273   const ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
274   for (auto ip : ips) {
275     const MapEntry* map = thread_tree_.FindMap(thread, ip, false);
276     Dso* dso = map->dso;
277     if (!dso->HasDumpId() && dso->type() != DSO_UNKNOWN_FILE) {
278       dso->CreateDumpId();
279     }
280     const Symbol* symbol = thread_tree_.FindSymbol(map, ip, nullptr, &dso);
281     if (!symbol->HasDumpId()) {
282       dso->CreateSymbolDumpId(symbol);
283     }
284   }
285 }
286 
JoinCallChains()287 bool DebugUnwindCommand::JoinCallChains() {
288   // 1. Prepare joined callchains.
289   if (!callchain_joiner_.JoinCallChains()) {
290     return false;
291   }
292   // 2. Move records from record_filename_ to a temporary file.
293   if (!writer_->Close()) {
294     return false;
295   }
296   writer_.reset();
297   std::unique_ptr<TemporaryFile> tmp_file = ScopedTempFiles::CreateTempFile();
298   if (!Workload::RunCmd({"mv", output_filename_, tmp_file->path})) {
299     return false;
300   }
301 
302   // 3. Read records from the temporary file, and write records with joined call chains back
303   // to record_filename_.
304   std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmp_file->path);
305   if (!reader) {
306     return false;
307   }
308   writer_ = RecordFileWriter::CreateInstance(output_filename_);
309   if (!writer_ || !writer_->WriteAttrSection(reader->AttrSection())) {
310     return false;
311   }
312 
313   auto record_callback = [&](std::unique_ptr<Record> r) {
314     if (r->type() != PERF_RECORD_SAMPLE) {
315       return writer_->WriteRecord(*r);
316     }
317     SampleRecord& sr = *static_cast<SampleRecord*>(r.get());
318     if (!sr.HasUserCallChain()) {
319       return writer_->WriteRecord(sr);
320     }
321     pid_t pid;
322     pid_t tid;
323     CallChainJoiner::ChainType type;
324     std::vector<uint64_t> ips;
325     std::vector<uint64_t> sps;
326     do {
327       if (!callchain_joiner_.GetNextCallChain(pid, tid, type, ips, sps)) {
328         return false;
329       }
330       if (!writer_->WriteRecord(CallChainRecord(pid, tid, type, sr.Timestamp(), ips, sps))) {
331         return false;
332       }
333     } while (type != CallChainJoiner::JOINED_OFFLINE);
334     CHECK_EQ(pid, static_cast<pid_t>(sr.tid_data.pid));
335     CHECK_EQ(tid, static_cast<pid_t>(sr.tid_data.tid));
336     sr.UpdateUserCallChain(ips);
337     return writer_->WriteRecord(sr);
338   };
339   return reader->ReadDataSection(record_callback);
340 }
341 
WriteFeatureSections()342 bool DebugUnwindCommand::WriteFeatureSections() {
343   // Add debug_unwind info in META_INFO section, and add symbol info in FILE section.
344   const std::map<int, PerfFileFormat::SectionDesc>& features = reader_->FeatureSectionDescriptors();
345   size_t new_feature_count = features.size();
346   for (int feature : {PerfFileFormat::FEAT_FILE, PerfFileFormat::FEAT_META_INFO}) {
347     if (features.find(feature) == features.end()) {
348       new_feature_count++;
349     }
350   }
351   if (!writer_->BeginWriteFeatures(new_feature_count)) {
352     return false;
353   }
354 
355   auto it = features.begin();
356   // Copy all feature sections except FEAT_FILE and FEAT_META_INFO, which require special handling.
357   while (it != features.end() && it->first < PerfFileFormat::FEAT_FILE) {
358     std::vector<char> data;
359     if (!reader_->ReadFeatureSection(it->first, &data) || !writer_->WriteFeature(it->first, data)) {
360       return false;
361     }
362     ++it;
363   }
364   // Write a new file section.
365   if (it != features.end() && it->first == PerfFileFormat::FEAT_FILE) {
366     ++it;
367   }
368   if (!writer_->WriteFileFeatures(thread_tree_.GetAllDsos())) {
369     return false;
370   }
371   // Write meta_info section.
372   std::unordered_map<std::string, std::string> info_map;
373   if (it != features.end() && it->first == PerfFileFormat::FEAT_META_INFO) {
374     info_map = reader_->GetMetaInfoFeature();
375     ++it;
376   }
377   info_map["debug_unwind"] = "true";
378   info_map["debug_unwind_mem_before"] = stat_.mem_before_unwinding.ToString();
379   info_map["debug_unwind_mem_after"] = stat_.mem_after_unwinding.ToString();
380   if (!writer_->WriteMetaInfoFeature(info_map)) {
381     return false;
382   }
383   CHECK(it == features.end());
384   return writer_->EndWriteFeatures() && writer_->Close();
385 }
386 
PrintStat()387 void DebugUnwindCommand::PrintStat() {
388   printf("Unwinding sample count: %" PRIu64 "\n", stat_.unwinding_sample_count);
389   if (stat_.unwinding_sample_count > 0u) {
390     printf("Average unwinding time: %f us\n", static_cast<double>(stat_.total_unwinding_time_in_ns)
391            / 1000 / stat_.unwinding_sample_count);
392     printf("Max unwinding time: %f us\n", static_cast<double>(stat_.max_unwinding_time_in_ns)
393            / 1000);
394   }
395   printf("Memory change:\n");
396   PrintIndented(1, "VmPeak: %s -> %s\n", stat_.mem_before_unwinding.vm_peak.c_str(),
397                 stat_.mem_after_unwinding.vm_peak.c_str());
398   PrintIndented(1, "VmSize: %s -> %s\n", stat_.mem_before_unwinding.vm_size.c_str(),
399                 stat_.mem_after_unwinding.vm_size.c_str());
400   PrintIndented(1, "VmHWM: %s -> %s\n", stat_.mem_before_unwinding.vm_hwm.c_str(),
401                 stat_.mem_after_unwinding.vm_hwm.c_str());
402   PrintIndented(1, "VmRSS: %s -> %s\n", stat_.mem_before_unwinding.vm_rss.c_str(),
403                 stat_.mem_after_unwinding.vm_rss.c_str());
404   callchain_joiner_.DumpStat();
405   printf("Please use debug_unwind_reporter.py to get a report in details.\n");
406 }
407 
RegisterDebugUnwindCommand()408 void RegisterDebugUnwindCommand() {
409   RegisterCommand("debug-unwind",
410                   []{ return std::unique_ptr<Command>(new DebugUnwindCommand()); });
411 }
412