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