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 
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 
49     void EndOfStream() override {}
50     int GetSize() override
51     {
52         static const int HEAP_PROFILER_CHUNK_SIZE = 100_KB;
53         return HEAP_PROFILER_CHUNK_SIZE;
54     }
55     bool WriteChunk([[maybe_unused]] char *data, [[maybe_unused]] int32_t size) override
56     {
57         return true;
58     }
59     bool WriteBinBlock(char *data, int32_t size) override
60     {
61         return WriteChunk(data, size);
62     }
63     bool Good() override
64     {
65         return testStream_.good();
66     }
67 
68     void UpdateHeapStats([[maybe_unused]] HeapStat* updateData, [[maybe_unused]] int32_t count) override
69     {
70     }
71 
72     void UpdateLastSeenObjectId([[maybe_unused]] int32_t lastSeenObjectId, [[maybe_unused]]int64_t timeStampUs) override
73     {
74     }
75 
Clear()76     void Clear()
77     {
78         testStream_.clear(std::ios::badbit);
79     }
80 
81 private:
82     std::fstream testStream_;
83 };
84 }
85 
86 namespace panda::test {
87 class HeapTrackerTest : public testing::Test {
88 public:
SetUpTestCase()89     static void SetUpTestCase()
90     {
91         GTEST_LOG_(INFO) << "SetUpTestCase";
92     }
93 
TearDownTestCase()94     static void TearDownTestCase()
95     {
96         GTEST_LOG_(INFO) << "TearDownCase";
97     }
98 
99     void SetUp() override
100     {
101         TestHelper::CreateEcmaVMWithScope(instance, thread, scope);
102         instance->SetEnableForceGC(false);
103     }
104 
105     void TearDown() override
106     {
107         TestHelper::DestroyEcmaVMWithScope(instance, scope);
108     }
109 
110     EcmaVM *instance {nullptr};
111     EcmaHandleScope *scope {nullptr};
112     JSThread *thread {nullptr};
113 };
114 
HWTEST_F_L0(HeapTrackerTest, HeapTracker)115 HWTEST_F_L0(HeapTrackerTest, HeapTracker)
116 {
117     [[maybe_unused]] EcmaHandleScope handleScope(thread);
118     HeapProfilerInterface *heapProfile = HeapProfilerInterface::GetInstance(instance);
119     heapProfile->StartHeapTracking(50);
120     sleep(1);
121     int count = 100;
122     while (count-- > 0) {
123         instance->GetFactory()->NewJSAsyncFuncObject();
124     }
125     sleep(1);
126     count = 100;
127     while (count-- > 0) {
128         instance->GetFactory()->NewJSSymbol();
129     }
130     sleep(1);
131     count = 100;
132     while (count-- > 0) {
133         JSHandle<JSTaggedValue> undefined = instance->GetJSThread()->GlobalConstants()->GetHandledUndefined();
134         JSHandle<EcmaString> string = instance->GetFactory()->NewFromASCII("Hello World");
135         instance->GetFactory()->NewJSString(JSHandle<JSTaggedValue>(string), undefined);
136     }
137 
138     // Create file test.heaptimeline
139     std::string fileName = "test.heaptimeline";
140     fstream outputString(fileName, std::ios::out);
141     outputString.close();
142     outputString.clear();
143 
144     FileStream stream(fileName.c_str());
145     heapProfile->StopHeapTracking(&stream, nullptr);
146     HeapProfilerInterface::Destroy(instance);
147 
148     // Check
149     fstream inputStream(fileName, std::ios::in);
150     std::string line;
151     std::string emptySample = "\"samples\":";
152     std::string firstSample = "\"samples\":[0, ";
153     uint32_t emptySize = emptySample.size();
154     bool isFind = false;
155     while (getline(inputStream, line)) {
156         if (line.substr(0U, emptySize) == emptySample) {
157             ASSERT_TRUE(line.substr(0, firstSample.size()) == firstSample);
158             isFind = true;
159         }
160     }
161     ASSERT_TRUE(isFind);
162 
163     inputStream.close();
164     inputStream.clear();
165     std::remove(fileName.c_str());
166 }
167 
HWTEST_F_L0(HeapTrackerTest, HeapTrackerTraceAllocation)168 HWTEST_F_L0(HeapTrackerTest, HeapTrackerTraceAllocation)
169 {
170     [[maybe_unused]] EcmaHandleScope handleScope(thread);
171     HeapProfilerInterface *heapProfile = HeapProfilerInterface::GetInstance(instance);
172     TestStream testStream;
173     heapProfile->StartHeapTracking(50, true, &testStream, true);
174     sleep(1);
175     int count = 100;
176     while (count-- > 0) {
177         instance->GetFactory()->NewJSAsyncFuncObject();
178     }
179     sleep(1);
180     count = 100;
181     while (count-- > 0) {
182         instance->GetFactory()->NewJSSymbol();
183     }
184     sleep(1);
185     count = 100;
186     while (count-- > 0) {
187         JSHandle<JSTaggedValue> undefined = instance->GetJSThread()->GlobalConstants()->GetHandledUndefined();
188         JSHandle<EcmaString> string = instance->GetFactory()->NewFromASCII("Hello World");
189         instance->GetFactory()->NewJSString(JSHandle<JSTaggedValue>(string), undefined);
190     }
191 
192     // Create file test.heaptimeline
193     std::string fileName = "test.heaptimeline";
194     fstream outputString(fileName, std::ios::out);
195     outputString.close();
196     outputString.clear();
197 
198     FileStream stream(fileName.c_str());
199     TestProgress testProgress;
200     heapProfile->StopHeapTracking(&stream, &testProgress);
201     HeapProfilerInterface::Destroy(instance);
202 
203     // Check
204     fstream inputStream(fileName, std::ios::in);
205     std::string line;
206     std::string emptyTraceFunctionInfo = "\"trace_function_infos\":[";
207     std::string firstTraceFunctionInfo = "\"trace_function_infos\":[0,";
208     uint32_t emptyTraceFunctionInfoSize = emptyTraceFunctionInfo.size();
209     bool traceFunctionInfoIsFind = false;
210     while (getline(inputStream, line)) {
211         if (line.substr(0U, emptyTraceFunctionInfoSize) == emptyTraceFunctionInfo) {
212             ASSERT_TRUE(line.substr(0, firstTraceFunctionInfo.size()) == firstTraceFunctionInfo);
213             traceFunctionInfoIsFind = true;
214             break;
215         }
216     }
217     ASSERT_TRUE(traceFunctionInfoIsFind);
218 
219     inputStream.close();
220     inputStream.clear();
221     std::remove(fileName.c_str());
222 }
223 
HWTEST_F_L0(HeapTrackerTest, DumpHeapSnapshot)224 HWTEST_F_L0(HeapTrackerTest, DumpHeapSnapshot)
225 {
226     [[maybe_unused]] EcmaHandleScope handleScope(thread);
227     HeapProfilerInterface *heapProfile = HeapProfilerInterface::GetInstance(instance);
228 
229     sleep(1);
230     int count = 100;
231     while (count-- > 0) {
232         instance->GetFactory()->NewJSAsyncFuncObject();
233     }
234     sleep(1);
235     count = 100;
236     while (count-- > 0) {
237         instance->GetFactory()->NewJSSymbol();
238     }
239     sleep(1);
240     count = 100;
241     while (count-- > 0) {
242         JSHandle<JSTaggedValue> undefined = instance->GetJSThread()->GlobalConstants()->GetHandledUndefined();
243         JSHandle<EcmaString> string = instance->GetFactory()->NewFromASCII("Hello World");
244         instance->GetFactory()->NewJSString(JSHandle<JSTaggedValue>(string), undefined);
245     }
246 
247     // Create file test.heaptimeline
248     std::string fileName = "HeapTrackerTest1.heapsnapshot";
249     fstream outputString(fileName, std::ios::out);
250     outputString.close();
251     outputString.clear();
252 
253     FileStream stream(fileName.c_str());
254     DumpSnapShotOption dumpOption = { DumpFormat::JSON, true, true };
255     heapProfile->DumpHeapSnapshot(&stream, dumpOption);
256     HeapProfilerInterface::Destroy(instance);
257 
258     // Check
259     fstream inputStream(fileName, std::ios::in);
260     std::string line;
261     std::string nodes = "\"nodes\":[";
262     std::string sample = "\"samples\":[]";
263     uint32_t nodesSize = nodes.size();
264     uint32_t sampleSize = sample.size();
265     bool isNodesFind = false;
266     bool isSampleFind = false;
267     while (getline(inputStream, line)) {
268         if (line.substr(0U, nodesSize) == nodes) {
269             isNodesFind = true;
270         }
271 
272         if (line.substr(0U, sampleSize) == sample) {
273             isSampleFind = true;
274         }
275     }
276     ASSERT_TRUE(isNodesFind);
277     ASSERT_TRUE(isSampleFind);
278 
279     inputStream.close();
280     inputStream.clear();
281     std::remove(fileName.c_str());
282 }
283 
HWTEST_F_L0(HeapTrackerTest, HeapSnapshotBuildUp)284 HWTEST_F_L0(HeapTrackerTest, HeapSnapshotBuildUp)
285 {
286     bool isVmMode = true;
287     bool isPrivate = false;
288     bool traceAllocation = false;
289     bool captureNumericValue = false;
290     HeapProfiler heapProfiler(instance);
291     DumpSnapShotOption dumpOption;
292     dumpOption.isVmMode = isVmMode;
293     dumpOption.isPrivate = isPrivate;
294     dumpOption.captureNumericValue = captureNumericValue;
295     HeapSnapshot heapSnapshot(instance, heapProfiler.GetEcmaStringTable(), dumpOption, traceAllocation,
296                               heapProfiler.GetEntryIdMap(), instance->GetChunk());
297     EXPECT_TRUE(heapSnapshot.BuildUp());
298 }
299 
HWTEST_F_L0(HeapTrackerTest, HeapSnapshotUpdateNode)300 HWTEST_F_L0(HeapTrackerTest, HeapSnapshotUpdateNode)
301 {
302     bool isVmMode = true;
303     bool isPrivate = false;
304     bool traceAllocation = false;
305     bool captureNumericValue = false;
306     HeapProfiler heapProfiler(instance);
307     DumpSnapShotOption dumpOption;
308     dumpOption.isVmMode = isVmMode;
309     dumpOption.isPrivate = isPrivate;
310     dumpOption.captureNumericValue = captureNumericValue;
311     HeapSnapshot heapSnapshot(instance, heapProfiler.GetEcmaStringTable(), dumpOption, traceAllocation,
312                               heapProfiler.GetEntryIdMap(), instance->GetChunk());
313     size_t beginNode = heapSnapshot.GetNodeCount();
314     heapSnapshot.UpdateNodes();
315     size_t endNode = heapSnapshot.GetNodeCount();
316     EXPECT_TRUE(beginNode != endNode);
317 }
318 }  // namespace panda::test
319