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 <cstdio> 17#include <fstream> 18#include <cstdlib> 19#include <ctime> 20 21#include "ecmascript/accessor_data.h" 22#include "ecmascript/ecma_vm.h" 23#include "ecmascript/global_dictionary-inl.h" 24#include "ecmascript/global_env.h" 25#include "ecmascript/dfx/hprof/heap_profiler.h" 26#include "ecmascript/dfx/hprof/heap_profiler_interface.h" 27#include "ecmascript/dfx/hprof/heap_snapshot.h" 28#include "ecmascript/dfx/hprof/heap_snapshot_json_serializer.h" 29#include "ecmascript/dfx/hprof/string_hashmap.h" 30#include "ecmascript/ic/ic_handler.h" 31#include "ecmascript/ic/property_box.h" 32#include "ecmascript/ic/proto_change_details.h" 33#include "ecmascript/jobs/micro_job_queue.h" 34#include "ecmascript/jobs/pending_job.h" 35#include "ecmascript/jspandafile/program_object.h" 36#include "ecmascript/js_arguments.h" 37#include "ecmascript/js_array.h" 38#include "ecmascript/js_array_iterator.h" 39#include "ecmascript/js_arraybuffer.h" 40#include "ecmascript/js_async_function.h" 41#include "ecmascript/js_collator.h" 42#include "ecmascript/js_dataview.h" 43#include "ecmascript/js_date.h" 44#include "ecmascript/js_date_time_format.h" 45#include "ecmascript/js_for_in_iterator.h" 46#include "ecmascript/js_function.h" 47#include "ecmascript/js_generator_object.h" 48#include "ecmascript/js_global_object.h" 49#include "ecmascript/js_handle.h" 50#include "ecmascript/js_intl.h" 51#include "ecmascript/js_locale.h" 52#include "ecmascript/js_map.h" 53#include "ecmascript/js_map_iterator.h" 54#include "ecmascript/js_number_format.h" 55#include "ecmascript/js_object-inl.h" 56#include "ecmascript/js_plural_rules.h" 57#include "ecmascript/js_primitive_ref.h" 58#include "ecmascript/js_promise.h" 59#include "ecmascript/js_realm.h" 60#include "ecmascript/js_regexp.h" 61#include "ecmascript/js_relative_time_format.h" 62#include "ecmascript/js_set.h" 63#include "ecmascript/js_set_iterator.h" 64#include "ecmascript/js_string_iterator.h" 65#include "ecmascript/js_tagged_number.h" 66#include "ecmascript/js_tagged_value-inl.h" 67#include "ecmascript/js_thread.h" 68#include "ecmascript/js_typed_array.h" 69#include "ecmascript/js_weak_container.h" 70#include "ecmascript/layout_info-inl.h" 71#include "ecmascript/lexical_env.h" 72#include "ecmascript/linked_hash_table.h" 73#include "ecmascript/mem/assert_scope.h" 74#include "ecmascript/mem/c_containers.h" 75#include "ecmascript/mem/machine_code.h" 76#include "ecmascript/object_factory.h" 77#include "ecmascript/tagged_array.h" 78#include "ecmascript/tagged_dictionary.h" 79#include "ecmascript/template_map.h" 80#include "ecmascript/tests/test_helper.h" 81#include "ecmascript/transitions_dictionary.h" 82 83using namespace panda::ecmascript; 84using namespace panda::ecmascript::base; 85 86namespace panda::test { 87using MicroJobQueue = panda::ecmascript::job::MicroJobQueue; 88using PendingJob = panda::ecmascript::job::PendingJob; 89class HProfTest : public testing::Test { 90public: 91 static void SetUpTestCase() 92 { 93 GTEST_LOG_(INFO) << "SetUpTestCase"; 94 } 95 96 static void TearDownTestCase() 97 { 98 GTEST_LOG_(INFO) << "TearDownCase"; 99 } 100 101 void SetUp() override 102 { 103 TestHelper::CreateEcmaVMWithScope(instance, thread, scope); 104 } 105 106 void TearDown() override 107 { 108 TestHelper::DestroyEcmaVMWithScope(instance, scope); 109 } 110 EcmaVM *instance {nullptr}; 111 EcmaHandleScope *scope {nullptr}; 112 JSThread *thread {nullptr}; 113}; 114 115class HProfTestHelper { 116public: 117 explicit HProfTestHelper(EcmaVM *vm) : instance(vm) {} 118 119 ~HProfTestHelper() 120 { 121 HeapProfilerInterface::Destroy(instance); 122 } 123 124 size_t GenerateSnapShot(const std::string &filePath) 125 { 126 // first generate this file of filePath if not exist, 127 // so the function `realpath` of FileStream can not failed on arm/arm64. 128 fstream outputString(filePath, std::ios::out); 129 outputString.close(); 130 outputString.clear(); 131 FileStream stream(filePath.c_str()); 132 HeapProfilerInterface *heapProfile = HeapProfilerInterface::GetInstance(instance); 133 DumpSnapShotOption dumpOption; 134 dumpOption.dumpFormat = DumpFormat::JSON; 135 heapProfile->DumpHeapSnapshot(&stream, dumpOption); 136 return heapProfile->GetIdCount(); 137 } 138 139 bool ContrastJSONLineHeader(const std::string &filePath, std::string lineHeader) 140 { 141 std::string line; 142 std::ifstream inputStream(filePath); 143 while (getline(inputStream, line)) { 144 if (line.find(lineHeader) != line.npos) { 145 return true; 146 } 147 } 148 return false; 149 } 150 151 bool ContrastJSONSectionPayload(const std::string &filePath, std::string dataLable, int fieldNum) 152 { 153 std::string line; 154 int i = 1; 155 std::ifstream inputStream(filePath); 156 while (getline(inputStream, line)) { 157 if (i > 10 && line.find(dataLable) != line.npos) { // 10 : Hit the line 158 std::string::size_type pos = 0; 159 int loop = 0; 160 while ((pos = line.find(",", pos)) != line.npos) { 161 pos++; 162 loop++; // "," count 163 } 164 return loop == fieldNum - 1; 165 } 166 i++; // Search the Next Line 167 } 168 return false; // Lost the Line 169 } 170 171 bool ContrastJSONPayloadCntAndHitStrField(const std::string &filePath, std::string dataLable, int fieldCnt, 172 std::string fieldStr) 173 { 174 std::string line; 175 int i = 1; 176 std::ifstream inputStream(filePath); 177 while (getline(inputStream, line)) { 178 if (line.find(dataLable) != line.npos) { // 3 : Hit the line 179 std::string::size_type pos = 0; 180 int loop = 0; 181 while ((pos = line.find(",", pos)) != line.npos) { 182 pos++; 183 loop++; // "," count 184 } 185 if (loop != fieldCnt - 1) { 186 return false; 187 } 188 return line.find(fieldStr) != line.npos; // check if hit the target field 189 } 190 i++; // Search the Next Line 191 } 192 return false; // Lost the Line 193 } 194 195 bool ContrastJSONNativeSizeNum(const std::string &filePath, std::string nodesLable, int nNum) 196 { 197 std::string line; 198 bool hit = false; 199 std::vector<std::string> nodeNativeSizes; 200 std::ifstream inputStream(filePath); 201 while (getline(inputStream, line)) { 202 if (!hit && line.find(nodesLable) == line.npos) { 203 continue; // Not Get 204 } 205 if (line.find("[]") != line.npos) { // Empty 206 break; 207 } 208 if (!hit) { 209 hit = true; 210 } 211 if (hit && (line.find("],") != line.npos)) { 212 break; // Reach End 213 } 214 int rCommaInd = line.rfind(','); // find last one 215 if (rCommaInd == line.npos) { 216 return false; 217 } 218 std::string nativeSizeStr = line.substr(rCommaInd + 1, line.size() - rCommaInd); 219 if (nativeSizeStr.compare("0") != 0) { 220 nodeNativeSizes.push_back(nativeSizeStr); 221 } 222 } 223 const Heap* heap = instance->GetHeap(); 224 if (heap->nativePointerList_.size() != nodeNativeSizes.size() || nodeNativeSizes.size() != nNum) { 225 return false; 226 } 227 int ind = 0; 228 for (JSNativePointer* jsNp : heap->nativePointerList_) { 229 if (nodeNativeSizes[ind].compare(std::to_string(jsNp->GetBindingSize())) != 0) { 230 return false; 231 } 232 ind++; 233 } 234 return true; 235 } 236 237 bool ContrastJSONClousure(const std::string &filePath) 238 { 239 std::string lineBk; // The Last Line 240 std::string line; 241 std::ifstream inputStream(filePath); 242 while (getline(inputStream, line)) { 243 lineBk = line; 244 } 245 return lineBk.compare("}") == 0; 246 } 247 248 int ExtractCountFromMeta(const std::string &filePath, std::string typeLable) 249 { 250 std::string line; 251 std::ifstream inputStream(filePath); 252 while (getline(inputStream, line)) { 253 int length = line.length() - typeLable.length() - 1; 254 if (line.find(typeLable) != line.npos) { // Get 255 if (line.find(",") == line.npos) { // "trace_function_count" end without "," 256 length = line.length() - typeLable.length(); 257 } 258 line = line.substr(typeLable.length(), length); 259 return std::stoi(line.c_str()); 260 } 261 } 262 return -1; 263 } 264 265 int ExtractCountFromPayload(const std::string &filePath, std::string dataLabel) 266 { 267 std::string line; 268 bool hit = false; 269 int loop = 0; 270 std::ifstream inputStream(filePath); 271 while (getline(inputStream, line)) { 272 if (!hit && line.find(dataLabel) != line.npos) { // Get 273 loop += 1; // First Line 274 hit = true; 275 if (line.find("[]") != line.npos) { // Empty 276 loop = 0; 277 return loop; 278 } else { 279 continue; 280 } 281 } 282 if (hit) { 283 if (line.find("],") != line.npos) { // Reach End 284 loop += 1; // End Line 285 return loop; 286 } else { 287 loop++; 288 continue; 289 } 290 } 291 } 292 return -1; 293 } 294 295private: 296 EcmaVM *instance {nullptr}; 297}; 298 299HWTEST_F_L0(HProfTest, ParseJSONHeader) 300{ 301 HProfTestHelper tester(instance); 302 tester.GenerateSnapShot("test1.heapsnapshot"); 303 ASSERT_TRUE(tester.ContrastJSONLineHeader("test1.heapsnapshot", "{\"snapshot\":")); 304 ASSERT_TRUE(tester.ContrastJSONLineHeader("test1.heapsnapshot", "{\"meta\":")); 305 ASSERT_TRUE(tester.ContrastJSONLineHeader("test1.heapsnapshot", "{\"node_fields\":")); 306 ASSERT_TRUE(tester.ContrastJSONLineHeader("test1.heapsnapshot", "\"node_types\":")); 307 ASSERT_TRUE(tester.ContrastJSONLineHeader("test1.heapsnapshot", "\"edge_fields\":")); 308 ASSERT_TRUE(tester.ContrastJSONLineHeader("test1.heapsnapshot", "\"edge_types\":")); 309 ASSERT_TRUE(tester.ContrastJSONLineHeader("test1.heapsnapshot", "\"trace_function_info_fields\":")); 310 ASSERT_TRUE(tester.ContrastJSONLineHeader("test1.heapsnapshot", "\"trace_node_fields\":")); 311 ASSERT_TRUE(tester.ContrastJSONLineHeader("test1.heapsnapshot", "\"sample_fields\":")); 312 ASSERT_TRUE(tester.ContrastJSONLineHeader("test1.heapsnapshot", "\"location_fields\":")); 313} 314 315HWTEST_F_L0(HProfTest, ContrastTraceFunctionInfo) 316{ 317 HProfTestHelper tester(instance); 318 tester.GenerateSnapShot("test2.heapsnapshot"); 319 ASSERT_TRUE(tester.ContrastJSONSectionPayload("test2.heapsnapshot", "\"trace_function_infos\":", 2)); // Empty 320} 321 322HWTEST_F_L0(HProfTest, ContrastTraceTree) 323{ 324 HProfTestHelper tester(instance); 325 tester.GenerateSnapShot("test3.heapsnapshot"); 326 ASSERT_TRUE(tester.ContrastJSONSectionPayload("test3.heapsnapshot", "\"trace_tree\":", 2)); // Empty 327} 328 329HWTEST_F_L0(HProfTest, ContrastSamples) 330{ 331 HProfTestHelper tester(instance); 332 tester.GenerateSnapShot("test4.heapsnapshot"); 333 ASSERT_TRUE(tester.ContrastJSONSectionPayload("test4.heapsnapshot", "\"samples\":", 2)); // Empty 334} 335 336HWTEST_F_L0(HProfTest, ContrastLocations) 337{ 338 HProfTestHelper tester(instance); 339 tester.GenerateSnapShot("test5.heapsnapshot"); 340 ASSERT_TRUE(tester.ContrastJSONSectionPayload("test5.heapsnapshot", "\"locations\":", 2)); // Empty 341} 342 343HWTEST_F_L0(HProfTest, ContrastString) 344{ 345 HProfTestHelper tester(instance); 346 tester.GenerateSnapShot("test6.heapsnapshot"); 347 ASSERT_TRUE(tester.ContrastJSONSectionPayload("test6.heapsnapshot", "\"strings\":[", 2)); 348} 349 350HWTEST_F_L0(HProfTest, ContrastClosure) 351{ 352 HProfTestHelper tester(instance); 353 tester.GenerateSnapShot("test7.heapsnapshot"); 354 ASSERT_TRUE(tester.ContrastJSONClousure("test7.heapsnapshot")); 355} 356 357HWTEST_F_L0(HProfTest, ContrastEdgeCount) 358{ 359 HProfTestHelper tester(instance); 360 tester.GenerateSnapShot("test8.heapsnapshot"); 361 ASSERT_TRUE(tester.ExtractCountFromMeta("test8.heapsnapshot", "\"edge_count\":") == 362 tester.ExtractCountFromPayload("test8.heapsnapshot", "\"edges\":[")); 363} 364 365HWTEST_F_L0(HProfTest, TraceFuncInfoCount) 366{ 367 HProfTestHelper tester(instance); 368 tester.GenerateSnapShot("test9.heapsnapshot"); 369 ASSERT_TRUE(tester.ExtractCountFromMeta("test9.heapsnapshot", "\"trace_function_count\":") == 370 tester.ExtractCountFromPayload("test9.heapsnapshot", "\"trace_function_infos\":")); 371} 372 373HWTEST_F_L0(HProfTest, DumpNativeSize) 374{ 375 int nativeSizeNum = 6; 376 int count = nativeSizeNum / 2; 377 while (count-- > 0) { 378 instance->GetFactory()->NewJSArrayBuffer(10); 379 instance->GetFactory()->NewJSArrayBuffer(20); 380 } 381 HProfTestHelper tester(instance); 382 tester.GenerateSnapShot("test10.heapsnapshot"); 383 384 ASSERT_TRUE(tester.ContrastJSONPayloadCntAndHitStrField("test10.heapsnapshot", "{\"node_fields\":", 385 9, "native_size")); 386 ASSERT_TRUE(tester.ContrastJSONSectionPayload("test10.heapsnapshot", "\"nodes\":[", 8)); 387 ASSERT_TRUE(tester.ContrastJSONNativeSizeNum("test10.heapsnapshot", "\"nodes\":[", nativeSizeNum)); 388} 389 390HWTEST_F_L0(HProfTest, TestSetDumpFormatInRandomNum) 391{ 392 std::srand(std::time(nullptr)); 393 for (int i = 0; i < 20; i++) { 394 size_t size = std::rand() % 128; 395 if (size <= 0) { 396 continue; 397 } 398 EcmaVM *vm = instance; 399 size_t maxEnumNum = static_cast<size_t>(DumpFormat::OTHER) + 1; 400 DumpFormat dumpFormat = static_cast<DumpFormat>(size % maxEnumNum); 401 DumpSnapShotOption dumpOption; 402 dumpOption.dumpFormat = dumpFormat; 403 dumpOption.isVmMode = true; 404 dumpOption.isPrivate = false; 405 dumpOption.captureNumericValue = false; 406 if (size > sizeof(double)) { 407 size = sizeof(double); 408 } 409 uint8_t* data = reinterpret_cast<uint8_t*>(malloc(size)); 410 for (size_t j = 0; j < size - 1; j++) { 411 data[j] = size; 412 } 413 data[size - 1] = 0; 414 std::string path(data, data + size); 415 FileStream stream(path); 416 Progress *progress = nullptr; 417 DFXJSNApi::DumpHeapSnapshot(vm, &stream, dumpOption, progress); 418 HeapProfilerInterface *heapProfile = HeapProfilerInterface::GetInstance(instance); 419 heapProfile->DumpHeapSnapshot(&stream, dumpOption); 420 ASSERT_TRUE(heapProfile->GetIdCount() > 0); 421 free(data); 422 } 423} 424 425HWTEST_F_L0(HProfTest, TestIdConsistency) 426{ 427 HProfTestHelper tester(instance); 428 int64_t count1 = tester.GenerateSnapShot("TestIdConsistency_1.heapsnapshot"); 429 for (int i = 0; i < 100; ++i) { 430 instance->GetFactory()->NewJSAsyncFuncObject(); 431 instance->GetFactory()->NewJSSymbol(); 432 } 433 int64_t count2 = tester.GenerateSnapShot("TestIdConsistency_2.heapsnapshot"); 434 ASSERT_TRUE(std::abs(count1 - count2) <= 500LL); 435 // load two heapsnapshots into chrome, and further use "Comparision View" 436} 437} // namespace panda::test 438