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