• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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