* Copyright (c) 2021 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <cstdio>
#include <fstream>
#include <fcntl.h>
#include <sstream>
#include "ecmascript/dfx/hprof/heap_profiler_interface.h"
#include "ecmascript/js_object.h"
#include "ecmascript/dfx/hprof/heap_profiler.h"
#include "ecmascript/dfx/hprof/heap_snapshot_json_serializer.h"
#include "ecmascript/dfx/hprof/heap_snapshot.h"
#include "ecmascript/ecma_string.h"
#include "ecmascript/global_env.h"
#include "ecmascript/js_tagged_value.h"
#include "ecmascript/js_thread.h"
#include "ecmascript/mem/heap.h"
#include "ecmascript/tests/test_helper.h"
#include "ecmascript/dfx/hprof/file_stream.h"
using namespace panda::ecmascript;
namespace panda::ecmascript {
class TestProgress : public Progress {
public:
TestProgress() = default;
~TestProgress() = default;
void ReportProgress([[maybe_unused]] int32_t done, [[maybe_unused]] int32_t total) override {}
};
class TestStream : public Stream {
public:
TestStream() = default;
~TestStream() = default;
void EndOfStream() override {}
int GetSize() override
{
static const int HEAP_PROFILER_CHUNK_SIZE = 100_KB;
return HEAP_PROFILER_CHUNK_SIZE;
}
bool WriteChunk([[maybe_unused]] char *data, [[maybe_unused]] int32_t size) override
{
return true;
}
bool WriteBinBlock(char *data, int32_t size) override
{
return WriteChunk(data, size);
}
bool Good() override
{
return testStream_.good();
}
void UpdateHeapStats([[maybe_unused]] HeapStat* updateData, [[maybe_unused]] int32_t count) override
{
}
void UpdateLastSeenObjectId([[maybe_unused]] int32_t lastSeenObjectId, [[maybe_unused]]int64_t timeStampUs) override
{
}
void Clear()
{
testStream_.clear(std::ios::badbit);
}
private:
std::fstream testStream_;
};
}
namespace panda::test {
class HeapTrackerTest : public testing::Test {
public:
static void SetUpTestCase()
{
GTEST_LOG_(INFO) << "SetUpTestCase";
}
static void TearDownTestCase()
{
GTEST_LOG_(INFO) << "TearDownCase";
}
void SetUp() override
{
TestHelper::CreateEcmaVMWithScope(instance, thread, scope);
instance->SetEnableForceGC(false);
}
void TearDown() override
{
TestHelper::DestroyEcmaVMWithScope(instance, scope);
}
EcmaVM *instance {nullptr};
EcmaHandleScope *scope {nullptr};
JSThread *thread {nullptr};
};
struct MockNativeRef {
void *data;
};
static void *MockGetNativeReferenceData(void *ref)
{
return reinterpret_cast<MockNativeRef *>(ref)->data;
}
HWTEST_F_L0(HeapTrackerTest, HeapTracker)
{
[[maybe_unused]] EcmaHandleScope handleScope(thread);
HeapProfilerInterface *heapProfile = HeapProfilerInterface::GetInstance(instance);
heapProfile->StartHeapTracking(50);
sleep(1);
int count = 100;
while (count-- > 0) {
instance->GetFactory()->NewJSAsyncFuncObject();
}
sleep(1);
count = 100;
while (count-- > 0) {
instance->GetFactory()->NewJSSymbol();
}
sleep(1);
count = 100;
while (count-- > 0) {
JSHandle<JSTaggedValue> undefined = instance->GetJSThread()->GlobalConstants()->GetHandledUndefined();
JSHandle<EcmaString> string = instance->GetFactory()->NewFromASCII("Hello World");
instance->GetFactory()->NewJSString(JSHandle<JSTaggedValue>(string), undefined);
}
static int wrappedNativeObj = 0xABCD;
MockNativeRef *mockRef = new MockNativeRef { reinterpret_cast<void *>(&wrappedNativeObj) };
{
EcmaHandleScope innerScope(thread);
ObjectFactory *factory = instance->GetFactory();
JSHandle<JSObject> wrappedObject = factory->CreateNapiObject(true);
JSObject::SetNativePointerFieldCount(thread, wrappedObject, 1);
JSObject::SetNativePointerField(thread, wrappedObject, 0,
reinterpret_cast<void *>(mockRef),
nullptr, nullptr);
instance->SetNativeReferenceDataGetter(
reinterpret_cast<ecmascript::NativeReferenceDataCallbackGetter>(
MockGetNativeReferenceData));
}
delete mockRef;
mockRef = nullptr;
std::stringstream nativeAddrSS;
nativeAddrSS << "0x" << std::hex << reinterpret_cast<uintptr_t>(&wrappedNativeObj);
std::string nativeAddrStr = nativeAddrSS.str();
std::string fileName = "test.heaptimeline";
fstream outputString(fileName, std::ios::out);
outputString.close();
outputString.clear();
FileStream stream(fileName.c_str());
heapProfile->StopHeapTracking(&stream, nullptr);
HeapProfilerInterface::Destroy(instance);
fstream inputStream(fileName, std::ios::in);
std::string line;
std::string emptySample = "\"samples\":";
std::string firstSample = "\"samples\":[0, ";
uint32_t emptySize = emptySample.size();
bool isFind = false;
bool isWrappedFind = false;
bool isNativeAddrFind = false;
while (getline(inputStream, line)) {
if (line.substr(0U, emptySize) == emptySample) {
ASSERT_TRUE(line.substr(0, firstSample.size()) == firstSample);
isFind = true;
}
if (line.find("JSWrappedNapiObject") != std::string::npos) {
isWrappedFind = true;
}
if (line.find(nativeAddrStr) != std::string::npos) {
isNativeAddrFind = true;
}
}
ASSERT_TRUE(isFind);
ASSERT_FALSE(isWrappedFind) << "GC'd object must not appear in timeline snapshot";
ASSERT_FALSE(isNativeAddrFind) << "Deleted native ref address must not appear: " << nativeAddrStr;
inputStream.close();
inputStream.clear();
std::remove(fileName.c_str());
}
HWTEST_F_L0(HeapTrackerTest, HeapTrackerTraceAllocation)
{
[[maybe_unused]] EcmaHandleScope handleScope(thread);
HeapProfilerInterface *heapProfile = HeapProfilerInterface::GetInstance(instance);
TestStream testStream;
heapProfile->StartHeapTracking(50, true, &testStream, true);
sleep(1);
int count = 100;
while (count-- > 0) {
instance->GetFactory()->NewJSAsyncFuncObject();
}
sleep(1);
count = 100;
while (count-- > 0) {
instance->GetFactory()->NewJSSymbol();
}
sleep(1);
count = 100;
while (count-- > 0) {
JSHandle<JSTaggedValue> undefined = instance->GetJSThread()->GlobalConstants()->GetHandledUndefined();
JSHandle<EcmaString> string = instance->GetFactory()->NewFromASCII("Hello World");
instance->GetFactory()->NewJSString(JSHandle<JSTaggedValue>(string), undefined);
}
static int wrappedNativeObj = 0xF00D;
MockNativeRef *mockRef = new MockNativeRef { reinterpret_cast<void *>(&wrappedNativeObj) };
{
EcmaHandleScope innerScope(thread);
ObjectFactory *factory = instance->GetFactory();
JSHandle<JSObject> wrappedObject = factory->CreateNapiObject(true);
JSObject::SetNativePointerFieldCount(thread, wrappedObject, 1);
JSObject::SetNativePointerField(thread, wrappedObject, 0,
reinterpret_cast<void *>(mockRef),
nullptr, nullptr);
instance->SetNativeReferenceDataGetter(
reinterpret_cast<ecmascript::NativeReferenceDataCallbackGetter>(
MockGetNativeReferenceData));
}
delete mockRef;
mockRef = nullptr;
std::stringstream nativeAddrSS;
nativeAddrSS << "0x" << std::hex << reinterpret_cast<uintptr_t>(&wrappedNativeObj);
std::string nativeAddrStr = nativeAddrSS.str();
std::string fileName = "test.heaptimeline";
fstream outputString(fileName, std::ios::out);
outputString.close();
outputString.clear();
FileStream stream(fileName.c_str());
TestProgress testProgress;
heapProfile->StopHeapTracking(&stream, &testProgress);
HeapProfilerInterface::Destroy(instance);
fstream inputStream(fileName, std::ios::in);
std::string line;
std::string emptyTraceFunctionInfo = "\"trace_function_infos\":[";
std::string firstTraceFunctionInfo = "\"trace_function_infos\":[0,";
uint32_t emptyTraceFunctionInfoSize = emptyTraceFunctionInfo.size();
bool traceFunctionInfoIsFind = false;
bool isWrappedFind = false;
bool isNativeAddrFind = false;
while (getline(inputStream, line)) {
if (line.substr(0U, emptyTraceFunctionInfoSize) == emptyTraceFunctionInfo) {
ASSERT_TRUE(line.substr(0, firstTraceFunctionInfo.size()) == firstTraceFunctionInfo);
traceFunctionInfoIsFind = true;
}
if (line.find("JSWrappedNapiObject") != std::string::npos) {
isWrappedFind = true;
}
if (line.find(nativeAddrStr) != std::string::npos) {
isNativeAddrFind = true;
}
}
ASSERT_TRUE(traceFunctionInfoIsFind);
ASSERT_FALSE(isWrappedFind) << "GC'd object must not appear in timeline snapshot";
ASSERT_FALSE(isNativeAddrFind) << "Deleted native ref address must not appear: " << nativeAddrStr;
inputStream.close();
inputStream.clear();
std::remove(fileName.c_str());
}
HWTEST_F_L0(HeapTrackerTest, HeapTrackerWithCreateJSProxy)
{
[[maybe_unused]] EcmaHandleScope handleScope(thread);
HeapProfilerInterface *heapProfiler = HeapProfilerInterface::GetInstance(instance);
bool result = heapProfiler->StartHeapTracking(50);
ASSERT_TRUE(result);
ObjectFactory *factory = instance->GetFactory();
for (int i = 0; i < 1000; i++) {
JSHandle<JSTaggedValue> target(factory->NewEmptyJSObject());
JSHandle<JSTaggedValue> handler(factory->NewEmptyJSObject());
factory->NewJSProxy(target, handler);
}
std::string fileName = "test_proxy.heaptimeline";
fstream outputString(fileName, std::ios::out);
outputString.close();
outputString.clear();
FileStream stream(fileName.c_str());
result = heapProfiler->StopHeapTracking(&stream, nullptr);
ASSERT_TRUE(result);
HeapProfilerInterface::Destroy(instance);
std::remove(fileName.c_str());
}
HWTEST_F_L0(HeapTrackerTest, DumpHeapSnapshot)
{
[[maybe_unused]] EcmaHandleScope handleScope(thread);
HeapProfilerInterface *heapProfile = HeapProfilerInterface::GetInstance(instance);
sleep(1);
int count = 100;
while (count-- > 0) {
instance->GetFactory()->NewJSAsyncFuncObject();
}
sleep(1);
count = 100;
while (count-- > 0) {
instance->GetFactory()->NewJSSymbol();
}
sleep(1);
count = 100;
while (count-- > 0) {
JSHandle<JSTaggedValue> undefined = instance->GetJSThread()->GlobalConstants()->GetHandledUndefined();
JSHandle<EcmaString> string = instance->GetFactory()->NewFromASCII("Hello World");
instance->GetFactory()->NewJSString(JSHandle<JSTaggedValue>(string), undefined);
}
std::string fileName = "HeapTrackerTest1.heapsnapshot";
fstream outputString(fileName, std::ios::out);
outputString.close();
outputString.clear();
FileStream stream(fileName.c_str());
DumpSnapShotOption dumpOption = { DumpFormat::JSON, true, true };
heapProfile->DumpHeapSnapshot(&stream, dumpOption);
HeapProfilerInterface::Destroy(instance);
fstream inputStream(fileName, std::ios::in);
std::string line;
std::string nodes = "\"nodes\":[";
std::string sample = "\"samples\":[]";
uint32_t nodesSize = nodes.size();
uint32_t sampleSize = sample.size();
bool isNodesFind = false;
bool isSampleFind = false;
while (getline(inputStream, line)) {
if (line.substr(0U, nodesSize) == nodes) {
isNodesFind = true;
}
if (line.substr(0U, sampleSize) == sample) {
isSampleFind = true;
}
}
ASSERT_TRUE(isNodesFind);
ASSERT_TRUE(isSampleFind);
inputStream.close();
inputStream.clear();
std::remove(fileName.c_str());
}
HWTEST_F_L0(HeapTrackerTest, HeapSnapshotBuildUp)
{
bool isVmMode = true;
bool isPrivate = false;
bool traceAllocation = false;
bool captureNumericValue = false;
HeapProfiler heapProfiler(instance);
DumpSnapShotOption dumpOption;
dumpOption.isVmMode = isVmMode;
dumpOption.isPrivate = isPrivate;
dumpOption.captureNumericValue = captureNumericValue;
HeapSnapshot heapSnapshot(instance, heapProfiler.GetEcmaStringTable(), dumpOption, traceAllocation,
heapProfiler.GetEntryIdMap());
EXPECT_TRUE(heapSnapshot.BuildUp());
}
HWTEST_F_L0(HeapTrackerTest, HeapSnapshotUpdateNode)
{
bool isVmMode = true;
bool isPrivate = false;
bool traceAllocation = false;
bool captureNumericValue = false;
HeapProfiler heapProfiler(instance);
DumpSnapShotOption dumpOption;
dumpOption.isVmMode = isVmMode;
dumpOption.isPrivate = isPrivate;
dumpOption.captureNumericValue = captureNumericValue;
HeapSnapshot heapSnapshot(instance, heapProfiler.GetEcmaStringTable(), dumpOption, traceAllocation,
heapProfiler.GetEntryIdMap());
size_t beginNode = heapSnapshot.GetNodeCount();
instance->GetHeap()->WaitAndHandleCCFinished();
heapSnapshot.UpdateNodes();
size_t endNode = heapSnapshot.GetNodeCount();
EXPECT_TRUE(beginNode != endNode);
}
}