1 /*
2 * Copyright (c) 2021 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 "perf.h"
17
18 #include <cstring>
19 #include <sstream>
20 #include <cstdio>
21 #include <cstdlib>
22
23 #include <libxml/parser.h>
24 using namespace std;
25
26 namespace OHOS {
27 namespace TestAW {
28
29 #define ERR_MSG(...) fprintf(stderr, __VA_ARGS__)
30 #define INF_MSG(...) fprintf(stdout, __VA_ARGS__)
31 #define DBG_MSG(...) fprintf(stdout, __VA_ARGS__)
32
33 #define ID_LARGER_IS_BETTER true
34 #define ID_SMALLER_IS_BETTER false
35
36 namespace {
37 const auto XML_TAG_ROOT = "configuration";
38 const auto XML_TAG_DATETIME = "datetime";
39 const auto XML_TAG_URL = "url";
40 const auto XML_TAG_CASENAME = "name";
41 const int ID_PROPERTY_LENGTH = 64;
42 }
43
44 // BaseLineManager
BaseLineManager()45 BaseLineManager::BaseLineManager()
46 : m_bNoBaseline(false)
47 {
48 }
49
BaseLineManager(const string path)50 BaseLineManager::BaseLineManager(const string path)
51 : m_bNoBaseline(false)
52 {
53 LoadConfig(path);
54 }
55
~BaseLineManager()56 BaseLineManager::~BaseLineManager()
57 {
58 if (m_bNoBaseline) {
59 ERR_MSG("[ WARNING ] no baseline loaded, please manual check output value is OK.\n");
60 }
61 }
62
63 // parser configuration file in json format.
LoadConfig(const string path)64 bool BaseLineManager::LoadConfig(const string path)
65 {
66 m_bNoBaseline = false;
67 if (!ReadXmlFile(path)) {
68 ERR_MSG("[ WARNING ] failed to load config from %s\n", path.c_str());
69 m_bNoBaseline = true;
70 return false;
71 }
72
73 INF_MSG("[ PERF ] Load BaseLines from: %s\n", path.c_str());
74 return true;
75 }
76
ParseProperties(const xmlNode currNode,map<string,string> & properties)77 void ParseProperties(const xmlNode currNode, map<string, string>& properties)
78 {
79 for (auto attrs = currNode.properties; attrs != nullptr; attrs = attrs->next) {
80 auto name = attrs->name;
81 if (name == nullptr) {
82 continue;
83 }
84 auto value = xmlGetProp(&currNode, name);
85 if (value == nullptr) {
86 continue;
87 }
88 string propName(reinterpret_cast<const char*>(name));
89 string propValue(reinterpret_cast<char*>(value));
90 properties[propName] = std::move(propValue);
91 xmlFree(value);
92 }
93 }
94
ReadXmlFile(string baselinePath)95 bool BaseLineManager::ReadXmlFile(string baselinePath)
96 {
97 xmlDocPtr ptrXmlDoc = xmlReadFile(baselinePath.c_str(), nullptr, XML_PARSE_NOBLANKS);
98 if (ptrXmlDoc == nullptr) {
99 return false;
100 }
101
102 xmlNodePtr ptrRootNode = xmlDocGetRootElement(ptrXmlDoc);
103 if (ptrRootNode == nullptr || ptrRootNode->name == nullptr ||
104 xmlStrcmp(ptrRootNode->name, reinterpret_cast<const xmlChar*>(XML_TAG_ROOT))) {
105 xmlFreeDoc(ptrXmlDoc);
106 return false;
107 }
108
109 map<string, string> properties;
110 ParseProperties(*ptrRootNode, properties);
111 if (properties.count(XML_TAG_DATETIME) == 1) {
112 m_bastCfg.date = properties[XML_TAG_DATETIME];
113 }
114 if (properties.count(XML_TAG_URL) == 1) {
115 m_bastCfg.date = properties[XML_TAG_URL];
116 }
117
118 xmlNodePtr currNodePtr = ptrRootNode->xmlChildrenNode;
119 for (; currNodePtr != nullptr; currNodePtr = currNodePtr->next) {
120 if (currNodePtr->name == nullptr || currNodePtr->type == XML_COMMENT_NODE) {
121 xmlFreeDoc(ptrXmlDoc);
122 return false;
123 }
124
125 map<string, string> properties_temp;
126 ParseProperties(*currNodePtr, properties_temp);
127 m_bastCfg.items.push_back(properties_temp);
128 }
129
130 xmlFreeDoc(ptrXmlDoc);
131 return true;
132 }
133
IsNoBaseline()134 bool BaseLineManager::IsNoBaseline()
135 {
136 return m_bNoBaseline;
137 }
138
StrtoDouble(const string & str)139 double BaseLineManager::StrtoDouble(const string& str)
140 {
141 istringstream iss(str);
142 double num;
143 iss >> num;
144 return num;
145 }
146
GetExtraValueDouble(const string testcaseName,const string extra,double & value)147 bool BaseLineManager::GetExtraValueDouble(const string testcaseName, const string extra, double &value)
148 {
149 if (testcaseName == "" || extra == "") {
150 DBG_MSG("[ ERROR ] invalid arguments: testcaseName=%s, extra=%s\n", testcaseName.c_str(), extra.c_str());
151 return false;
152 }
153
154 for (auto iter = m_bastCfg.items.begin(); iter != m_bastCfg.items.end(); iter++) {
155 map<string, string> properties = *iter;
156 if (properties.count(XML_TAG_CASENAME) == 1 && properties[XML_TAG_CASENAME] == testcaseName) {
157 if (properties.count(extra) == 1) {
158 value = StrtoDouble(properties[extra]);
159 }
160 break;
161 }
162 }
163
164 return true;
165 }
166
167 // GtestPerfTestCase
GtestPerfTestCase(BaseLineManager * pManager,testing::Test * tester,int caseVersion,std::string testClassName,std::string testInterfaceName)168 GtestPerfTestCase::GtestPerfTestCase(BaseLineManager* pManager,
169 testing::Test *tester,
170 int caseVersion,
171 std::string testClassName,
172 std::string testInterfaceName)
173 {
174 m_pManager = pManager;
175 m_pTester = tester;
176 m_dCaseVersion = caseVersion;
177 m_strCaseName = "";
178 m_strTestClassName = testClassName;
179 m_strTestInterfaceName = testInterfaceName;
180
181 // get test case name from GTEST API.
182 // should be use tester->XXX() instead of this.
183 if (tester != nullptr && ::testing::UnitTest::GetInstance() != nullptr) {
184 m_strCaseName = string(::testing::UnitTest::GetInstance()->current_test_info()->name());
185 }
186
187 // start initialize.
188 Initialize();
189 }
190
SetBaseLine(string testcaseName)191 bool GtestPerfTestCase::SetBaseLine(string testcaseName)
192 {
193 if (testcaseName == "") {
194 return false;
195 }
196
197 m_strCaseName = testcaseName;
198
199 return Initialize();
200 }
201
ResetValues()202 void GtestPerfTestCase::ResetValues()
203 {
204 m_bHasBaseLine = false;
205 m_dbBaseLine = -1.0;
206 m_bHasLastValue = false;
207 m_dbLastValue = -1.0;
208 m_bHasFloatRange = false;
209 m_dbFloatRange = -1.0;
210
211 m_bTestResult = false;
212 m_dbTestResult = -1.0;
213 }
214
Initialize()215 bool GtestPerfTestCase::Initialize()
216 {
217 // clear all values.
218 ResetValues();
219 if (m_strCaseName == "" || m_pManager == nullptr) {
220 return false;
221 }
222
223 // get baseline value
224 m_bHasBaseLine = m_pManager->GetExtraValueDouble(m_strCaseName, "baseline", m_dbBaseLine);
225 if (!m_bHasBaseLine) {
226 return false;
227 }
228
229 // get last test value from config.
230 m_bHasLastValue = m_pManager->GetExtraValueDouble(m_strCaseName, "lastvalue", m_dbLastValue);
231
232 // get float range value from config.
233 m_bHasFloatRange = m_pManager->GetExtraValueDouble(m_strCaseName, "floatrange", m_dbFloatRange);
234 // check values is valid, and update them.
235 if (m_bHasFloatRange && (m_dbFloatRange < 0 || m_dbFloatRange >= 1)) {
236 DBG_MSG("[ ERROR ] %s has invalid float range: %f.\n", m_strCaseName.c_str(), m_dbFloatRange);
237 m_bHasFloatRange = false;
238 }
239
240 if (!m_bHasFloatRange) {
241 m_dbFloatRange = 0.0;
242 }
243
244 if (!m_bHasLastValue) {
245 m_dbLastValue = m_dbBaseLine;
246 }
247
248 return true;
249 }
250
251 // return true if testValue >= baseline value
ExpectLarger(double testValue)252 bool GtestPerfTestCase::ExpectLarger(double testValue)
253 {
254 return ExpectValue(testValue, ID_LARGER_IS_BETTER);
255 }
256
257 // return true if testValue <= baseline value
ExpectSmaller(double testValue)258 bool GtestPerfTestCase::ExpectSmaller(double testValue)
259 {
260 return ExpectValue(testValue, ID_SMALLER_IS_BETTER);
261 }
262
ExpectValue(double testValue,bool isLargerBetter)263 bool GtestPerfTestCase::ExpectValue(double testValue, bool isLargerBetter)
264 {
265 if (m_strCaseName == "") {
266 ERR_MSG("[ ERROR ] failed to get testcase name.\n");
267 return false;
268 }
269
270 m_bTestResult = false;
271 m_dbTestResult = testValue;
272
273 // check pass or failed.
274 if (m_pManager != nullptr && m_pManager->IsNoBaseline()) {
275 // no baseline.json is loaded at startup.
276 // set result to TRUE, please check testValue manually.
277 m_bTestResult = true;
278 EXPECT_TRUE(m_bTestResult);
279 } else if (!m_bHasBaseLine) {
280 ERR_MSG("[ ERROR ] %s has NO baseline.\n", m_strCaseName.c_str());
281 EXPECT_TRUE(m_bHasBaseLine);
282 } else {
283 double baseValue = -1;
284 if (isLargerBetter) {
285 baseValue = (m_dbLastValue >= m_dbBaseLine) ? m_dbLastValue : m_dbBaseLine;
286 EXPECT_GE(testValue, (baseValue * (1.0 - m_dbFloatRange)));
287 m_bTestResult = (testValue >= (baseValue * (1.0 - m_dbFloatRange))) ? true : false;
288 } else {
289 baseValue = (m_dbLastValue <= m_dbBaseLine) ? m_dbLastValue : m_dbBaseLine;
290 EXPECT_LE(testValue, (baseValue * (1.0 + m_dbFloatRange)));
291 m_bTestResult = (testValue <= (baseValue * (1.0 + m_dbFloatRange))) ? true : false;
292 }
293 }
294
295 // save result.
296 SaveResult(testValue);
297
298 return m_bTestResult;
299 }
300
SaveResult(double testValue)301 bool GtestPerfTestCase::SaveResult(double testValue)
302 {
303 char buffer[ID_PROPERTY_LENGTH] = {0};
304
305 if (m_pTester == nullptr) {
306 ERR_MSG("[ ERROR ] m_pTester is nullptr.\n");
307 return false;
308 }
309
310 INF_MSG("[ PERF ] %s: baseline:%f, test_result: %f\n", m_strCaseName.c_str(), m_dbBaseLine, testValue);
311
312 (void)memset_s(buffer, sizeof(buffer), 0, sizeof(buffer));
313 if (snprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, "%g", m_dbBaseLine) > 0) {
314 m_pTester->RecordProperty("baseline", buffer);
315 }
316
317 (void)memset_s(buffer, sizeof(buffer), 0, sizeof(buffer));
318 if (snprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, "%d", m_dCaseVersion) > 0) {
319 m_pTester->RecordProperty("tc_version", buffer);
320 }
321
322 (void)memset_s(buffer, sizeof(buffer), 0, sizeof(buffer));
323 if (snprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, "%g", m_dbLastValue) > 0) {
324 m_pTester->RecordProperty("lastvalue", m_bHasLastValue ? buffer : "");
325 }
326
327 (void)memset_s(buffer, sizeof(buffer), 0, sizeof(buffer));
328 if (snprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, "%g", testValue) > 0) {
329 m_pTester->RecordProperty("value", buffer);
330 }
331
332 m_pTester->RecordProperty("category", "performance");
333 m_pTester->RecordProperty("test_class", m_strTestClassName.c_str());
334 m_pTester->RecordProperty("test_interface", m_strTestInterfaceName.c_str());
335
336 return true;
337 }
338 } // namespace TestAW
339 } // namespace OHOS
340