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 50 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 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: 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 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 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 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 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 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 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 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 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 408 void RegisterDebugUnwindCommand() { 409 RegisterCommand("debug-unwind", 410 []{ return std::unique_ptr<Command>(new DebugUnwindCommand()); }); 411 } 412