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(true));
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 return {};
122 }
123
124 // this is for i18n date time
125 DateTime dateTime;
126 dateTime.year = static_cast<uint32_t>(local->tm_year + BASE_YEAR);
127 dateTime.month = static_cast<uint32_t>(local->tm_mon);
128 dateTime.day = static_cast<uint32_t>(local->tm_mday);
129 dateTime.hour = static_cast<uint32_t>(local->tm_hour);
130 dateTime.minute = static_cast<uint32_t>(local->tm_min);
131 dateTime.second = static_cast<uint32_t>(local->tm_sec);
132 auto time = Localization::GetInstance()->FormatDateTime(dateTime, DATE_FORMAT);
133 return time;
134 }
135
GetCodeInfo(int32_t row,int32_t col)136 CodeInfo AceScopedPerformanceCheck::GetCodeInfo(int32_t row, int32_t col)
137 {
138 auto container = Container::Current();
139 CHECK_NULL_RETURN(container, {});
140 auto frontend = container->GetFrontend();
141 CHECK_NULL_RETURN(frontend, {});
142 auto sourceMap = frontend->GetCurrentPageSourceMap();
143 CHECK_NULL_RETURN(sourceMap, {});
144 // There is no same row and column info of viewPU in sourcemap, but the row info is correct.
145 auto codeInfo = sourceMap->Find(row, col, false);
146 return { codeInfo.row, codeInfo.col, codeInfo.sources };
147 }
148
CheckPage(const CodeInfo & codeInfo,const std::string & rule)149 bool AceScopedPerformanceCheck::CheckPage(const CodeInfo& codeInfo, const std::string& rule)
150 {
151 if (!codeInfo.sources.empty() && CheckIsRuleContainsPage(rule, codeInfo.sources)) {
152 return true;
153 }
154 return false;
155 }
156
RecordPerformanceCheckData(const PerformanceCheckNodeMap & nodeMap,int64_t vsyncTimeout)157 void AceScopedPerformanceCheck::RecordPerformanceCheckData(const PerformanceCheckNodeMap& nodeMap, int64_t vsyncTimeout)
158 {
159 auto codeInfo = GetCodeInfo(1, 1);
160 std::vector<PerformanceCheckNode> pageNodeList;
161 std::vector<PerformanceCheckNode> flexNodeList;
162 std::unordered_map<int32_t, PerformanceCheckNode> foreachNodeMap;
163 int32_t itemCount = 0;
164 int32_t maxDepth = 0;
165 for (const auto& node : nodeMap) {
166 if (node.second.childrenSize >= AceChecker::GetNodeChildren()) {
167 pageNodeList.emplace_back(node.second);
168 }
169 if (node.second.pageDepth > maxDepth) {
170 maxDepth = node.second.pageDepth;
171 }
172 if (node.second.flexLayouts != 0 && node.second.flexLayouts >= AceChecker::GetFlexLayouts()) {
173 flexNodeList.emplace_back(node.second);
174 }
175 if (node.second.isForEachItem) {
176 itemCount++;
177 auto iter = foreachNodeMap.find(node.second.codeRow);
178 if (iter != foreachNodeMap.end()) {
179 iter->second.foreachItems++;
180 } else {
181 foreachNodeMap.insert(std::make_pair(node.second.codeRow, node.second));
182 }
183 }
184 }
185 RecordPageNodeCountAndDepth(nodeMap.size(), maxDepth, pageNodeList, codeInfo);
186 RecordForEachItemsCount(itemCount, foreachNodeMap, codeInfo);
187 RecordFlexLayoutsCount(flexNodeList, codeInfo);
188 RecordVsyncTimeout(nodeMap, vsyncTimeout / CONVERT_NANOSECONDS, codeInfo);
189 }
190
RecordPageNodeCountAndDepth(int32_t pageNodeCount,int32_t pageDepth,std::vector<PerformanceCheckNode> & pageNodeList,const CodeInfo & codeInfo)191 void AceScopedPerformanceCheck::RecordPageNodeCountAndDepth(
192 int32_t pageNodeCount, int32_t pageDepth, std::vector<PerformanceCheckNode>& pageNodeList, const CodeInfo& codeInfo)
193 {
194 if ((pageNodeCount < AceChecker::GetPageNodes() && pageDepth < AceChecker::GetPageDepth()) ||
195 CheckPage(codeInfo, "9901")) {
196 return;
197 }
198 auto eventTime = GetCurrentTime();
199 CHECK_NULL_VOID(AcePerformanceCheck::performanceInfo_);
200 auto ruleJson = AcePerformanceCheck::performanceInfo_->GetValue("9901");
201 auto pageJson = JsonUtil::Create(true);
202 pageJson->Put("eventTime", eventTime.c_str());
203 pageJson->Put("pagePath", codeInfo.sources.c_str());
204 pageJson->Put("nodeCount", pageNodeCount);
205 pageJson->Put("depth", pageDepth);
206 // add children size > 100 of component to pageJson
207 for (const auto& iter : pageNodeList) {
208 auto componentJson = JsonUtil::Create(true);
209 componentJson->Put("name", iter.nodeTag.c_str());
210 componentJson->Put("items", iter.childrenSize);
211 componentJson->Put("sourceLine", GetCodeInfo(iter.codeRow, iter.codeCol).row);
212 std::unique_ptr<JsonValue> componentsJson;
213 if (pageJson->Contains("components")) {
214 componentsJson = pageJson->GetValue("components");
215 componentsJson->Put(componentJson);
216 } else {
217 componentsJson = JsonUtil::CreateArray(true);
218 componentsJson->Put(componentJson);
219 pageJson->Put("components", componentsJson);
220 }
221 }
222 ruleJson->Put(pageJson);
223 }
224
RecordFunctionTimeout(int64_t time,const std::string & functionName)225 void AceScopedPerformanceCheck::RecordFunctionTimeout(int64_t time, const std::string& functionName)
226 {
227 if (time < AceChecker::GetFunctionTimeout()) {
228 return;
229 }
230 auto codeInfo = GetCodeInfo(1, 1);
231 if (!codeInfo.sources.empty() && CheckIsRuleContainsPage("9902", codeInfo.sources)) {
232 return;
233 }
234 auto eventTime = GetCurrentTime();
235 CHECK_NULL_VOID(AcePerformanceCheck::performanceInfo_);
236 auto ruleJson = AcePerformanceCheck::performanceInfo_->GetValue("9902");
237 auto pageJson = JsonUtil::Create(true);
238 pageJson->Put("eventTime", eventTime.c_str());
239 pageJson->Put("pagePath", codeInfo.sources.c_str());
240 pageJson->Put("functionName", functionName.c_str());
241 pageJson->Put("costTime", time);
242 ruleJson->Put(pageJson);
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 } // namespace OHOS::Ace
340