1 /**
2 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "tools/sampler/aspt_converter.h"
17
18 namespace ark::tooling::sampler {
19
CollectTracesStats()20 size_t AsptConverter::CollectTracesStats()
21 {
22 stackTraces_.clear();
23
24 size_t sampleCounter = 0;
25 SampleInfo sample;
26 while (reader_.GetNextSample(&sample)) {
27 ++sampleCounter;
28
29 if (dumpType_ == DumpType::WITHOUT_THREAD_SEPARATION) {
30 // NOTE: zeroing thread_id to make samples indistinguishable in mode without thread separation
31 sample.threadInfo.threadId = 0;
32 }
33
34 if (!buildColdGraph_) {
35 // NOTE: zeroing thread_status to make samples indistinguishable
36 // in mode without building cold flamegraph
37 sample.threadInfo.threadStatus = SampleInfo::ThreadStatus::UNDECLARED;
38 }
39
40 auto it = stackTraces_.find(sample);
41 if (it == stackTraces_.end()) {
42 stackTraces_.insert({sample, 1});
43 continue;
44 }
45 ++it->second;
46 }
47 return sampleCounter;
48 }
49
CollectModules()50 bool AsptConverter::CollectModules()
51 {
52 FileInfo mInfo;
53 while (reader_.GetNextModule(&mInfo)) {
54 modules_.push_back(mInfo);
55 }
56
57 return !modules_.empty();
58 }
59
BuildModulesMap()60 bool AsptConverter::BuildModulesMap()
61 {
62 for (auto &mdl : modules_) {
63 std::string filepath = mdl.pathname;
64
65 if (substituteDirectories_.has_value()) {
66 SubstituteDirectories(&filepath);
67 }
68
69 if (!ark::os::IsFileExists(filepath)) {
70 LOG(ERROR, PROFILER) << "Module not found, path: " << filepath;
71 }
72
73 if (modulesMap_.find(mdl.ptr) == modulesMap_.end()) {
74 auto pfUnique = panda_file::OpenPandaFileOrZip(filepath.c_str());
75 if (mdl.checksum != pfUnique->GetHeader()->checksum) {
76 LOG(FATAL, PROFILER) << "Checksum of panda files isn't equal";
77 return false;
78 }
79
80 modulesMap_.insert({mdl.ptr, std::move(pfUnique)});
81 }
82 }
83 return !modulesMap_.empty();
84 }
85
SubstituteDirectories(std::string * pathname) const86 void AsptConverter::SubstituteDirectories(std::string *pathname) const
87 {
88 for (size_t i = 0; i < substituteDirectories_->source.size(); ++i) {
89 auto pos = pathname->find(substituteDirectories_->source[i]);
90 if (pos != std::string::npos) {
91 pathname->replace(pos, substituteDirectories_->source[i].size(), substituteDirectories_->destination[i]);
92 break;
93 }
94 }
95 }
96
DumpResolvedTracesAsCSV(const char * filename)97 bool AsptConverter::DumpResolvedTracesAsCSV(const char *filename)
98 {
99 std::unique_ptr<TraceDumper> dumper;
100
101 switch (dumpType_) {
102 case DumpType::WITHOUT_THREAD_SEPARATION:
103 case DumpType::THREAD_SEPARATION_BY_TID:
104 dumper =
105 std::make_unique<SingleCSVDumper>(filename, dumpType_, &modulesMap_, &methodsMap_, buildColdGraph_);
106 break;
107 case DumpType::THREAD_SEPARATION_BY_CSV:
108 dumper = std::make_unique<MultipleCSVDumper>(filename, &modulesMap_, &methodsMap_, buildColdGraph_);
109 break;
110 default:
111 UNREACHABLE();
112 }
113 for (auto &[sample, count] : stackTraces_) {
114 ASSERT(sample.stackInfo.managedStackSize <= SampleInfo::StackInfo::MAX_STACK_DEPTH);
115 dumper->DumpTraces(sample, count);
116 }
117 return true;
118 }
119
BuildMethodsMap()120 void AsptConverter::BuildMethodsMap()
121 {
122 for (const auto &pfPair : modulesMap_) {
123 const panda_file::File *pf = pfPair.second.get();
124 if (pf == nullptr) {
125 continue;
126 }
127 auto classesSpan = pf->GetClasses();
128 BuildMethodsMapHelper(pf, classesSpan);
129 }
130 }
131
BuildMethodsMapHelper(const panda_file::File * pf,Span<const uint32_t> & classesSpan)132 void AsptConverter::BuildMethodsMapHelper(const panda_file::File *pf, Span<const uint32_t> &classesSpan)
133 {
134 for (auto id : classesSpan) {
135 if (pf->IsExternal(panda_file::File::EntityId(id))) {
136 continue;
137 }
138 panda_file::ClassDataAccessor cda(*pf, panda_file::File::EntityId(id));
139 cda.EnumerateMethods([&](panda_file::MethodDataAccessor &mda) {
140 std::string methodName = utf::Mutf8AsCString(mda.GetName().data);
141 std::string className = utf::Mutf8AsCString(cda.GetDescriptor());
142 if (className[className.length() - 1] == ';') {
143 className.pop_back();
144 }
145 std::string fullName = className + "::";
146 fullName += methodName;
147 methodsMap_[pf][mda.GetMethodId().GetOffset()] = std::move(fullName);
148 });
149 }
150 }
151
DumpModulesToFile(const std::string & outname) const152 bool AsptConverter::DumpModulesToFile(const std::string &outname) const
153 {
154 std::ofstream out(outname, std::ios::binary);
155 if (!out) {
156 LOG(ERROR, PROFILER) << "Can't open " << outname;
157 out.close();
158 return false;
159 }
160
161 for (auto &mdl : modules_) {
162 out << mdl.checksum << " " << mdl.pathname << "\n";
163 }
164
165 if (out.fail()) {
166 LOG(ERROR, PROFILER) << "Failbit or the badbit (or both) was set";
167 out.close();
168 return false;
169 }
170
171 return true;
172 }
173
174 /* static */
GetDumpTypeFromOptions(const Options & cliOptions)175 DumpType AsptConverter::GetDumpTypeFromOptions(const Options &cliOptions)
176 {
177 const std::string dumpTypeStr = cliOptions.GetCsvTidSeparation();
178
179 DumpType dumpType;
180 if (dumpTypeStr == "single-csv-single-tid") {
181 dumpType = DumpType::WITHOUT_THREAD_SEPARATION;
182 } else if (dumpTypeStr == "single-csv-multi-tid") {
183 dumpType = DumpType::THREAD_SEPARATION_BY_TID;
184 } else if (dumpTypeStr == "multi-csv") {
185 dumpType = DumpType::THREAD_SEPARATION_BY_CSV;
186 } else {
187 std::cerr << "unknown value of csv-tid-distribution option: '" << dumpTypeStr
188 << "' single-csv-multi-tid will be set" << std::endl;
189 dumpType = DumpType::THREAD_SEPARATION_BY_TID;
190 }
191
192 return dumpType;
193 }
194
RunDumpModulesMode(const std::string & outname)195 bool AsptConverter::RunDumpModulesMode(const std::string &outname)
196 {
197 if (CollectTracesStats() == 0) {
198 LOG(ERROR, PROFILER) << "No samples found in file";
199 return false;
200 }
201
202 if (!CollectModules()) {
203 LOG(ERROR, PROFILER) << "No modules found in file, names would not be resolved";
204 return false;
205 }
206
207 if (!DumpModulesToFile(outname)) {
208 LOG(ERROR, PROFILER) << "Can't dump modules to file";
209 return false;
210 }
211
212 return true;
213 }
214
RunDumpTracesInCsvMode(const std::string & outname)215 bool AsptConverter::RunDumpTracesInCsvMode(const std::string &outname)
216 {
217 if (CollectTracesStats() == 0) {
218 LOG(ERROR, PROFILER) << "No samples found in file";
219 return false;
220 }
221
222 if (!CollectModules()) {
223 LOG(ERROR, PROFILER) << "No modules found in file, names would not be resolved";
224 }
225
226 if (!BuildModulesMap()) {
227 LOG(ERROR, PROFILER) << "Can't build modules map";
228 return false;
229 }
230
231 BuildMethodsMap();
232 DumpResolvedTracesAsCSV(outname.c_str());
233
234 return true;
235 }
236
RunWithOptions(const Options & cliOptions)237 bool AsptConverter::RunWithOptions(const Options &cliOptions)
238 {
239 std::string outname = cliOptions.GetOutput();
240
241 dumpType_ = GetDumpTypeFromOptions(cliOptions);
242 buildColdGraph_ = cliOptions.IsColdGraphEnable();
243
244 if (cliOptions.IsSubstituteModuleDir()) {
245 substituteDirectories_ = {cliOptions.GetSubstituteSourceStr(), cliOptions.GetSubstituteDestinationStr()};
246 if (substituteDirectories_->source.size() != substituteDirectories_->destination.size()) {
247 LOG(FATAL, PROFILER) << "different number of strings in substitute option";
248 }
249 }
250
251 if (cliOptions.IsDumpModules()) {
252 return RunDumpModulesMode(outname);
253 }
254
255 return RunDumpTracesInCsvMode(outname);
256 }
257
258 } // namespace ark::tooling::sampler
259