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 <fcntl.h>
19
20 #include "ecmascript/dfx/hprof/heap_profiler_interface.h"
21 #include "ecmascript/dfx/hprof/heap_profiler.h"
22 #include "ecmascript/dfx/hprof/heap_snapshot_json_serializer.h"
23 #include "ecmascript/dfx/hprof/heap_snapshot.h"
24 #include "ecmascript/ecma_string.h"
25 #include "ecmascript/global_env.h"
26
27 #include "ecmascript/js_tagged_value.h"
28 #include "ecmascript/js_thread.h"
29 #include "ecmascript/mem/heap.h"
30 #include "ecmascript/tests/test_helper.h"
31 #include "ecmascript/dfx/hprof/file_stream.h"
32
33 using namespace panda::ecmascript;
34
35 namespace panda::ecmascript {
36 class TestProgress : public Progress {
37 public:
38 TestProgress() = default;
39 ~TestProgress() = default;
40
ReportProgress(int32_t done,int32_t total)41 void ReportProgress([[maybe_unused]]int32_t done, [[maybe_unused]]int32_t total) override {}
42 };
43
44 class TestStream : public Stream {
45 public:
46 TestStream() = default;
47 ~TestStream() = default;
48
EndOfStream()49 void EndOfStream() override {}
GetSize()50 int GetSize() override
51 {
52 static const int heapProfilerChunkSise = 100_KB;
53 return heapProfilerChunkSise;
54 }
WriteChunk(char * data,int32_t size)55 bool WriteChunk([[maybe_unused]]char *data, [[maybe_unused]]int32_t size) override
56 {
57 return true;
58 }
Good()59 bool Good() override
60 {
61 return testStream_.good();
62 }
63
UpdateHeapStats(HeapStat * updateData,int32_t count)64 void UpdateHeapStats([[maybe_unused]]HeapStat* updateData, [[maybe_unused]]int32_t count) override
65 {
66 }
67
UpdateLastSeenObjectId(int32_t lastSeenObjectId,int64_t timeStampUs)68 void UpdateLastSeenObjectId([[maybe_unused]]int32_t lastSeenObjectId, [[maybe_unused]]int64_t timeStampUs) override
69 {
70 }
71
Clear()72 void Clear()
73 {
74 testStream_.clear(std::ios::badbit);
75 }
76
77 private:
78 std::fstream testStream_;
79 };
80 }
81
82 namespace panda::test {
83 class HeapTrackerTest : public testing::Test {
84 public:
SetUpTestCase()85 static void SetUpTestCase()
86 {
87 GTEST_LOG_(INFO) << "SetUpTestCase";
88 }
89
TearDownTestCase()90 static void TearDownTestCase()
91 {
92 GTEST_LOG_(INFO) << "TearDownCase";
93 }
94
SetUp()95 void SetUp() override
96 {
97 TestHelper::CreateEcmaVMWithScope(instance, thread, scope);
98 instance->SetEnableForceGC(false);
99 }
100
TearDown()101 void TearDown() override
102 {
103 TestHelper::DestroyEcmaVMWithScope(instance, scope);
104 }
105
106 EcmaVM *instance {nullptr};
107 EcmaHandleScope *scope {nullptr};
108 JSThread *thread {nullptr};
109 };
110
HWTEST_F_L0(HeapTrackerTest,HeapTracker)111 HWTEST_F_L0(HeapTrackerTest, HeapTracker)
112 {
113 [[maybe_unused]] EcmaHandleScope handleScope(thread);
114 HeapProfilerInterface *heapProfile = HeapProfilerInterface::GetInstance(instance);
115 heapProfile->StartHeapTracking(50);
116 sleep(1);
117 int count = 100;
118 while (count-- > 0) {
119 instance->GetFactory()->NewJSAsyncFuncObject();
120 }
121 sleep(1);
122 count = 100;
123 while (count-- > 0) {
124 instance->GetFactory()->NewJSSymbol();
125 }
126 sleep(1);
127 count = 100;
128 while (count-- > 0) {
129 JSHandle<EcmaString> string = instance->GetFactory()->NewFromASCII("Hello World");
130 instance->GetFactory()->NewJSString(JSHandle<JSTaggedValue>(string));
131 }
132
133 // Create file test.heaptimeline
134 std::string fileName = "test.heaptimeline";
135 fstream outputString(fileName, std::ios::out);
136 outputString.close();
137 outputString.clear();
138
139 FileStream stream(fileName.c_str());
140 heapProfile->StopHeapTracking(&stream, nullptr);
141 HeapProfilerInterface::Destroy(instance);
142
143 // Check
144 fstream inputStream(fileName, std::ios::in);
145 std::string line;
146 std::string emptySample = "\"samples\":";
147 std::string firstSample = "\"samples\":[0, ";
148 uint32_t emptySize = emptySample.size();
149 bool isFind = false;
150 while (getline(inputStream, line)) {
151 if (line.substr(0U, emptySize) == emptySample) {
152 ASSERT_TRUE(line.substr(0, firstSample.size()) == firstSample);
153 isFind = true;
154 }
155 }
156 ASSERT_TRUE(isFind);
157
158 inputStream.close();
159 inputStream.clear();
160 std::remove(fileName.c_str());
161 }
162
HWTEST_F_L0(HeapTrackerTest,HeapTrackerTraceAllocation)163 HWTEST_F_L0(HeapTrackerTest, HeapTrackerTraceAllocation)
164 {
165 [[maybe_unused]] EcmaHandleScope handleScope(thread);
166 HeapProfilerInterface *heapProfile = HeapProfilerInterface::GetInstance(instance);
167 TestStream testStream;
168 heapProfile->StartHeapTracking(50, true, &testStream, true);
169 sleep(1);
170 int count = 100;
171 while (count-- > 0) {
172 instance->GetFactory()->NewJSAsyncFuncObject();
173 }
174 sleep(1);
175 count = 100;
176 while (count-- > 0) {
177 instance->GetFactory()->NewJSSymbol();
178 }
179 sleep(1);
180 count = 100;
181 while (count-- > 0) {
182 JSHandle<EcmaString> string = instance->GetFactory()->NewFromASCII("Hello World");
183 instance->GetFactory()->NewJSString(JSHandle<JSTaggedValue>(string));
184 }
185
186 // Create file test.heaptimeline
187 std::string fileName = "test.heaptimeline";
188 fstream outputString(fileName, std::ios::out);
189 outputString.close();
190 outputString.clear();
191
192 FileStream stream(fileName.c_str());
193 TestProgress testProgress;
194 heapProfile->StopHeapTracking(&stream, &testProgress);
195 HeapProfilerInterface::Destroy(instance);
196
197 // Check
198 fstream inputStream(fileName, std::ios::in);
199 std::string line;
200 std::string emptyTraceFunctionInfo = "\"trace_function_infos\":[";
201 std::string emptyTraceNode = "\"trace_tree\":[";
202 std::string firstTraceFunctionInfo = "\"trace_function_infos\":[0,";
203 std::string firstTraceNode = "\"trace_tree\":[1,0";
204 uint32_t emptyTraceFunctionInfoSize = emptyTraceFunctionInfo.size();
205 uint32_t emptyTraceNodeSize = emptyTraceNode.size();
206 bool traceFunctionInfoIsFind = false;
207 bool traceNodeIsFind = false;
208 while (getline(inputStream, line)) {
209 if (line.substr(0U, emptyTraceFunctionInfoSize) == emptyTraceFunctionInfo) {
210 ASSERT_TRUE(line.substr(0, firstTraceFunctionInfo.size()) == firstTraceFunctionInfo);
211 traceFunctionInfoIsFind = true;
212 }
213
214 if (line.substr(0U, emptyTraceNodeSize) == emptyTraceNode) {
215 ASSERT_TRUE(line.substr(0, firstTraceNode.size()) == firstTraceNode);
216 traceNodeIsFind = true;
217 }
218 }
219 ASSERT_TRUE(traceFunctionInfoIsFind);
220 ASSERT_TRUE(traceNodeIsFind);
221
222 inputStream.close();
223 inputStream.clear();
224 std::remove(fileName.c_str());
225 }
226
HWTEST_F_L0(HeapTrackerTest,DumpHeapSnapshot)227 HWTEST_F_L0(HeapTrackerTest, DumpHeapSnapshot)
228 {
229 [[maybe_unused]] EcmaHandleScope handleScope(thread);
230 HeapProfilerInterface *heapProfile = HeapProfilerInterface::GetInstance(instance);
231
232 sleep(1);
233 int count = 100;
234 while (count-- > 0) {
235 instance->GetFactory()->NewJSAsyncFuncObject();
236 }
237 sleep(1);
238 count = 100;
239 while (count-- > 0) {
240 instance->GetFactory()->NewJSSymbol();
241 }
242 sleep(1);
243 count = 100;
244 while (count-- > 0) {
245 JSHandle<EcmaString> string = instance->GetFactory()->NewFromASCII("Hello World");
246 instance->GetFactory()->NewJSString(JSHandle<JSTaggedValue>(string));
247 }
248
249 // Create file test.heapsnapshot
250 std::string fileName = "test.heapsnapshot";
251 fstream outputString(fileName, std::ios::out);
252 outputString.close();
253 outputString.clear();
254
255 FileStream stream(fileName.c_str());
256 TestProgress testProgress;
257 heapProfile->DumpHeapSnapshot(DumpFormat::JSON, &stream, &testProgress, true, true);
258 HeapProfilerInterface::Destroy(instance);
259
260 // Check
261 fstream inputStream(fileName, std::ios::in);
262 std::string line;
263 std::string nodes = "\"nodes\":[";
264 std::string sample = "\"samples\":[]";
265 uint32_t nodesSize = nodes.size();
266 uint32_t sampleSize = sample.size();
267 bool isNodesFind = false;
268 bool isSampleFind = false;
269 while (getline(inputStream, line)) {
270 if (line.substr(0U, nodesSize) == nodes) {
271 isNodesFind = true;
272 }
273
274 if (line.substr(0U, sampleSize) == sample) {
275 isSampleFind = true;
276 }
277 }
278 ASSERT_TRUE(isNodesFind);
279 ASSERT_TRUE(isSampleFind);
280
281 inputStream.close();
282 inputStream.clear();
283 std::remove(fileName.c_str());
284 }
285
HWTEST_F_L0(HeapTrackerTest,HeapSnapshotBuildUp)286 HWTEST_F_L0(HeapTrackerTest, HeapSnapshotBuildUp)
287 {
288 bool isVmMode = true;
289 bool isPrivate = false;
290 bool traceAllocation = false;
291 HeapSnapshot heapSnapshot(instance, isVmMode, isPrivate, traceAllocation, instance->GetChunk());
292 EXPECT_TRUE(heapSnapshot.BuildUp());
293 }
294
HWTEST_F_L0(HeapTrackerTest,HeapSnapshotUpdateNode)295 HWTEST_F_L0(HeapTrackerTest, HeapSnapshotUpdateNode)
296 {
297 bool isVmMode = true;
298 bool isPrivate = false;
299 bool traceAllocation = false;
300 HeapSnapshot heapSnapshot(instance, isVmMode, isPrivate, traceAllocation, instance->GetChunk());
301 size_t beginNode = heapSnapshot.GetNodeCount();
302 heapSnapshot.UpdateNodes();
303 size_t endNode = heapSnapshot.GetNodeCount();
304 EXPECT_TRUE(beginNode != endNode);
305 }
306
HWTEST_F_L0(HeapTrackerTest,GenDumpFileName_001)307 HWTEST_F_L0(HeapTrackerTest, GenDumpFileName_001)
308 {
309 [[maybe_unused]] EcmaHandleScope handleScope(thread);
310 HeapProfilerInterface *heapProfile = HeapProfilerInterface::GetInstance(instance);
311
312 sleep(1);
313 int count = 100;
314 while (count-- > 0) {
315 instance->GetFactory()->NewJSAsyncFuncObject();
316 }
317 sleep(1);
318 count = 100;
319 while (count-- > 0) {
320 instance->GetFactory()->NewJSSymbol();
321 }
322 sleep(1);
323 count = 100;
324 while (count-- > 0) {
325 JSHandle<EcmaString> string = instance->GetFactory()->NewFromASCII("Hello World");
326 instance->GetFactory()->NewJSString(JSHandle<JSTaggedValue>(string));
327 }
328
329 TestStream stream;
330 stream.Clear();
331 EXPECT_TRUE(!stream.Good());
332 TestProgress testProgress;
333 heapProfile->DumpHeapSnapshot(DumpFormat::JSON, &stream, &testProgress, true, true);
334 HeapProfilerInterface::Destroy(instance);
335 }
336
HWTEST_F_L0(HeapTrackerTest,GenDumpFileName_002)337 HWTEST_F_L0(HeapTrackerTest, GenDumpFileName_002)
338 {
339 [[maybe_unused]] EcmaHandleScope handleScope(thread);
340 HeapProfilerInterface *heapProfile = HeapProfilerInterface::GetInstance(instance);
341
342 sleep(1);
343 int count = 100;
344 while (count-- > 0) {
345 instance->GetFactory()->NewJSAsyncFuncObject();
346 }
347 sleep(1);
348 count = 100;
349 while (count-- > 0) {
350 instance->GetFactory()->NewJSSymbol();
351 }
352 sleep(1);
353 count = 100;
354 while (count-- > 0) {
355 JSHandle<EcmaString> string = instance->GetFactory()->NewFromASCII("Hello World");
356 instance->GetFactory()->NewJSString(JSHandle<JSTaggedValue>(string));
357 }
358
359 TestStream stream;
360 stream.Clear();
361 EXPECT_TRUE(!stream.Good());
362 TestProgress testProgress;
363 heapProfile->DumpHeapSnapshot(DumpFormat::BINARY, &stream, &testProgress, true, true);
364 HeapProfilerInterface::Destroy(instance);
365 }
366
HWTEST_F_L0(HeapTrackerTest,GenDumpFileName_003)367 HWTEST_F_L0(HeapTrackerTest, GenDumpFileName_003)
368 {
369 [[maybe_unused]] EcmaHandleScope handleScope(thread);
370 HeapProfilerInterface *heapProfile = HeapProfilerInterface::GetInstance(instance);
371
372 sleep(1);
373 int count = 100;
374 while (count-- > 0) {
375 instance->GetFactory()->NewJSAsyncFuncObject();
376 }
377 sleep(1);
378 count = 100;
379 while (count-- > 0) {
380 instance->GetFactory()->NewJSSymbol();
381 }
382 sleep(1);
383 count = 100;
384 while (count-- > 0) {
385 JSHandle<EcmaString> string = instance->GetFactory()->NewFromASCII("Hello World");
386 instance->GetFactory()->NewJSString(JSHandle<JSTaggedValue>(string));
387 }
388
389 TestStream stream;
390 stream.Clear();
391 EXPECT_TRUE(!stream.Good());
392 TestProgress testProgress;
393 heapProfile->DumpHeapSnapshot(DumpFormat::OTHER, &stream, &testProgress, true, true);
394 HeapProfilerInterface::Destroy(instance);
395 }
396
HWTEST_F_L0(HeapTrackerTest,GenDumpFileName_004)397 HWTEST_F_L0(HeapTrackerTest, GenDumpFileName_004)
398 {
399 [[maybe_unused]] EcmaHandleScope handleScope(thread);
400 HeapProfilerInterface *heapProfile = HeapProfilerInterface::GetInstance(instance);
401
402 sleep(1);
403 int count = 100;
404 while (count-- > 0) {
405 instance->GetFactory()->NewJSAsyncFuncObject();
406 }
407 sleep(1);
408 count = 100;
409 while (count-- > 0) {
410 instance->GetFactory()->NewJSSymbol();
411 }
412 sleep(1);
413 count = 100;
414 while (count-- > 0) {
415 JSHandle<EcmaString> string = instance->GetFactory()->NewFromASCII("Hello World");
416 instance->GetFactory()->NewJSString(JSHandle<JSTaggedValue>(string));
417 }
418
419 TestStream stream;
420 stream.Clear();
421 EXPECT_TRUE(!stream.Good());
422 TestProgress testProgress;
423 DumpFormat dumFormat = static_cast<DumpFormat>(5);
424 heapProfile->DumpHeapSnapshot(dumFormat, &stream, &testProgress, true, true);
425 HeapProfilerInterface::Destroy(instance);
426 }
427
HWTEST_F_L0(HeapTrackerTest,FileDescriptorStreamEndOfStream)428 HWTEST_F_L0(HeapTrackerTest, FileDescriptorStreamEndOfStream)
429 {
430 int fd = 3;
431 FileDescriptorStream fileStream(fd);
432 EXPECT_TRUE(fileStream.Good());
433 fileStream.EndOfStream();
434 }
435
HWTEST_F_L0(HeapTrackerTest,StreamWriterEnd)436 HWTEST_F_L0(HeapTrackerTest, StreamWriterEnd)
437 {
438 [[maybe_unused]] EcmaHandleScope handleScope(thread);
439 HeapProfilerInterface *heapProfile = HeapProfilerInterface::GetInstance(instance);
440
441 sleep(1);
442 int count = 100;
443 while (count-- > 0) {
444 instance->GetFactory()->NewJSAsyncFuncObject();
445 }
446 sleep(1);
447 count = 100;
448 while (count-- > 0) {
449 instance->GetFactory()->NewJSSymbol();
450 }
451 sleep(1);
452 count = 100;
453 while (count-- > 0) {
454 JSHandle<EcmaString> string = instance->GetFactory()->NewFromASCII("Hello World");
455 instance->GetFactory()->NewJSString(JSHandle<JSTaggedValue>(string));
456 }
457
458 // Create file test.heapsnapshot
459 std::string fileName = "test.heapsnapshot";
460 fstream outputString(fileName, std::ios::out);
461 outputString.close();
462 outputString.clear();
463
464 FileStream stream(fileName.c_str());
465 CVector<HeapStat> statsBuffer;
466 statsBuffer.emplace_back(1, 2, 4);
467 stream.UpdateHeapStats(&statsBuffer.front(), static_cast<int32_t>(statsBuffer.size()));
468 stream.UpdateLastSeenObjectId(1, 1677567644913058);
469
470 TestProgress testProgress;
471 heapProfile->DumpHeapSnapshot(DumpFormat::JSON, &stream, &testProgress, true, true);
472 StreamWriter streamWriter(&stream);
473 streamWriter.End();
474 HeapProfilerInterface::Destroy(instance);
475
476 // Check
477 fstream inputStream(fileName, std::ios::in);
478 std::string line;
479 std::string nodes = "\"nodes\":[";
480 std::string sample = "\"samples\":[]";
481 uint32_t nodesSize = nodes.size();
482 uint32_t sampleSize = sample.size();
483 bool isNodesFind = false;
484 bool isSampleFind = false;
485 while (getline(inputStream, line)) {
486 if (line.substr(0U, nodesSize) == nodes) {
487 isNodesFind = true;
488 }
489
490 if (line.substr(0U, sampleSize) == sample) {
491 isSampleFind = true;
492 }
493 }
494 ASSERT_TRUE(isNodesFind);
495 ASSERT_TRUE(isSampleFind);
496
497 inputStream.close();
498 inputStream.clear();
499 std::remove(fileName.c_str());
500 }
501
HWTEST_F_L0(HeapTrackerTest,GetStringByKey)502 HWTEST_F_L0(HeapTrackerTest, GetStringByKey)
503 {
504 StringKey key = static_cast<StringKey>(2);
505 StringHashMap stringHashMap(instance);
506 CString *hashMap = stringHashMap.GetStringByKey(key);
507 EXPECT_TRUE(hashMap == nullptr);
508 }
509
HWTEST_F_L0(HeapTrackerTest,FormatString)510 HWTEST_F_L0(HeapTrackerTest, FormatString)
511 {
512 bool isVmMode = true;
513 bool isPrivate = false;
514 bool traceAllocation = false;
515 HeapSnapshot heapSnapshot(instance, isVmMode, isPrivate, traceAllocation, instance->GetChunk());
516
517 StringHashMap stringHashMap(instance);
518 CString ret = "H\"e\rl\nl\\o\t W\fo\31rld!";
519 stringHashMap.GetString(ret);
520 StringKey retKey = std::hash<std::string>{} (std::string(ret));
521
522 CString *tmpResult = nullptr;
523 tmpResult = stringHashMap.GetStringByKey(retKey);
524 EXPECT_TRUE(tmpResult != nullptr);
525 EXPECT_TRUE(*tmpResult == "H`e`l`l`o` W`o`rld!");
526 }
527
HWTEST_F_L0(HeapTrackerTest,FileDescriptorStreamWriteChunk)528 HWTEST_F_L0(HeapTrackerTest, FileDescriptorStreamWriteChunk)
529 {
530 int32_t fd = -1;
531 FileDescriptorStream testFileStream(fd);
532 CVector<HeapStat> statsBuffer;
533 statsBuffer.emplace_back(1, 2, 4);
534 testFileStream.UpdateHeapStats(&statsBuffer.front(), static_cast<int32_t>(statsBuffer.size()));
535 testFileStream.UpdateLastSeenObjectId(1, 1677567644913058);
536 testFileStream.GetSize();
537 std::string testString = "Hello!";
538 int strSize = testString.size();
539 bool isFileStream = testFileStream.WriteChunk(testString.data(), strSize);
540 EXPECT_TRUE(!isFileStream);
541
542 fd = 5;
543 FileDescriptorStream tmpFileStream(fd);
544 tmpFileStream.Good();
545 testString = "Hello!";
546 strSize = testString.size();
547 isFileStream = tmpFileStream.WriteChunk(testString.data(), strSize);
548 close(fd);
549 EXPECT_TRUE(!isFileStream);
550
551 std::string fileName = "test.StreamWriteChunk";
552 fd = open(fileName.c_str(), O_RDONLY);
553 if (fd < 0) {
554 fd = open(fileName.c_str(), O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IROTH);
555 }
556 FileDescriptorStream fileStream(fd);
557 testString = "Hello!";
558 strSize = testString.size();
559 isFileStream = fileStream.WriteChunk(testString.data(), strSize);
560 EXPECT_TRUE(isFileStream);
561 std::remove(fileName.c_str());
562 close(fd);
563 }
564
HWTEST_F_L0(HeapTrackerTest,AddNodeToTree)565 HWTEST_F_L0(HeapTrackerTest, AddNodeToTree)
566 {
567 CVector<uint32_t> traceNodeIndex;
568 for (int i = 0; i < 3; i++) {
569 traceNodeIndex.push_back(i + 1);
570 }
571 TraceTree traceTree;
572 TraceNode *traceNode = traceTree.AddNodeToTree(traceNodeIndex);
573 EXPECT_TRUE(traceNode != nullptr);
574 }
575
HWTEST_F_L0(HeapTrackerTest,FindOrAddChild)576 HWTEST_F_L0(HeapTrackerTest, FindOrAddChild)
577 {
578 TraceTree traceTree;
579 uint32_t index = 1;
580 TraceNode traceNode(&traceTree, index);
581 TraceNode *node = traceNode.FindOrAddChild(index);
582 EXPECT_TRUE(node->GetNodeIndex() == 1);
583
584 TraceNode *tmpNode = traceNode.FindOrAddChild(2);
585 EXPECT_TRUE(tmpNode->GetNodeIndex() == 2);
586 }
587 } // namespace panda::test
588