1 /*
2 * Copyright (c) 2023 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 "base/log/ace_performance_check.h"
17 #include "base/i18n/localization.h"
18 #include "base/log/ace_checker.h"
19 #include "base/log/dump_log.h"
20 #include "base/log/event_report.h"
21 #include "bridge/common/utils/utils.h"
22 #include "core/common/container.h"
23
24 namespace OHOS::Ace {
25 namespace {
26 constexpr int32_t BASE_YEAR = 1900;
27 constexpr char DATE_FORMAT[] = "MM-dd HH:mm:ss";
28 constexpr int32_t CONVERT_NANOSECONDS = 1000000;
29 constexpr int32_t FUNCTION_TIMEOUT = 150;
30 constexpr char ETS_PATH[] = "/src/main/ets/";
31 constexpr char DEBUG_PATH[] = "entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/";
32 constexpr char NEW_PATH[] = "entry|entry|1.0.0|src/main/ets/";
33 constexpr char TS_SUFFIX[] = ".ts";
34 constexpr char ETS_SUFFIX[] = ".ets";
35 } // namespace
36
37 // ============================== survival interval of JSON files ============================================
38
39 std::unique_ptr<JsonValue> AcePerformanceCheck::performanceInfo_ = nullptr;
40 std::string AceScopedPerformanceCheck::currentPath_;
41 std::vector<std::pair<int64_t, std::string>> AceScopedPerformanceCheck::records_;
Start()42 void AcePerformanceCheck::Start()
43 {
44 if (AceChecker::IsPerformanceCheckEnabled()) {
45 LOGI("performance check start");
46 performanceInfo_ = JsonUtil::Create(true);
47 }
48 }
49
Stop()50 void AcePerformanceCheck::Stop()
51 {
52 if (performanceInfo_) {
53 LOGI("performance check stop");
54 auto info = performanceInfo_->ToString();
55 // output info to json file
56 auto filePath = AceApplicationInfo::GetInstance().GetDataFileDirPath() + "/arkui_bestpractice.json";
57 std::unique_ptr<std::ostream> ss = std::make_unique<std::ofstream>(filePath);
58 CHECK_NULL_VOID(ss);
59 DumpLog::GetInstance().SetDumpFile(std::move(ss));
60 DumpLog::GetInstance().Print(info);
61 DumpLog::GetInstance().Reset();
62 AceChecker::NotifyCaution("AcePerformanceCheck::Stop, json data generated, store in " + filePath);
63 performanceInfo_.reset(nullptr);
64 }
65 }
66
67 // ============================== specific implementation ======================================================
68
AceScopedPerformanceCheck(const std::string & name)69 AceScopedPerformanceCheck::AceScopedPerformanceCheck(const std::string& name)
70 {
71 // micro time.
72 markTime_ = GetSysTimestamp();
73 name_ = name;
74 }
75
~AceScopedPerformanceCheck()76 AceScopedPerformanceCheck::~AceScopedPerformanceCheck()
77 {
78 auto time = static_cast<int64_t>((GetSysTimestamp() - markTime_) / CONVERT_NANOSECONDS);
79 if (time >= FUNCTION_TIMEOUT) {
80 EventReport::ReportFunctionTimeout(name_, time, FUNCTION_TIMEOUT);
81 }
82 if (AcePerformanceCheck::performanceInfo_) {
83 // convert micro time to ms with 1000.
84 std::pair recordInfo { time, name_ };
85 records_.push_back(recordInfo);
86 }
87 }
88
CheckIsRuleContainsPage(const std::string & ruleType,const std::string & pagePath)89 bool AceScopedPerformanceCheck::CheckIsRuleContainsPage(const std::string& ruleType, const std::string& pagePath)
90 {
91 // check for the presence of rule json
92 CHECK_NULL_RETURN(AcePerformanceCheck::performanceInfo_, false);
93 if (!AcePerformanceCheck::performanceInfo_->Contains(ruleType)) {
94 AcePerformanceCheck::performanceInfo_->Put(ruleType.c_str(), JsonUtil::CreateArray(true));
95 return false;
96 }
97 auto ruleJson = AcePerformanceCheck::performanceInfo_->GetValue(ruleType);
98 auto size = ruleJson->GetArraySize();
99 for (int32_t i = 0; i < size; i++) {
100 auto indexJson = ruleJson->GetArrayItem(i);
101 auto value = indexJson->GetString("pagePath", {});
102 if (value == pagePath) {
103 return true;
104 }
105 }
106 return false;
107 }
108
GetCurrentTime()109 std::string AceScopedPerformanceCheck::GetCurrentTime()
110 {
111 // get system date and time
112 auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
113 auto* local = std::localtime(&now);
114 if (!local) {
115 return {};
116 }
117
118 // this is for i18n date time
119 DateTime dateTime;
120 dateTime.year = static_cast<uint32_t>(local->tm_year + BASE_YEAR);
121 dateTime.month = static_cast<uint32_t>(local->tm_mon);
122 dateTime.day = static_cast<uint32_t>(local->tm_mday);
123 dateTime.hour = static_cast<uint32_t>(local->tm_hour);
124 dateTime.minute = static_cast<uint32_t>(local->tm_min);
125 dateTime.second = static_cast<uint32_t>(local->tm_sec);
126 auto time = Localization::GetInstance()->FormatDateTime(dateTime, DATE_FORMAT);
127 return time;
128 }
129
GetCodeInfo(int32_t row,int32_t col)130 CodeInfo AceScopedPerformanceCheck::GetCodeInfo(int32_t row, int32_t col)
131 {
132 auto sourceMap = GetCurrentSourceMap();
133 CHECK_NULL_RETURN(sourceMap, {});
134 // There is no same row and column info of viewPU in sourcemap, but the row info is correct.
135 auto codeInfo = sourceMap->Find(row, col, false);
136 return { codeInfo.row, codeInfo.col, codeInfo.sources };
137 }
138
CheckPage(const CodeInfo & codeInfo,const std::string & rule)139 bool AceScopedPerformanceCheck::CheckPage(const CodeInfo& codeInfo, const std::string& rule)
140 {
141 if (!codeInfo.sources.empty() && CheckIsRuleContainsPage(rule, codeInfo.sources)) {
142 return true;
143 }
144 return false;
145 }
146
RecordPerformanceCheckData(const PerformanceCheckNodeMap & nodeMap,int64_t vsyncTimeout,std::string path)147 void AceScopedPerformanceCheck::RecordPerformanceCheckData(
148 const PerformanceCheckNodeMap& nodeMap, int64_t vsyncTimeout, std::string path)
149 {
150 currentPath_ = path;
151 auto codeInfo = GetCodeInfo(1, 1);
152 std::vector<PerformanceCheckNode> pageNodeList;
153 std::vector<PerformanceCheckNode> flexNodeList;
154 std::unordered_map<int32_t, PerformanceCheckNode> foreachNodeMap;
155 int32_t itemCount = 0;
156 int32_t maxDepth = 0;
157 for (const auto& node : nodeMap) {
158 if (node.second.childrenSize >= AceChecker::GetNodeChildren()) {
159 pageNodeList.emplace_back(node.second);
160 }
161 if (node.second.pageDepth > maxDepth) {
162 maxDepth = node.second.pageDepth;
163 }
164 if (node.second.flexLayouts != 0 && node.second.flexLayouts >= AceChecker::GetFlexLayouts()) {
165 flexNodeList.emplace_back(node.second);
166 }
167 if (node.second.isForEachItem) {
168 itemCount++;
169 auto iter = foreachNodeMap.find(node.second.codeRow);
170 if (iter != foreachNodeMap.end()) {
171 iter->second.foreachItems++;
172 } else {
173 foreachNodeMap.insert(std::make_pair(node.second.codeRow, node.second));
174 }
175 }
176 }
177 RecordFunctionTimeout();
178 RecordPageNodeCountAndDepth(nodeMap.size(), maxDepth, pageNodeList, codeInfo);
179 RecordForEachItemsCount(itemCount, foreachNodeMap, codeInfo);
180 RecordFlexLayoutsCount(flexNodeList, codeInfo);
181 RecordVsyncTimeout(nodeMap, vsyncTimeout / CONVERT_NANOSECONDS, codeInfo);
182 }
183
RecordPageNodeCountAndDepth(int32_t pageNodeCount,int32_t pageDepth,std::vector<PerformanceCheckNode> & pageNodeList,const CodeInfo & codeInfo)184 void AceScopedPerformanceCheck::RecordPageNodeCountAndDepth(
185 int32_t pageNodeCount, int32_t pageDepth, std::vector<PerformanceCheckNode>& pageNodeList, const CodeInfo& codeInfo)
186 {
187 if ((pageNodeCount < AceChecker::GetPageNodes() && pageDepth < AceChecker::GetPageDepth()) ||
188 CheckPage(codeInfo, "9901")) {
189 return;
190 }
191 auto eventTime = GetCurrentTime();
192 CHECK_NULL_VOID(AcePerformanceCheck::performanceInfo_);
193 auto ruleJson = AcePerformanceCheck::performanceInfo_->GetValue("9901");
194 auto pageJson = JsonUtil::Create(true);
195 pageJson->Put("eventTime", eventTime.c_str());
196 pageJson->Put("pagePath", codeInfo.sources.c_str());
197 pageJson->Put("nodeCount", pageNodeCount);
198 pageJson->Put("depth", pageDepth);
199 // add children size > 100 of component to pageJson
200 for (const auto& iter : pageNodeList) {
201 auto componentJson = JsonUtil::Create(true);
202 componentJson->Put("name", iter.nodeTag.c_str());
203 componentJson->Put("items", iter.childrenSize);
204 componentJson->Put("sourceLine", GetCodeInfo(iter.codeRow, iter.codeCol).row);
205 std::unique_ptr<JsonValue> componentsJson;
206 if (pageJson->Contains("components")) {
207 componentsJson = pageJson->GetValue("components");
208 componentsJson->Put(componentJson);
209 } else {
210 componentsJson = JsonUtil::CreateArray(true);
211 componentsJson->Put(componentJson);
212 pageJson->Put("components", componentsJson);
213 }
214 }
215 ruleJson->Put(pageJson);
216 }
217
RecordFunctionTimeout()218 void AceScopedPerformanceCheck::RecordFunctionTimeout()
219 {
220 CHECK_NULL_VOID(AcePerformanceCheck::performanceInfo_);
221 if (records_.empty()) {
222 return;
223 }
224 for (const auto &record : records_) {
225 if (record.first < AceChecker::GetFunctionTimeout()) {
226 continue;
227 }
228 auto codeInfo = GetCodeInfo(1, 1);
229 if (!codeInfo.sources.empty()) {
230 continue;
231 }
232 CheckIsRuleContainsPage("9902", codeInfo.sources);
233 auto eventTime = GetCurrentTime();
234 auto ruleJson = AcePerformanceCheck::performanceInfo_->GetValue("9902");
235 auto pageJson = JsonUtil::Create(true);
236 pageJson->Put("eventTime", eventTime.c_str());
237 pageJson->Put("pagePath", codeInfo.sources.c_str());
238 pageJson->Put("functionName", record.second.c_str());
239 pageJson->Put("costTime", record.first);
240 ruleJson->Put(pageJson);
241 }
242 records_.clear();
243 }
244
RecordVsyncTimeout(const PerformanceCheckNodeMap & nodeMap,int64_t vsyncTimeout,const CodeInfo & codeInfo)245 void AceScopedPerformanceCheck::RecordVsyncTimeout(
246 const PerformanceCheckNodeMap& nodeMap, int64_t vsyncTimeout, const CodeInfo& codeInfo)
247 {
248 if (vsyncTimeout < AceChecker::GetVsyncTimeout() || CheckPage(codeInfo, "9903")) {
249 return;
250 }
251 auto eventTime = GetCurrentTime();
252 CHECK_NULL_VOID(AcePerformanceCheck::performanceInfo_);
253 auto ruleJson = AcePerformanceCheck::performanceInfo_->GetValue("9903");
254 auto pageJson = JsonUtil::Create(true);
255 pageJson->Put("eventTime", eventTime.c_str());
256 pageJson->Put("pagePath", codeInfo.sources.c_str());
257 pageJson->Put("costTime", vsyncTimeout);
258 for (const auto& node : nodeMap) {
259 int64_t layoutTime = node.second.layoutTime / CONVERT_NANOSECONDS;
260 if (layoutTime != 0 && layoutTime >= AceChecker::GetNodeTimeout() && node.second.nodeTag != "page" &&
261 node.second.nodeTag != "ContainerModal" && node.second.nodeTag != "JsView") {
262 auto componentJson = JsonUtil::Create(true);
263 componentJson->Put("name", node.second.nodeTag.c_str());
264 componentJson->Put("costTime", layoutTime);
265 componentJson->Put("sourceLine", GetCodeInfo(node.second.codeRow, node.second.codeCol).row);
266 std::unique_ptr<JsonValue> componentsJson;
267 if (pageJson->Contains("components")) {
268 componentsJson = pageJson->GetValue("components");
269 componentsJson->Put(componentJson);
270 } else {
271 componentsJson = JsonUtil::CreateArray(true);
272 componentsJson->Put(componentJson);
273 pageJson->Put("components", componentsJson);
274 }
275 }
276 }
277 ruleJson->Put(pageJson);
278 }
279
RecordForEachItemsCount(int32_t count,std::unordered_map<int32_t,PerformanceCheckNode> & foreachNodeMap,const CodeInfo & codeInfo)280 void AceScopedPerformanceCheck::RecordForEachItemsCount(
281 int32_t count, std::unordered_map<int32_t, PerformanceCheckNode>& foreachNodeMap, const CodeInfo& codeInfo)
282 {
283 if (count == 0 || count < AceChecker::GetForeachItems() || CheckPage(codeInfo, "9904")) {
284 return;
285 }
286 auto eventTime = GetCurrentTime();
287 CHECK_NULL_VOID(AcePerformanceCheck::performanceInfo_);
288 auto ruleJson = AcePerformanceCheck::performanceInfo_->GetValue("9904");
289 auto pageJson = JsonUtil::Create(true);
290 pageJson->Put("eventTime", eventTime.c_str());
291 pageJson->Put("pagePath", codeInfo.sources.c_str());
292 for (const auto& iter : foreachNodeMap) {
293 auto componentJson = JsonUtil::Create(true);
294 componentJson->Put("name", iter.second.nodeTag.c_str());
295 componentJson->Put("items", iter.second.foreachItems + 1);
296 componentJson->Put("sourceLine", GetCodeInfo(iter.second.codeRow, iter.second.codeCol).row);
297 std::unique_ptr<JsonValue> componentsJson;
298 if (pageJson->Contains("components")) {
299 componentsJson = pageJson->GetValue("components");
300 componentsJson->Put(componentJson);
301 } else {
302 componentsJson = JsonUtil::CreateArray(true);
303 componentsJson->Put(componentJson);
304 pageJson->Put("components", componentsJson);
305 }
306 }
307 ruleJson->Put(pageJson);
308 }
309
RecordFlexLayoutsCount(const std::vector<PerformanceCheckNode> & flexNodeList,const CodeInfo & codeInfo)310 void AceScopedPerformanceCheck::RecordFlexLayoutsCount(
311 const std::vector<PerformanceCheckNode>& flexNodeList, const CodeInfo& codeInfo)
312 {
313 if (flexNodeList.empty() || CheckPage(codeInfo, "9905")) {
314 return;
315 }
316 auto eventTime = GetCurrentTime();
317 CHECK_NULL_VOID(AcePerformanceCheck::performanceInfo_);
318 auto ruleJson = AcePerformanceCheck::performanceInfo_->GetValue("9905");
319 auto pageJson = JsonUtil::Create(true);
320 pageJson->Put("eventTime", eventTime.c_str());
321 pageJson->Put("pagePath", codeInfo.sources.c_str());
322 for (auto& node : flexNodeList) {
323 auto componentJson = JsonUtil::Create(true);
324 componentJson->Put("name", node.nodeTag.c_str());
325 componentJson->Put("flexTime", node.flexLayouts);
326 componentJson->Put("sourceLine", GetCodeInfo(node.codeRow, node.codeCol).row);
327 std::unique_ptr<JsonValue> componentsJson;
328 if (pageJson->Contains("components")) {
329 componentsJson = pageJson->GetValue("components");
330 componentsJson->Put(componentJson);
331 } else {
332 componentsJson = JsonUtil::CreateArray(true);
333 componentsJson->Put(componentJson);
334 pageJson->Put("components", componentsJson);
335 }
336 }
337 ruleJson->Put(pageJson);
338 }
339
GetCurrentSourceMap()340 RefPtr<Framework::RevSourceMap> AceScopedPerformanceCheck::GetCurrentSourceMap()
341 {
342 std::string jsSourceMap;
343 auto sourceMap = AceType::MakeRefPtr<Framework::RevSourceMap>();
344 auto container = Container::Current();
345 CHECK_NULL_RETURN(container, nullptr);
346 auto pos = currentPath_.find("pages");
347 if (pos != std::string::npos) {
348 currentPath_ = currentPath_.substr(pos, currentPath_.size());
349 }
350 pos = currentPath_.find(".js");
351 if (pos != std::string::npos) {
352 currentPath_ = currentPath_.substr(0, pos);
353 }
354 if (container->IsUseStageModel()) {
355 auto pagePath = currentPath_;
356 auto moduleName = container->GetModuleName();
357 std::string judgePath = "";
358 if (Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_TWELVE)) {
359 judgePath = DEBUG_PATH + moduleName + ETS_PATH + pagePath + TS_SUFFIX;
360 } else {
361 judgePath = moduleName + ETS_PATH + pagePath + ETS_SUFFIX;
362 }
363 if (Framework::GetAssetContentImpl(container->GetAssetManager(), "sourceMaps.map", jsSourceMap)) {
364 auto jsonPages = JsonUtil::ParseJsonString(jsSourceMap);
365 auto child = jsonPages->GetChild();
366 if (!child->GetValue("entry-package-info")->IsNull()) {
367 judgePath = NEW_PATH + pagePath + TS_SUFFIX;
368 }
369 auto jsonPage = jsonPages->GetValue(judgePath)->ToString();
370 sourceMap = AceType::MakeRefPtr<Framework::RevSourceMap>();
371 sourceMap->Init(jsonPage);
372 return sourceMap;
373 }
374 } else {
375 if (Framework::GetAssetContentImpl(container->GetAssetManager(), currentPath_ + ".map", jsSourceMap)) {
376 auto faPageMap = AceType::MakeRefPtr<Framework::RevSourceMap>();
377 faPageMap->Init(jsSourceMap);
378 return faPageMap;
379 }
380 }
381 return nullptr;
382 }
383 } // namespace OHOS::Ace