1 /**
2 * Copyright 2019-2023 Huawei Technologies Co., Ltd
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 "utils/profile.h"
18 #include <chrono>
19 #include <numeric>
20 #include <cstdio>
21 #include <sstream>
22 #include <iomanip>
23 #include <vector>
24 #include <list>
25 #include <utility>
26 #include <cfloat>
27 #include "utils/log_adapter.h"
28 #include "utils/ms_utils.h"
29 #include "utils/compile_config.h"
30
31 namespace mindspore {
32 namespace {
33 constexpr size_t TIME_INFO_PREFIX_NUM_LEN = 4;
34 constexpr char kProcessStatusFileName[] = "/proc/self/status";
35 constexpr auto kLineMaxSize = 1024;
36 constexpr auto kInvalid = -1;
37 const auto kVmRSS = "VmRSS";
38
39 void PrintProfile(std::ostringstream &oss, const TimeInfo &time_info, int indent = 0,
40 std::map<std::string, double> *sums = nullptr, const std::string &prefix = "");
41
PrintTimeInfoMap(std::ostringstream & oss,const TimeInfoMap & dict,int indent=0,std::map<std::string,double> * sums=nullptr,const std::string & prefix="")42 void PrintTimeInfoMap(std::ostringstream &oss, const TimeInfoMap &dict, int indent = 0,
43 std::map<std::string, double> *sums = nullptr, const std::string &prefix = "") {
44 size_t count = 0;
45 for (const auto &iter : dict) {
46 count++;
47 if (iter.second == nullptr) {
48 continue;
49 }
50 // Indent by multiples of 4 spaces.
51 if (iter.first.size() < TIME_INFO_PREFIX_NUM_LEN) {
52 MS_LOG(INTERNAL_EXCEPTION) << "In TimeInfoMap, the " << count << "th string key is " << iter.first
53 << ", but the length is less than " << TIME_INFO_PREFIX_NUM_LEN;
54 }
55 auto name = iter.first.substr(TIME_INFO_PREFIX_NUM_LEN);
56 oss << std::setw(indent * 4) << ""
57 << "[" << name << "]: " << iter.second->time_;
58 if (iter.second->dict_ != nullptr) {
59 oss << ", [" << iter.second->dict_->size() << "]";
60 }
61 oss << "\n";
62
63 std::string newPrefix = prefix;
64 if (iter.first.find("Cycle ") == std::string::npos) {
65 newPrefix = prefix.empty() ? iter.first : prefix + "." + iter.first;
66 }
67 PrintProfile(oss, *iter.second, indent + 1, sums, newPrefix);
68 if (iter.second->dict_ == nullptr) {
69 (*sums)[newPrefix] += iter.second->time_;
70 }
71 }
72 }
73
PrintProfile(std::ostringstream & oss,const TimeInfo & time_info,int indent,std::map<std::string,double> * sums,const std::string & prefix)74 void PrintProfile(std::ostringstream &oss, const TimeInfo &time_info, int indent, std::map<std::string, double> *sums,
75 const std::string &prefix) {
76 bool need_free = false;
77 if (sums == nullptr) {
78 sums = new (std::nothrow) std::map<std::string, double>();
79 if (sums == nullptr) {
80 MS_LOG(ERROR) << "memory allocation failed";
81 return;
82 }
83 need_free = true;
84 }
85
86 // Indent by multiples of 4 spaces.
87 if (indent == 0) {
88 oss << "\nTotalTime = " << time_info.time_;
89 if (time_info.dict_ != nullptr) {
90 oss << ", [" << time_info.dict_->size() << "]";
91 }
92 oss << "\n";
93 }
94
95 if (time_info.dict_ != nullptr) {
96 PrintTimeInfoMap(oss, *time_info.dict_, indent, sums, prefix);
97 }
98
99 // Print time percentage info
100 if (need_free) {
101 double total = 0.0;
102 for (auto iter = sums->begin(); iter != sums->end(); ++iter) {
103 total += iter->second;
104 }
105 oss << "Sums\n";
106 if (total >= 0.0 + DBL_EPSILON) {
107 for (auto &iter : *sums) {
108 std::string name = iter.first;
109 name.erase(0, TIME_INFO_PREFIX_NUM_LEN);
110 std::size_t pos = 0;
111 while ((pos = name.find('.', pos)) != std::string::npos) {
112 pos++;
113 name.erase(pos, TIME_INFO_PREFIX_NUM_LEN);
114 }
115 oss << " " << std::left << std::setw(36) << name << " : " << std::right << std::setw(12) << std::fixed
116 << std::setprecision(6) << iter.second << "s : " << std::right << std::setw(5) << std::fixed
117 << std::setprecision(2) << (iter.second / total) * 100 << "%\n";
118 }
119 }
120 delete sums;
121 }
122 }
123 } // namespace
124
GetTime()125 double GetTime() {
126 auto now = std::chrono::steady_clock::now();
127 std::chrono::duration<double> d = now.time_since_epoch();
128 return d.count();
129 }
130
~TimeInfo()131 TimeInfo::~TimeInfo() {
132 if (dict_ == nullptr) {
133 return;
134 }
135 for (auto iter = dict_->begin(); iter != dict_->end(); ++iter) {
136 delete iter->second;
137 iter->second = nullptr;
138 }
139 delete dict_;
140 dict_ = nullptr;
141 }
142
ProfileBase()143 ProfileBase::ProfileBase() : context_("", this) {
144 ctx_ptr_ = &context_;
145 context_.parent_ = nullptr;
146 }
147
~ProfileBase()148 ProfileBase::~ProfileBase() {
149 context_.parent_ = nullptr;
150 if (context_.time_info_ != nullptr) {
151 delete context_.time_info_;
152 context_.time_info_ = nullptr;
153 }
154 ctx_ptr_ = nullptr;
155 }
156
Print()157 void Profile::Print() {
158 if (ctx_ptr_ == nullptr || ctx_ptr_->time_info_ == nullptr) {
159 return;
160 }
161 std::ostringstream oss;
162 PrintProfile(oss, *ctx_ptr_->time_info_);
163 // Here use printf to output profile info, not use MS_LOG(INFO) since when open log, it affects performance
164 std::cout << oss.str() << std::endl;
165 }
166
167 // Start a step in the current context with the given name.
168 // Name must be unique otherwise the previous record will be overwritten.
Step(const std::string & name)169 ProfContext *Profile::Step(const std::string &name) {
170 ctx_ptr_ = new (std::nothrow) ProfContext(name, this);
171 if (ctx_ptr_ == nullptr) {
172 MS_LOG(ERROR) << "memory allocation failed";
173 return nullptr;
174 }
175 return ctx_ptr_;
176 }
177
178 // Creates subcontext for a repeated action.
179 // Count should be monotonically increasing.
Lap(int count)180 ProfContext *Profile::Lap(int count) {
181 std::ostringstream oss;
182 oss << "Cycle " << count;
183 ctx_ptr_ = new (std::nothrow) ProfContext(oss.str(), this);
184 if (ctx_ptr_ == nullptr) {
185 MS_LOG(ERROR) << "memory allocation failed";
186 return nullptr;
187 }
188 return ctx_ptr_;
189 }
190
Pop()191 void Profile::Pop() noexcept {
192 if (ctx_ptr_ == nullptr) {
193 return;
194 }
195 ctx_ptr_ = ctx_ptr_->parent_;
196 }
197
ProfContext(const std::string & name,ProfileBase * const prof)198 ProfContext::ProfContext(const std::string &name, ProfileBase *const prof)
199 : name_(name), prof_(prof), time_info_(nullptr) {
200 // Initialize a subcontext.
201 if (prof == nullptr || IsTopContext()) {
202 parent_ = nullptr;
203 } else {
204 parent_ = prof->ctx_ptr_;
205 }
206 }
207
~ProfContext()208 ProfContext::~ProfContext() {
209 // Top level context
210 if (parent_ == nullptr || IsTopContext()) {
211 if (time_info_ != nullptr) {
212 delete time_info_;
213 }
214 } else {
215 parent_->Insert(name_, time_info_);
216 if (prof_ != nullptr) {
217 prof_->Pop();
218 }
219 }
220
221 time_info_ = nullptr;
222 prof_ = nullptr;
223 parent_ = nullptr;
224 }
225
SetTime(double time)226 void ProfContext::SetTime(double time) noexcept {
227 if (time_info_ == nullptr) {
228 time_info_ = new (std::nothrow) TimeInfo(time);
229 if (time_info_ == nullptr) {
230 MS_LOG(ERROR) << "memory allocation failed";
231 return;
232 }
233 }
234 time_info_->time_ = time;
235 }
236
Insert(const std::string & name,const TimeInfo * time)237 void ProfContext::Insert(const std::string &name, const TimeInfo *time) noexcept {
238 if (time_info_ == nullptr) {
239 time_info_ = new (std::nothrow) TimeInfo();
240 if (time_info_ == nullptr) {
241 MS_LOG(ERROR) << "memory allocation failed";
242 delete time;
243 time = nullptr;
244 return;
245 }
246 }
247
248 if (time_info_->dict_ == nullptr) {
249 time_info_->dict_ = new (std::nothrow) TimeInfoMap();
250 if (time_info_->dict_ == nullptr) {
251 MS_LOG(ERROR) << "memory allocation failed";
252 delete time;
253 time = nullptr;
254 delete time_info_;
255 time_info_ = nullptr;
256 return;
257 }
258 }
259
260 std::stringstream ss;
261 ss << std::setw(TIME_INFO_PREFIX_NUM_LEN) << std::setfill('0') << time_info_->actionNum_;
262 std::string sorted_name(ss.str() + name);
263 time_info_->actionNum_++;
264 auto iter = time_info_->dict_->find(sorted_name);
265 // If contains item with same name, delete it
266 if (iter != time_info_->dict_->end()) {
267 delete iter->second;
268 iter->second = nullptr;
269 (void)time_info_->dict_->erase(iter);
270 }
271 (*time_info_->dict_)[sorted_name] = time;
272 }
273
IsTopContext() const274 bool ProfContext::IsTopContext() const noexcept { return (prof_ != nullptr) && (this == &prof_->context_); }
275
ProfTransaction(const ProfileBase * prof)276 ProfTransaction::ProfTransaction(const ProfileBase *prof) { ctx_ = (prof != nullptr ? prof->ctx_ptr_ : nullptr); }
277
ProfTransaction(ProfContext * const ctx)278 ProfTransaction::ProfTransaction(ProfContext *const ctx) : ctx_(ctx) {}
279
~ProfTransaction()280 ProfTransaction::~ProfTransaction() {
281 if (ctx_ != nullptr && !ctx_->IsTopContext()) {
282 delete ctx_;
283 }
284 ctx_ = nullptr;
285 }
286
GetInstance()287 DumpTime &DumpTime::GetInstance() {
288 static DumpTime instance;
289 return instance;
290 }
291
~DumpTime()292 DumpTime::~DumpTime() {
293 try {
294 Save();
295 } catch (const std::exception &e) {
296 MS_LOG(ERROR) << "Cannot save file by profile::DumpTime::save";
297 } catch (...) {
298 MS_LOG(ERROR) << "Uncaught exception";
299 }
300 }
301
Record(const std::string & step_name,const double time,const bool is_start)302 void DumpTime::Record(const std::string &step_name, const double time, const bool is_start) {
303 file_ss_ << " {" << std::endl;
304 file_ss_ << " \"name\": "
305 << "\"" << step_name << "\"," << std::endl;
306 file_ss_ << " \"cat\": "
307 << "\"FUNCTION\"," << std::endl;
308 if (is_start) {
309 file_ss_ << " \"ph\": "
310 << "\"B\"," << std::endl;
311 } else {
312 file_ss_ << " \"ph\": "
313 << "\"E\"," << std::endl;
314 }
315 file_ss_ << " \"ts\": " << std::setprecision(16) << time * 1000000 << "," << std::endl;
316 file_ss_ << " \"pid\": "
317 << "1" << std::endl;
318 file_ss_ << " }" << std::endl;
319 file_ss_ << " ," << std::endl;
320 }
321
Save()322 void DumpTime::Save() {
323 try {
324 file_out_.open(file_path_, std::ios::trunc | std::ios::out);
325 } catch (const std::exception &e) {
326 MS_LOG(EXCEPTION) << "Cannot open file in " << (file_path_);
327 }
328 file_out_ << "{\n";
329 file_out_ << " \"traceEvents\": [" << std::endl;
330 file_ss_ >> file_out_.rdbuf();
331 constexpr int offset = -7;
332 (void)file_out_.seekp(offset, std::ios::end);
333 file_out_ << " ]" << std::endl << " ,\n";
334 file_out_ << " \"displayTimeUnit\": \"ms\"" << std::endl;
335 file_out_ << "}";
336 file_out_.close();
337 }
338
339 struct TimeInfoGroup {
340 double total_time = 0.0;
341 int total_count = 0;
342 std::list<std::map<std::string, TimeStat>::const_iterator> items;
343 };
344
PrintTimeStat(std::ostringstream & oss,const TimeInfoGroup & group,const std::string & prefix)345 static void PrintTimeStat(std::ostringstream &oss, const TimeInfoGroup &group, const std::string &prefix) {
346 oss << "------[" << prefix << "] " << std::setw(10) << std::fixed << std::setprecision(6) << group.total_time
347 << std::setw(6) << group.total_count << "\n";
348 for (const auto &iter : group.items) {
349 oss << std::setw(5) << std::fixed << std::setprecision(2) << (iter->second.time_ / group.total_time) * 100
350 << "% : " << std::setw(12) << std::fixed << std::setprecision(6) << iter->second.time_ << "s : " << std::setw(6)
351 << iter->second.count_ << ": " << iter->first << "\n";
352 }
353 }
354
~MsProfile()355 MsProfile::~MsProfile() { Clear(); }
356
Reset()357 void MsProfile::Reset() { GetSingleton().Clear(); }
358
GetProfile()359 ProfileBase *MsProfile::GetProfile() {
360 MsProfile &ms_prof = GetSingleton();
361 if (ms_prof.profile_ == nullptr) {
362 if (EnabledProfile()) {
363 ms_prof.profile_ = new Profile();
364 } else {
365 ms_prof.profile_ = new ProfileBase();
366 }
367 }
368 return ms_prof.profile_;
369 }
370
GetSingleton()371 MsProfile &MsProfile::GetSingleton() {
372 static MsProfile profile;
373 return profile;
374 }
375
Clear()376 void MsProfile::Clear() {
377 time_stat_.clear();
378 if (profile_ != nullptr) {
379 delete profile_;
380 profile_ = nullptr;
381 }
382 }
383
StatTime(const std::string & id,double time)384 void MsProfile::StatTime(const std::string &id, double time) { GetSingleton().time_stat_[id] += time; }
385
Print()386 void MsProfile::Print() {
387 GetProfile()->Print();
388 std::vector<std::string> items = {"substitution.", "type_inference.", "replace.", "match.", "predicate.",
389 "func_graph_cloner_run.", "meta_graph.", "manager.", "pynative"};
390 std::vector<TimeInfoGroup> groups(items.size() + 1);
391 const auto &stat = GetSingleton().time_stat_;
392 // Group all time infos
393 for (auto iter = stat.cbegin(); iter != stat.cend(); ++iter) {
394 auto matched_idx = items.size();
395 for (size_t i = 0; i < items.size(); ++i) {
396 if (iter->first.find(items[i]) != std::string::npos) {
397 matched_idx = i;
398 break;
399 }
400 }
401 groups[matched_idx].total_time += iter->second.time_;
402 groups[matched_idx].total_count += iter->second.count_;
403 groups[matched_idx].items.push_back(iter);
404 }
405 std::ostringstream oss;
406 for (size_t i = 0; i < groups.size(); ++i) {
407 std::string prefix = (i < items.size() ? items[i] : std::string("others."));
408 PrintTimeStat(oss, groups[i], prefix);
409 }
410 // Here use printf to output profile info, not use MS_LOG(INFO) since when open log, it affects performance
411 std::cout << "\nTime group info:\n" << oss.str() << std::endl;
412 }
413
GetInstance()414 ProcessStatus &ProcessStatus::GetInstance() {
415 static ProcessStatus recorder;
416 return recorder;
417 }
418
GetMemoryCost(const std::string & key) const419 int64_t ProcessStatus::GetMemoryCost(const std::string &key) const {
420 #if defined(_WIN32) || defined(_WIN64) || defined(__APPLE__)
421 return kInvalid;
422 #else
423
424 FILE *file = fopen(kProcessStatusFileName, "r");
425 if (file == nullptr) {
426 MS_LOG(ERROR) << "Get process status file failed.";
427 return 0;
428 }
429
430 int64_t mem_size = 0;
431 char buf[kLineMaxSize] = {0};
432 while (fgets(buf, kLineMaxSize, file)) {
433 // Get mem title.
434 std::string line(buf);
435 auto title_end_pos = line.find(":");
436 auto title = line.substr(0, title_end_pos);
437 // Get mem size.
438 if (title == key) {
439 auto mem_size_end_pos = line.find_last_of(" ");
440 auto mem_size_begin_pos = line.find_last_of(" ", mem_size_end_pos - 1);
441 if ((mem_size_end_pos != std::string::npos) && (mem_size_begin_pos != std::string::npos)) {
442 auto mem_size_string = line.substr(mem_size_begin_pos, mem_size_end_pos - mem_size_begin_pos);
443 mem_size = std::atol(mem_size_string.c_str());
444 }
445 break;
446 }
447 if (memset_s(buf, kLineMaxSize, 0, kLineMaxSize) != EOK) {
448 MS_LOG(ERROR) << "Set process status file failed.";
449 (void)fclose(file);
450 return 0;
451 }
452 }
453 (void)fclose(file);
454 return mem_size;
455 #endif
456 }
457
RecordStart(const std::string & step_name)458 void ProcessStatus::RecordStart(const std::string &step_name) {
459 static const bool record_memory_flag = common::GetCompileConfig("RECORD_MEMORY") == "1";
460 if (!record_memory_flag) {
461 return;
462 }
463 MemoryInfo memory_info;
464 memory_info.name = step_name;
465 memory_info.depth = stack_.size();
466 memory_info.start_memory = GetMemoryCost(kVmRSS);
467 (void)stack_.emplace_back(memory_info);
468 }
469
RecordEnd()470 void ProcessStatus::RecordEnd() {
471 static const bool record_memory_flag = common::GetCompileConfig("RECORD_MEMORY") == "1";
472 if (!record_memory_flag) {
473 return;
474 }
475 if (stack_.empty()) {
476 MS_INTERNAL_EXCEPTION(ValueError) << "ProcessStatus stack is empty";
477 }
478 auto memory_info = stack_.back();
479 memory_info.end_memory = GetMemoryCost(kVmRSS);
480 (void)memory_used_.emplace_back(memory_info);
481 stack_.pop_back();
482 }
483
Print()484 void ProcessStatus::Print() {
485 static const bool record_memory_flag = common::GetCompileConfig("RECORD_MEMORY") == "1";
486 if (!record_memory_flag) {
487 return;
488 }
489 std::ostringstream oss;
490 constexpr auto indent = 2;
491 for (auto item : memory_used_) {
492 std::string spaces(item.depth * indent, ' ');
493 oss << spaces << "[" << item.name << "]: " << item.start_memory << "KB -> " << item.end_memory
494 << "KB. Increased: " << item.end_memory - item.start_memory << "KB\n";
495 }
496 std::cout << "\nMemory increase info:\n" << oss.str() << std::endl;
497 Clear();
498 }
499
Clear()500 void ProcessStatus::Clear() {
501 memory_used_.clear();
502 stack_.clear();
503 }
504 } // namespace mindspore
505