1 /* 2 * Copyright (c) 2025 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 <sstream> 17 #include <unistd.h> 18 #include <regex.h> 19 #include <cstdio> 20 #include <cstdlib> 21 #include "frontend_api_handler.h" 22 #include "perf_test.h" 23 24 namespace OHOS::perftest { 25 using namespace std; 26 using namespace nlohmann; 27 using namespace nlohmann::detail; 28 29 static mutex g_gcQueueMutex; 30 31 class PerfTestCallbackFowarder : public PerfTestCallback { 32 public: PerfTestCallbackFowarder()33 PerfTestCallbackFowarder() {}; OnCall(const string && codeRef,const int32_t timeout,ApiCallErr & error)34 void OnCall(const string&& codeRef, const int32_t timeout, ApiCallErr &error) 35 { 36 LOG_I("%{public}s called, codeRef = %{public}s, timeout = %{public}d", __func__, codeRef.c_str(), timeout); 37 if (codeRef == "") { 38 LOG_W("Callback have not been defined"); 39 return; 40 } 41 const auto &server = FrontendApiServer::Get(); 42 ApiCallInfo in; 43 ApiReplyInfo out; 44 in.apiId_ = "PerfTest.run"; 45 in.paramList_.push_back(codeRef); 46 in.paramList_.push_back(timeout); 47 server.Callback(in, out); 48 error = out.exception_; 49 } OnDestroy(const list<string> codeRefs,ApiCallErr & error)50 void OnDestroy(const list<string> codeRefs, ApiCallErr &error) 51 { 52 const auto &server = FrontendApiServer::Get(); 53 ApiCallInfo in; 54 ApiReplyInfo out; 55 in.apiId_ = "PerfTest.destroy"; 56 in.paramList_.push_back(codeRefs); 57 server.Callback(in, out); 58 } 59 }; 60 61 /** API argument type list map.*/ 62 static multimap<string, pair<vector<string>, size_t>> sApiArgTypesMap; 63 ParseMethodSignature(string_view signature,vector<string> & types,size_t & defArg)64 static void ParseMethodSignature(string_view signature, vector<string> &types, size_t &defArg) 65 { 66 int charIndex = 0; 67 constexpr size_t benLen = 32; 68 char buf[benLen]; 69 size_t tokenLen = 0; 70 size_t defArgCount = 0; 71 string token; 72 for (char ch : signature) { 73 if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '[') { 74 buf[tokenLen++] = ch; 75 } else if (ch == '?') { 76 defArgCount++; 77 } else if (ch == ',' || ch == '?' || ch == ')') { 78 if (tokenLen > 0) { 79 token = string_view(buf, tokenLen); 80 DCHECK(find(DATA_TYPE_SCOPE.begin(), DATA_TYPE_SCOPE.end(), token) != DATA_TYPE_SCOPE.end()); 81 types.emplace_back(token); 82 } 83 tokenLen = 0; // consume token and reset buffer 84 if (ch == ')') { 85 // add return value type to the end of types. 86 string retType = string(signature.substr(charIndex + 2)); 87 types.emplace_back(retType); 88 break; // end parsing 89 } 90 } 91 charIndex++; 92 } 93 defArg = defArgCount; 94 } 95 96 /** Parse frontend method definitions to collect type information.*/ ParseFrontendMethodsSignature()97 static void ParseFrontendMethodsSignature() 98 { 99 for (auto classDef : FRONTEND_CLASS_DEFS) { 100 LOG_I("parse class %{public}s", string(classDef->name_).c_str()); 101 if (classDef->methods_ == nullptr || classDef->methodCount_ <= 0) { 102 continue; 103 } 104 for (size_t idx = 0; idx < classDef->methodCount_; idx++) { 105 auto methodDef = classDef->methods_[idx]; 106 auto paramTypes = vector<string>(); 107 size_t hasDefaultArg = 0; 108 ParseMethodSignature(methodDef.signature_, paramTypes, hasDefaultArg); 109 sApiArgTypesMap.insert(make_pair(string(methodDef.name_), make_pair(paramTypes, hasDefaultArg))); 110 } 111 } 112 } 113 Get()114 FrontendApiServer &FrontendApiServer::Get() 115 { 116 static FrontendApiServer server; 117 return server; 118 } 119 AddHandler(string_view apiId,ApiInvokeHandler handler)120 void FrontendApiServer::AddHandler(string_view apiId, ApiInvokeHandler handler) 121 { 122 if (handler == nullptr) { 123 return; 124 } 125 handlers_.insert(make_pair(apiId, handler)); 126 } 127 SetCallbackHandler(ApiInvokeHandler handler)128 void FrontendApiServer::SetCallbackHandler(ApiInvokeHandler handler) 129 { 130 callbackHandler_ = handler; 131 } 132 Callback(const ApiCallInfo & in,ApiReplyInfo & out) const133 void FrontendApiServer::Callback(const ApiCallInfo& in, ApiReplyInfo& out) const 134 { 135 if (callbackHandler_ == nullptr) { 136 out.exception_ = ApiCallErr(ERR_INTERNAL, "No callback handler set!"); 137 return; 138 } 139 LOG_I("%{public}s called", __func__); 140 callbackHandler_(in, out); 141 } 142 HasHandlerFor(std::string_view apiId) const143 bool FrontendApiServer::HasHandlerFor(std::string_view apiId) const 144 { 145 return handlers_.find(string(apiId)) != handlers_.end(); 146 } 147 RemoveHandler(string_view apiId)148 void FrontendApiServer::RemoveHandler(string_view apiId) 149 { 150 handlers_.erase(string(apiId)); 151 } 152 AddCommonPreprocessor(string_view name,ApiInvokeHandler processor)153 void FrontendApiServer::AddCommonPreprocessor(string_view name, ApiInvokeHandler processor) 154 { 155 if (processor == nullptr) { 156 return; 157 } 158 commonPreprocessors_.insert(make_pair(name, processor)); 159 } 160 RemoveCommonPreprocessor(string_view name)161 void FrontendApiServer::RemoveCommonPreprocessor(string_view name) 162 { 163 commonPreprocessors_.erase(string(name)); 164 } 165 Call(const ApiCallInfo & in,ApiReplyInfo & out) const166 void FrontendApiServer::Call(const ApiCallInfo &in, ApiReplyInfo &out) const 167 { 168 LOG_I("Begin to invoke api '%{public}s', '%{public}s'", in.apiId_.data(), in.paramList_.dump().data()); 169 auto call = in; 170 // initialize method signature 171 if (sApiArgTypesMap.empty()) { 172 ParseFrontendMethodsSignature(); 173 } 174 auto find = handlers_.find(call.apiId_); 175 if (find == handlers_.end()) { 176 out.exception_ = ApiCallErr(ERR_INTERNAL, "No handler found for api '" + call.apiId_ + "'"); 177 return; 178 } 179 try { 180 for (auto &[name, processor] : commonPreprocessors_) { 181 processor(call, out); 182 if (out.exception_.code_ != NO_ERROR) { 183 out.exception_.message_ = "(PreProcessing: " + name + ")" + out.exception_.message_; 184 return; // error during pre-processing, abort 185 } 186 } 187 } catch (std::exception &ex) { 188 out.exception_ = ApiCallErr(ERR_INTERNAL, "Preprocessor failed: " + string(ex.what())); 189 } 190 try { 191 find->second(call, out); 192 } catch (std::exception &ex) { 193 // catch possible json-parsing error 194 out.exception_ = ApiCallErr(ERR_INTERNAL, "Handler failed: " + string(ex.what())); 195 } 196 } 197 ApiTransact(const ApiCallInfo & in,ApiReplyInfo & out)198 void ApiTransact(const ApiCallInfo &in, ApiReplyInfo &out) 199 { 200 LOG_I("Begin to invoke api '%{public}s', '%{public}s'", in.apiId_.data(), in.paramList_.dump().data()); 201 FrontendApiServer::Get().Call(in, out); 202 } 203 204 /** Backend objects cache.*/ 205 static map<string, unique_ptr<BackendClass>> sBackendObjects; 206 /** PerfTest binding map.*/ 207 static map<string, string> sPerfTestBindingMap; 208 209 210 #define CHECK_CALL_ARG(condition, code, message, error) \ 211 if (!(condition)) { \ 212 error = ApiCallErr((code), (message)); \ 213 return true; \ 214 } 215 216 static bool CheckCallArgType(string_view expect, const json &value, bool isDefAgc, ApiCallErr &error); 217 CheckCallArgClassType(string_view expect,const json & value,ApiCallErr & error)218 static bool CheckCallArgClassType(string_view expect, const json &value, ApiCallErr &error) 219 { 220 auto begin = FRONTEND_CLASS_DEFS.begin(); 221 auto end = FRONTEND_CLASS_DEFS.end(); 222 auto find = find_if(begin, end, [&expect](const FrontEndClassDef *def) { return def->name_ == expect; }); 223 if (find == end) { 224 return false; 225 } 226 const auto type = value.type(); 227 CHECK_CALL_ARG(type == value_t::string, ERR_INVALID_INPUT, "Expect " + string(expect), error); 228 const auto findRef = sBackendObjects.find(value.get<string>()); 229 CHECK_CALL_ARG(findRef != sBackendObjects.end(), ERR_INVALID_INPUT, "Bad object ref", error); 230 return true; 231 } 232 CheckCallArgJsonType(string_view expect,const json & value,ApiCallErr & error)233 static bool CheckCallArgJsonType(string_view expect, const json &value, ApiCallErr &error) 234 { 235 auto begin = FRONTEND_JSON_DEFS.begin(); 236 auto end = FRONTEND_JSON_DEFS.end(); 237 auto find = find_if(begin, end, [&expect](const FrontEndJsonDef *def) { return def->name_ == expect; }); 238 if (find == end) { 239 return false; 240 } 241 const auto type = value.type(); 242 CHECK_CALL_ARG(type == value_t::object, ERR_INVALID_INPUT, "Expect " + string(expect), error); 243 auto copy = value; 244 for (size_t idx = 0; idx < (*find)->propCount_; idx++) { 245 auto def = (*find)->props_ + idx; 246 const auto propName = string(def->name_); 247 if (!value.contains(propName)) { 248 CHECK_CALL_ARG(!(def->required_), ERR_INVALID_INPUT, "Missing property " + propName, error); 249 continue; 250 } 251 copy.erase(propName); 252 // check json property value type recursive 253 CheckCallArgType(def->type_, value[propName], !def->required_, error); 254 if (error.code_ != NO_ERROR) { 255 error.message_ = "Illegal value of property '" + propName + "': " + error.message_; 256 return true; 257 } 258 } 259 CHECK_CALL_ARG(copy.empty(), ERR_INVALID_INPUT, "Illegal property of " + string(expect), error); 260 return true; 261 } 262 CheckCallArgArrayType(string_view expect,const json & value,ApiCallErr & error)263 static bool CheckCallArgArrayType(string_view expect, const json &value, ApiCallErr &error) 264 { 265 if (expect.front() != '[' || expect.back() != ']') { 266 return false; 267 } 268 const auto type = value.type(); 269 const auto isArray = type == value_t::array; 270 if (!isArray) { 271 error = ApiCallErr(ERR_INVALID_INPUT, "Expect array"); 272 return true; 273 } 274 string_view expectParaType = expect.substr(1, expect.size() - 2); 275 for (auto& para : value) { 276 CheckCallArgType(expectParaType, para, false, error); 277 if (error.code_ != NO_ERROR) { 278 return true; 279 } 280 } 281 return true; 282 } 283 284 /** Check if the json value represents and illegal data of expected type.*/ CheckCallArgType(string_view expect,const json & value,bool isDefAgc,ApiCallErr & error)285 static bool CheckCallArgType(string_view expect, const json &value, bool isDefAgc, ApiCallErr &error) 286 { 287 const auto type = value.type(); 288 if (isDefAgc && type == value_t::null) { 289 return true; 290 } 291 if (CheckCallArgClassType(expect, value, error) || CheckCallArgJsonType(expect, value, error) || 292 CheckCallArgArrayType(expect, value, error)) { 293 return true; 294 } 295 const auto isInteger = type == value_t::number_integer || type == value_t::number_unsigned; 296 if (expect == "int") { 297 CHECK_CALL_ARG(isInteger, ERR_INVALID_INPUT, "Expect integer", error); 298 if (atoi(value.dump().c_str()) < 0) { 299 error = ApiCallErr(ERR_INVALID_INPUT, "Expect integer which cannot be less than 0"); 300 return true; 301 } 302 } else if (expect == "float") { 303 CHECK_CALL_ARG(isInteger || type == value_t::number_float, ERR_INVALID_INPUT, "Expect float", error); 304 } else if (expect == "bool") { 305 CHECK_CALL_ARG(type == value_t::boolean, ERR_INVALID_INPUT, "Expect boolean", error); 306 } else if (expect == "string") { 307 CHECK_CALL_ARG(type == value_t::string, ERR_INVALID_INPUT, "Expect string", error); 308 } else { 309 CHECK_CALL_ARG(false, ERR_INVALID_INPUT, "Unknown target type " + string(expect), error); 310 } 311 return true; 312 } 313 314 /** Checks ApiCallInfo data, deliver exception and abort invocation if check fails.*/ APiCallInfoChecker(const ApiCallInfo & in,ApiReplyInfo & out)315 static void APiCallInfoChecker(const ApiCallInfo &in, ApiReplyInfo &out) 316 { 317 auto count = sApiArgTypesMap.count(in.apiId_); 318 // return nullptr by default 319 out.resultValue_ = nullptr; 320 auto find = sApiArgTypesMap.find(in.apiId_); 321 size_t index = 0; 322 while (index < count) { 323 if (find == sApiArgTypesMap.end()) { 324 return; 325 } 326 bool checkArgNum = false; 327 bool checkArgType = true; 328 out.exception_ = {NO_ERROR, "No Error"}; 329 auto &types = find->second.first; 330 auto argSupportDefault = find->second.second; 331 // check argument count.(last item of "types" is return value type) 332 auto maxArgc = types.size() - 1; 333 auto minArgc = maxArgc - argSupportDefault; 334 auto argc = in.paramList_.size(); 335 checkArgNum = argc <= maxArgc && argc >= minArgc; 336 if (!checkArgNum) { 337 out.exception_ = ApiCallErr(ERR_INVALID_INPUT, "Illegal argument count"); 338 ++find; 339 ++index; 340 continue; 341 } 342 // check argument type 343 for (size_t idx = 0; idx < argc; idx++) { 344 auto isDefArg = (idx >= minArgc) ? true : false; 345 CheckCallArgType(types.at(idx), in.paramList_.at(idx), isDefArg, out.exception_); 346 if (out.exception_.code_ != NO_ERROR) { 347 out.exception_.message_ = "Check arg" + to_string(idx) + " failed: " + out.exception_.message_; 348 checkArgType = false; 349 break; 350 } 351 } 352 if (checkArgType) { 353 return; 354 } 355 ++find; 356 ++index; 357 } 358 } 359 360 /** Store the backend object and return the reference-id.*/ StoreBackendObject(unique_ptr<BackendClass> ptr,string_view ownerRef="")361 static string StoreBackendObject(unique_ptr<BackendClass> ptr, string_view ownerRef = "") 362 { 363 static map<string, uint32_t> sObjectCounts; 364 DCHECK(ptr != nullptr); 365 const auto typeName = string(ptr->GetFrontendClassDef().name_); 366 auto find = sObjectCounts.find(typeName); 367 uint32_t index = 0; 368 if (find != sObjectCounts.end()) { 369 index = find->second; 370 } 371 auto ref = typeName + "#" + to_string(index); 372 sObjectCounts[typeName] = index + 1; 373 sBackendObjects[ref] = move(ptr); 374 if (!ownerRef.empty()) { 375 DCHECK(sBackendObjects.find(string(ownerRef)) != sBackendObjects.end()); 376 sPerfTestBindingMap[ref] = ownerRef; 377 } 378 return ref; 379 } 380 381 /** Retrieve the stored backend object by reference-id.*/ 382 template <typename T, typename = enable_if<is_base_of_v<BackendClass, T>>> GetBackendObject(string_view ref,ApiCallErr & error)383 static T *GetBackendObject(string_view ref, ApiCallErr &error) 384 { 385 auto find = sBackendObjects.find(string(ref)); 386 if (find == sBackendObjects.end() || find->second == nullptr) { 387 error = ApiCallErr(ERR_INTERNAL, "Object does not exist"); 388 return nullptr; 389 } 390 return reinterpret_cast<T *>(find->second.get()); 391 } 392 393 /** Delete stored backend objects.*/ BackendObjectsCleaner(const ApiCallInfo & in,ApiReplyInfo & out)394 static void BackendObjectsCleaner(const ApiCallInfo &in, ApiReplyInfo &out) 395 { 396 stringstream ss("Deleted objects["); 397 DCHECK(in.paramList_.type() == value_t::array); 398 for (const auto &item : in.paramList_) { 399 DCHECK(item.type() == value_t::string); // must be objRef 400 const auto ref = item.get<string>(); 401 auto findBinding = sPerfTestBindingMap.find(ref); 402 if (findBinding != sPerfTestBindingMap.end()) { 403 sPerfTestBindingMap.erase(findBinding); 404 } 405 auto findObject = sBackendObjects.find(ref); 406 if (findObject == sBackendObjects.end()) { 407 LOG_W("No such object living: %{public}s", ref.c_str()); 408 continue; 409 } 410 sBackendObjects.erase(findObject); 411 ss << ref << ","; 412 } 413 ss << "]"; 414 LOG_I("%{public}s", ss.str().c_str()); 415 } 416 ReadCallArg(const ApiCallInfo & in,size_t index)417 template <typename T> static T ReadCallArg(const ApiCallInfo &in, size_t index) 418 { 419 DCHECK(in.paramList_.type() == value_t::array); 420 DCHECK(index <= in.paramList_.size()); 421 return in.paramList_.at(index).get<T>(); 422 } 423 ReadCallArg(const ApiCallInfo & in,size_t index,T defValue)424 template <typename T> static T ReadCallArg(const ApiCallInfo &in, size_t index, T defValue) 425 { 426 DCHECK(in.paramList_.type() == value_t::array); 427 if (index >= in.paramList_.size()) { 428 return defValue; 429 } 430 auto type = in.paramList_.at(index).type(); 431 if (type == value_t::null) { 432 return defValue; 433 } else { 434 return in.paramList_.at(index).get<T>(); 435 } 436 } 437 RegisterPerfTestCreate()438 static void RegisterPerfTestCreate() 439 { 440 auto &server = FrontendApiServer::Get(); 441 auto create = [](const ApiCallInfo &in, ApiReplyInfo &out) { 442 auto perfTestStrategyJson = ReadCallArg<json>(in, INDEX_ZERO); 443 if (perfTestStrategyJson.empty()) { 444 out.exception_ = ApiCallErr(ERR_INVALID_INPUT, "PerfTestStrategy cannot be empty"); 445 return; 446 } 447 auto metricsArray = perfTestStrategyJson["metrics"]; 448 if (!metricsArray.is_array() || metricsArray.empty()) { 449 out.exception_ = ApiCallErr(ERR_INVALID_INPUT, "Metrics cannot be empty"); 450 return; 451 } 452 set<PerfMetric> metrics; 453 for (auto& metricNum : metricsArray) { 454 auto metric = metricNum.get<PerfMetric>(); 455 if (metric < ZERO || metric >= PerfMetric::METRIC_COUNT) { 456 out.exception_ = ApiCallErr(ERR_INVALID_INPUT, "Illegal perfMetric"); 457 return; 458 } 459 metrics.insert(metric); 460 } 461 auto actionCodeRef = perfTestStrategyJson["actionCode"]; 462 auto resetCodeRef = ReadArgFromJson<string>(perfTestStrategyJson, "resetCode", ""); 463 auto bundleName = ReadArgFromJson<string>(perfTestStrategyJson, "bundleName", ""); 464 auto iterations = ReadArgFromJson<int32_t>(perfTestStrategyJson, "iterations", TEST_ITERATIONS); 465 auto timeout = ReadArgFromJson<int32_t>(perfTestStrategyJson, "timeout", EXECUTION_TIMEOUT); 466 auto perfTestStrategy = make_unique<PerfTestStrategy>(metrics, actionCodeRef, resetCodeRef, bundleName, 467 iterations, timeout, out.exception_); 468 auto perfTestCallback = make_unique<PerfTestCallbackFowarder>(); 469 if (out.exception_.code_ != NO_ERROR) { 470 return; 471 } 472 auto perfTest = make_unique<PerfTest>(move(perfTestStrategy), move(perfTestCallback)); 473 out.resultValue_ = StoreBackendObject(move(perfTest)); 474 }; 475 server.AddHandler("PerfTest.create", create); 476 } 477 RegisterPerfTestRun()478 static void RegisterPerfTestRun() 479 { 480 auto &server = FrontendApiServer::Get(); 481 auto run = [](const ApiCallInfo &in, ApiReplyInfo &out) { 482 auto perfTest = GetBackendObject<PerfTest>(in.callerObjRef_, out.exception_); 483 if (out.exception_.code_ != NO_ERROR) { 484 return; 485 } 486 perfTest->RunTest(out.exception_); 487 }; 488 server.AddHandler("PerfTest.run", run); 489 } 490 RegisterGetMeasureResult()491 static void RegisterGetMeasureResult() 492 { 493 auto &server = FrontendApiServer::Get(); 494 auto getMeasureResult = [](const ApiCallInfo &in, ApiReplyInfo &out) { 495 auto perfTest = GetBackendObject<PerfTest>(in.callerObjRef_, out.exception_); 496 if (out.exception_.code_ != NO_ERROR) { 497 return; 498 } 499 auto metric = ReadCallArg<PerfMetric>(in, INDEX_ZERO); 500 if (metric < ZERO || metric >= PerfMetric::METRIC_COUNT) { 501 out.exception_ = ApiCallErr(ERR_INVALID_INPUT, "Illegal perfMetric"); 502 return; 503 } 504 if (perfTest->IsMeasureRunning()) { 505 out.exception_ = ApiCallErr(ERR_GET_RESULT_FAILED, 506 "Measure is running, can not get measure result now"); 507 return; 508 } 509 json resData = perfTest->GetMeasureResult(metric, out.exception_); 510 out.resultValue_ = resData; 511 }; 512 server.AddHandler("PerfTest.getMeasureResult", getMeasureResult); 513 } 514 RegisterPerfTestDestroy()515 static void RegisterPerfTestDestroy() 516 { 517 auto &server = FrontendApiServer::Get(); 518 auto destroy = [](const ApiCallInfo &in, ApiReplyInfo &out) { 519 auto perfTest = GetBackendObject<PerfTest>(in.callerObjRef_, out.exception_); 520 if (out.exception_.code_ != NO_ERROR) { 521 return; 522 } 523 if (perfTest->IsMeasureRunning()) { 524 out.exception_ = ApiCallErr(ERR_INTERNAL, "Measure is running, can not destroy now"); 525 return; 526 } 527 perfTest->Destroy(out.exception_); 528 auto gcCall = ApiCallInfo {.apiId_ = "BackendObjectsCleaner"}; 529 unique_lock<mutex> lock(g_gcQueueMutex); 530 gcCall.paramList_.emplace_back(in.callerObjRef_); 531 lock.unlock(); 532 auto gcReply = ApiReplyInfo(); 533 BackendObjectsCleaner(gcCall, gcReply); 534 }; 535 server.AddHandler("PerfTest.destroy", destroy); 536 } 537 538 /** Register frontendApiHandlers and preprocessors on startup.*/ RegisterApiHandlers()539 __attribute__((constructor)) static void RegisterApiHandlers() 540 { 541 auto &server = FrontendApiServer::Get(); 542 server.AddCommonPreprocessor("APiCallInfoChecker", APiCallInfoChecker); 543 server.AddHandler("BackendObjectsCleaner", BackendObjectsCleaner); 544 RegisterPerfTestCreate(); 545 RegisterPerfTestRun(); 546 RegisterGetMeasureResult(); 547 RegisterPerfTestDestroy(); 548 } 549 } // namespace OHOS::perftest 550