• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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