1 /*
2 * Copyright (c) 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 "timers.h"
17 #include "os/file.h"
18
19 #include <algorithm>
20 #include <cstring>
21
22 #ifndef IS_ARKUI_X_TARGET
23 #include <nlohmann/json.hpp>
24 #endif
25
26 namespace panda {
27 TimeStartFunc Timer::timerStart = Timer::TimerStartDoNothing;
28 TimeEndFunc Timer::timerEnd = Timer::TimerEndDoNothing;
29 std::unordered_map<std::string_view, TimeRecord> Timer::timers_;
30 std::vector<std::string_view> Timer::events_;
31 std::mutex Timer::mutex_;
32 std::string Timer::perfFile_;
33 #ifndef IS_ARKUI_X_TARGET
34 static const int INDENT_LEVEL = 4;
35 #endif
36
InitializeTimer(std::string & perfFile)37 void Timer::InitializeTimer(std::string &perfFile)
38 {
39 if (!perfFile.empty()) {
40 Timer::timerStart = Timer::TimerStartImpl;
41 Timer::timerEnd = Timer::TimerEndImpl;
42 perfFile_ = perfFile;
43 }
44 }
45
WriteFile(std::stringstream & ss,std::string & perfFile)46 void WriteFile(std::stringstream &ss, std::string &perfFile)
47 {
48 std::ofstream fs;
49 fs.open(panda::os::file::File::GetExtendedFilePath(perfFile));
50 if (!fs.is_open()) {
51 std::cerr << "Failed to open perf file: " << perfFile << ". Errro: " << std::strerror(errno) << std::endl;
52 return;
53 }
54 fs << ss.str();
55 fs.close();
56 }
57
DescentComparator(const std::pair<std::string,double> p1,const std::pair<std::string,double> p2)58 bool DescentComparator(const std::pair<std::string, double> p1, const std::pair<std::string, double> p2)
59 {
60 return p1.second > p2.second;
61 }
62
ProcessTimePointRecord(const std::string & file,const TimePointRecord & timePointRecord,double & eventTime,std::vector<std::pair<std::string,double>> & eachFileTime,std::vector<std::string> & incompleteRecords)63 void ProcessTimePointRecord(
64 const std::string& file,
65 const TimePointRecord& timePointRecord,
66 double& eventTime,
67 std::vector<std::pair<std::string, double>>& eachFileTime,
68 std::vector<std::string>& incompleteRecords)
69 {
70 if (timePointRecord.startTime != std::chrono::steady_clock::time_point() &&
71 timePointRecord.endTime != std::chrono::steady_clock::time_point()) {
72 auto duration = timePointRecord.endTime - timePointRecord.startTime;
73 if (duration.count() >= 0) {
74 auto t = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
75 eventTime += t;
76 if (!file.empty()) {
77 eachFileTime.emplace_back(file, t);
78 }
79 } else {
80 incompleteRecords.push_back(file + ": The end time is earlier than the start time");
81 }
82 } else {
83 incompleteRecords.push_back(file + ": " +
84 (timePointRecord.startTime == std::chrono::steady_clock::time_point() ?
85 "Lack of start time" : "Lack of end time"));
86 }
87 }
88
PrintTimers()89 void Timer::PrintTimers()
90 {
91 #ifndef IS_ARKUI_X_TARGET
92 const std::string suffix = ".json";
93 if (perfFile_.length() >= suffix.length() &&
94 perfFile_.compare(perfFile_.length() - suffix.length(), suffix.length(), suffix) == 0) {
95 PrintJson();
96 return;
97 }
98 #endif
99 PrintTxt();
100 }
101
102 #ifndef IS_ARKUI_X_TARGET
to_json(nlohmann::json & j,const TimeRecord & t)103 void to_json(nlohmann::json& j, const TimeRecord& t)
104 {
105 auto early = std::chrono::steady_clock::time_point::max();
106 auto late = std::chrono::steady_clock::time_point::min();
107
108 for (auto& [fileName, timePointRecord] : t.timePoints) {
109 if (timePointRecord.startTime < early) {
110 early = timePointRecord.startTime;
111 }
112 if (timePointRecord.endTime > late) {
113 late = timePointRecord.endTime;
114 }
115 }
116 auto start = std::chrono::duration_cast<std::chrono::nanoseconds>(early.time_since_epoch()).count();
117 auto end = std::chrono::duration_cast<std::chrono::nanoseconds>(late.time_since_epoch()).count();
118 auto duration = end - start;
119
120 std::string parentEvent;
121 auto eventIter = eventParent.find(t.event);
122 if (eventIter != eventParent.end()) {
123 parentEvent = eventIter->second;
124 }
125 j = nlohmann::json{
126 {"name", t.event},
127 {"parentEvent", parentEvent},
128 {"startTime", start},
129 {"endTime", end},
130 {"duration", duration}
131 };
132 }
133
PrintJson()134 void Timer::PrintJson()
135 {
136 std::stringstream ss;
137 nlohmann::json jsonArray = nlohmann::json::array();
138
139 for (auto &event: events_) {
140 auto &timeRecord = timers_.at(event);
141 nlohmann::json j = timeRecord;
142 jsonArray.push_back(j);
143 }
144
145 ss << jsonArray.dump(INDENT_LEVEL) << std::endl;
146
147 WriteFile(ss, perfFile_);
148 }
149 #endif
150
PrintTxt()151 void Timer::PrintTxt()
152 {
153 std::stringstream ss;
154 ss << "------------- Compilation time consumption in milliseconds: -------------" << std::endl;
155 ss << "Note: When compiling multiple files in parallel, " <<
156 "we will track the time consumption of each file individually. The output will aggregate these times, " <<
157 "potentially resulting in a total time consumption less than the sum of individual file times." <<
158 std::endl << std::endl;
159
160 std::vector<std::string> summedUpTimeString;
161 ss << "------------- Compilation time consumption of each file: ----------------" << std::endl;
162
163 for (auto &event: events_) {
164 auto &timeRecord = timers_.at(event);
165 auto formattedEvent =
166 std::string(timeRecord.level, ' ') + std::string(timeRecord.level, '#') + std::string(event);
167
168 double eventTime = 0.0;
169 std::vector<std::pair<std::string, double>> eachFileTime;
170 std::vector<std::string> incompleteRecords;
171
172 for (const auto &[file, timePointRecord] : timeRecord.timePoints) {
173 ProcessTimePointRecord(file, timePointRecord, eventTime, eachFileTime, incompleteRecords);
174 }
175
176 // print each file time consumption in descending order
177 if (!eachFileTime.empty()) {
178 std::sort(eachFileTime.begin(), eachFileTime.end(), DescentComparator);
179 ss << formattedEvent << ", time consumption of each file:" << std::endl;
180 }
181 for (auto &pair : eachFileTime) {
182 if (!pair.first.empty()) {
183 ss << pair.first << ": " << pair.second << " ms" <<std::endl;
184 }
185 }
186
187 // Print incomplete records
188 if (!incompleteRecords.empty()) {
189 ss << formattedEvent << ", Incomplete records:" << std::endl;
190 for (const auto& record : incompleteRecords) {
191 ss << " " << record << std::endl;
192 }
193 }
194
195 // collect the sum of each file's time consumption
196 std::stringstream eventSummedUpTimeStream;
197 eventSummedUpTimeStream << formattedEvent << ": " << eventTime << " ms";
198 summedUpTimeString.push_back(eventSummedUpTimeStream.str());
199 }
200 ss << "------------- Compilation time consumption summed up: -------------------" << std::endl;
201 for (auto &str: summedUpTimeString) {
202 ss << str << std::endl;
203 }
204 ss << "-------------------------------------------------------------------------" << std::endl;
205 WriteFile(ss, perfFile_);
206 }
207
TimerStartImpl(const std::string_view event,std::string fileName)208 void Timer::TimerStartImpl(const std::string_view event, std::string fileName)
209 {
210 TimePointRecord tpr;
211 tpr.startTime = std::chrono::steady_clock::now();
212 int level = 0;
213 auto eventIter = eventMap.find(event);
214 if (eventIter != eventMap.end()) {
215 level = eventIter->second;
216 } else {
217 std::cerr << "Undefined event: " << event << ". Please check!" << std::endl;
218 }
219
220 std::unique_lock<std::mutex> lock(mutex_);
221 auto iter = timers_.find(event);
222 if (iter != timers_.end()) {
223 iter->second.timePoints.emplace(fileName, tpr);
224 } else {
225 TimeRecord tr;
226 tr.timePoints.emplace(fileName, tpr);
227 tr.event = event;
228 tr.level = level;
229
230 timers_.emplace(event, tr);
231 events_.push_back(event);
232 }
233 }
234
TimerEndImpl(const std::string_view event,std::string fileName)235 void Timer::TimerEndImpl(const std::string_view event, std::string fileName)
236 {
237 auto endTime = std::chrono::steady_clock::now();
238 std::unique_lock<std::mutex> lock(mutex_);
239 auto time_record_iter = timers_.find(event);
240 if (time_record_iter == timers_.end()) {
241 std::cerr << "Event " << event << " not found in records, skip record end time!" << std::endl;
242 return;
243 }
244 auto& timePoints = time_record_iter->second.timePoints;
245 auto time_point_iter = timePoints.find(fileName);
246 if (time_point_iter == timePoints.end()) {
247 std::cerr << "Event " << event << " and file " << fileName <<
248 " start timer not found in records, skip record end time!" << std::endl;
249 return;
250 }
251 time_point_iter->second.endTime = endTime;
252 }
253
254 } // namespace panda
255