* Copyright (c) 2023-2024 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 "ecmascript/pgo_profiler/pgo_profiler_info.h"
#include <iomanip>
#include "ecmascript/js_thread.h"
#include "ecmascript/ohos/framework_helper.h"
#include "ecmascript/pgo_profiler/pgo_profiler_manager.h"
#include "bytecode_instruction-inl.h"
#include "zlib.h"
namespace panda::ecmascript::pgo {
using StringHelper = base::StringHelper;
void PGOPandaFileInfos::ParseFromBinary(void *buffer, SectionInfo *const info)
{
void *addr = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(buffer) + info->offset_);
for (uint32_t i = 0; i < info->number_; i++) {
fileInfos_.emplace(*base::ReadBufferInSize<FileInfo>(&addr));
}
LOG_ECMA(DEBUG) << "Profiler panda file count:" << info->number_;
}
void PGOPandaFileInfos::ProcessToBinary(std::fstream &fileStream, SectionInfo *info) const
{
fileStream.seekp(info->offset_);
info->number_ = fileInfos_.size();
for (auto localInfo : fileInfos_) {
fileStream.write(reinterpret_cast<char *>(&localInfo), FileInfo::Size());
}
info->size_ = static_cast<uint32_t>(fileStream.tellp()) - info->offset_;
}
void PGOPandaFileInfos::Merge(const PGOPandaFileInfos &pandaFileInfos)
{
for (const auto &info : pandaFileInfos.fileInfos_) {
fileInfos_.emplace(info.GetChecksum(), info.GetAbcId());
}
}
void PGOPandaFileInfos::MergeSafe(const PGOPandaFileInfos& pandaFileInfos)
{
WriteLockHolder lock(fileInfosLock_);
Merge(pandaFileInfos);
}
bool PGOPandaFileInfos::VerifyChecksum(const PGOPandaFileInfos &pandaFileInfos, const std::string &base,
const std::string &incoming) const
{
std::set<FileInfo> unionChecksum;
set_union(fileInfos_.begin(), fileInfos_.end(), pandaFileInfos.fileInfos_.begin(), pandaFileInfos.fileInfos_.end(),
inserter(unionChecksum, unionChecksum.begin()));
if (!fileInfos_.empty() && unionChecksum.empty()) {
LOG_ECMA(ERROR) << "First AP file(" << base << ") and the incoming file(" << incoming
<< ") do not come from the same abc file, skip merge the incoming file.";
return false;
}
return true;
}
void PGOPandaFileInfos::ProcessToText(TextFormatter& fmt) const
{
fmt.Text("[Panda Files]").NewLine();
fmt.Indent()
.Right("ABC ID", TextFormatter::COL_WIDTH_ABC_ID)
.Pipe()
.Right("Checksum", TextFormatter::COL_WIDTH_CHECKSUM)
.NewLine();
fmt.Indent()
.Text(std::string(TextFormatter::COL_WIDTH_ABC_ID, '-'))
.Pipe()
.Text(std::string(TextFormatter::COL_WIDTH_CHECKSUM, '-'))
.NewLine();
for (auto &info : fileInfos_) {
fmt.Indent()
.Right(info.GetAbcId(), TextFormatter::COL_WIDTH_ABC_ID)
.Pipe()
.Right(TextFormatter::HexStr(info.GetChecksum()), TextFormatter::COL_WIDTH_CHECKSUM)
.NewLine();
}
fmt.NewLine();
}
bool PGOPandaFileInfos::Checksum(const std::unordered_map<CString, uint32_t>& fileNameToChecksumMap,
const std::shared_ptr<PGOAbcFilePool>& abcFilePool) const
{
for (const auto& fileNameToChecksumPair: fileNameToChecksumMap) {
ApEntityId abcId(0);
abcFilePool->GetEntryIdByNormalizedName(fileNameToChecksumPair.first, abcId);
FileInfo tempInfo = FileInfo(fileNameToChecksumPair.second, abcId);
auto it = fileInfos_.find(tempInfo);
if (it != fileInfos_.end()) {
if (it->GetChecksum() != tempInfo.GetChecksum()) {
LOG_ECMA(ERROR)
<< "Checksum verification failed. Please ensure that the "
".abc and .ap match. Fail file: "
<< fileNameToChecksumPair.first << "\n"
<< " compile file checksum: "
<< fileNameToChecksumPair.second
<< " recorded checksum in ap file: " << it->GetChecksum();
return false;
}
}
}
return true;
}
bool PGOPandaFileInfos::Checksum(const std::unordered_map<CString, uint32_t>& fileNameToChecksumMap) const
{
for (const auto& fileNameToChecksumPair: fileNameToChecksumMap) {
for (const auto &fileInfo : fileInfos_) {
if (fileInfo.GetChecksum() == fileNameToChecksumPair.second) {
return true;
}
}
}
LOG_ECMA(ERROR) << "Checksum verification failed. Please ensure that the .abc and .ap match.";
return false;
}
void PGOPandaFileInfos::UpdateFileInfosAbcID(const PGOContext &context)
{
std::set<FileInfo> newFileInfos;
auto oldToNewInfoMap = context.GetAbcIdRemap();
for (const auto &fileInfo : fileInfos_) {
auto changeInfo = oldToNewInfoMap.find(fileInfo.GetAbcId());
if (changeInfo != oldToNewInfoMap.end()) {
newFileInfos.emplace(fileInfo.GetChecksum(), changeInfo->second);
} else {
newFileInfos.emplace(fileInfo);
}
}
fileInfos_.swap(newFileInfos);
}
void PGOMethodInfo::ProcessToJson(ProfileType::VariantMap &function) const
{
std::string methodName = GetMethodName();
std::string functionName = methodName + "(" + std::to_string(GetMethodId().GetOffset()) + ")";
function.insert(std::make_pair(DumpJsonUtils::FUNCTION_NAME, functionName));
}
uint32_t PGOMethodInfo::CalcChecksum(const char *name, const uint8_t *byteCodeArray, uint32_t byteCodeLength)
{
uint32_t checksum = 0;
if (byteCodeArray != nullptr) {
checksum = CalcOpCodeChecksum(byteCodeArray, byteCodeLength);
}
if (name != nullptr) {
checksum = adler32(checksum, reinterpret_cast<const Bytef *>(name), strlen(name));
}
return checksum;
}
uint32_t PGOMethodInfo::CalcOpCodeChecksum(const uint8_t *byteCodeArray, uint32_t byteCodeLength)
{
uint32_t checksum = 0;
BytecodeInstruction bcIns(byteCodeArray);
auto bcInsLast = bcIns.JumpTo(byteCodeLength);
while (bcIns.GetAddress() != bcInsLast.GetAddress()) {
auto opCode = bcIns.GetOpcode();
checksum = adler32(checksum, reinterpret_cast<const Bytef *>(&opCode), sizeof(decltype(opCode)));
bcIns = bcIns.GetNext();
}
return checksum;
}
bool PGOMethodInfoMap::AddMethod(const JSThread *thread, Chunk *chunk, Method *jsMethod, SampleMode mode)
{
PGOMethodId methodId(jsMethod->GetMethodId());
auto result = methodInfos_.find(methodId);
if (result != methodInfos_.end()) {
auto info = result->second;
info->IncreaseCount();
info->SetSampleMode(mode);
return false;
} else {
CString methodName = jsMethod->GetMethodName(thread);
size_t strlen = methodName.size();
size_t size = static_cast<size_t>(PGOMethodInfo::Size(strlen));
void *infoAddr = chunk->Allocate(size);
if (infoAddr == nullptr) {
LOG_ECMA(ERROR) << "infoAddr is null!";
return false;
}
auto info = new (infoAddr) PGOMethodInfo(methodId, 0, mode, methodName.c_str());
info->IncreaseCount();
methodInfos_.emplace(methodId, info);
auto checksum = PGOMethodInfo::CalcChecksum(jsMethod->GetMethodName(thread), jsMethod->GetBytecodeArray(),
jsMethod->GetCodeSize(thread));
methodsChecksum_.emplace(methodId, checksum);
return true;
}
}
PGOMethodTypeSet *PGOMethodInfoMap::GetOrInsertMethodTypeSet(Chunk *chunk, PGOMethodId methodId)
{
auto typeInfoSetIter = methodTypeInfos_.find(methodId);
if (typeInfoSetIter != methodTypeInfos_.end()) {
return typeInfoSetIter->second;
} else {
auto typeInfoSet = chunk->New<PGOMethodTypeSet>();
methodTypeInfos_.emplace(methodId, typeInfoSet);
return typeInfoSet;
}
}
bool PGOMethodInfoMap::AddType(Chunk *chunk, PGOMethodId methodId, int32_t offset, PGOSampleType type)
{
auto typeInfoSet = GetOrInsertMethodTypeSet(chunk, methodId);
ASSERT(typeInfoSet != nullptr);
typeInfoSet->AddType(offset, type);
return true;
}
bool PGOMethodInfoMap::AddCallTargetType(Chunk *chunk, PGOMethodId methodId, int32_t offset, PGOSampleType type)
{
auto typeInfoSet = GetOrInsertMethodTypeSet(chunk, methodId);
ASSERT(typeInfoSet != nullptr);
typeInfoSet->AddCallTargetType(offset, type);
return true;
}
bool PGOMethodInfoMap::AddObjectInfo(Chunk *chunk, PGOMethodId methodId, int32_t offset, const PGOObjectInfo &info)
{
auto typeInfoSet = GetOrInsertMethodTypeSet(chunk, methodId);
ASSERT(typeInfoSet != nullptr);
typeInfoSet->AddObjectInfo(offset, info);
return true;
}
bool PGOMethodInfoMap::AddDefine(Chunk *chunk, PGOMethodId methodId, int32_t offset, PGODefineOpType type)
{
auto typeInfoSet = GetOrInsertMethodTypeSet(chunk, methodId);
ASSERT(typeInfoSet != nullptr);
typeInfoSet->AddDefine(offset, type);
return true;
}
void PGOMethodInfoMap::Merge(Chunk *chunk, PGOMethodInfoMap *methodInfos)
{
for (auto iter = methodInfos->methodInfos_.begin(); iter != methodInfos->methodInfos_.end(); iter++) {
auto methodId = iter->first;
auto fromMethodInfo = iter->second;
auto result = methodInfos_.find(methodId);
if (result != methodInfos_.end()) {
auto toMethodInfo = result->second;
toMethodInfo->Merge(fromMethodInfo);
} else {
size_t len = strlen(fromMethodInfo->GetMethodName());
size_t size = static_cast<size_t>(PGOMethodInfo::Size(len));
void *infoAddr = chunk->Allocate(size);
auto newMethodInfo = new (infoAddr) PGOMethodInfo(
methodId, fromMethodInfo->GetCount(), fromMethodInfo->GetSampleMode(), fromMethodInfo->GetMethodName());
methodInfos_.emplace(methodId, newMethodInfo);
}
fromMethodInfo->ClearCount();
}
for (auto iter = methodInfos->methodTypeInfos_.begin(); iter != methodInfos->methodTypeInfos_.end(); iter++) {
auto methodId = iter->first;
auto fromTypeInfo = iter->second;
auto result = methodTypeInfos_.find(methodId);
if (result != methodTypeInfos_.end()) {
auto toTypeInfo = result->second;
toTypeInfo->Merge(fromTypeInfo);
} else {
auto typeInfoSet = chunk->New<PGOMethodTypeSet>();
typeInfoSet->Merge(fromTypeInfo);
methodTypeInfos_.emplace(methodId, typeInfoSet);
}
}
for (auto iter = methodInfos->methodsChecksum_.begin(); iter != methodInfos->methodsChecksum_.end(); iter++) {
auto methodId = iter->first;
auto result = methodsChecksum_.find(methodId);
if (result == methodsChecksum_.end()) {
methodsChecksum_.emplace(methodId, iter->second);
}
}
}
bool PGOMethodInfoMap::SkipMethodFromBinary(PGOProfilerHeader* header,
void** addr,
void* buffer,
size_t bufferSize) const
{
if (header->SupportMethodChecksum()) {
base::ReadBuffer<uint32_t>(addr, sizeof(uint32_t));
if (!base::CheckBufferBounds(*addr, buffer, bufferSize, "SkipMethodChecksum")) {
return false;
}
}
if (header->SupportType()) {
PGOMethodTypeSet::SkipFromBinary(addr);
if (!base::CheckBufferBounds(*addr, buffer, bufferSize, "SkipPGOMethodTypeSet")) {
return false;
}
}
return true;
}
bool PGOMethodInfoMap::ParseMethodFromBinary(
Chunk* chunk, PGOContext& context, PGOMethodInfo* info, void** addr, void* buffer, size_t bufferSize)
{
PGOProfilerHeader* const header = context.GetHeader();
methodInfos_.emplace(info->GetMethodId(), info);
if (header->SupportMethodChecksum()) {
auto checksum = base::ReadBuffer<uint32_t>(addr, sizeof(uint32_t));
if (!base::CheckBufferBounds(*addr, buffer, bufferSize, "MethodChecksum")) {
return false;
}
methodsChecksum_.emplace(info->GetMethodId(), checksum);
}
if (header->SupportType()) {
auto typeInfoSet = chunk->New<PGOMethodTypeSet>();
size_t newSize = bufferSize - (reinterpret_cast<uintptr_t>(*addr) - reinterpret_cast<uintptr_t>(buffer));
if (!typeInfoSet->ParseFromBinary(context, addr, newSize)) {
return false;
}
methodTypeInfos_.emplace(info->GetMethodId(), typeInfoSet);
}
return true;
}
bool PGOMethodInfoMap::ParseFromBinary(Chunk* chunk, PGOContext& context, void** addr, size_t bufferSize)
{
PGOProfilerHeader* const header = context.GetHeader();
ASSERT(header != nullptr);
void* buffer = *addr;
SectionInfo secInfo = base::ReadBuffer<SectionInfo>(addr);
if (!base::CheckBufferBounds(*addr, buffer, bufferSize, "PGOMethodInfoMap")) {
return false;
}
for (uint32_t j = 0; j < secInfo.number_; j++) {
PGOMethodInfo* info = base::ReadBufferInSize<PGOMethodInfo>(addr);
if (!base::CheckBufferBounds(*addr, buffer, bufferSize, "PGOMethodInfo")) {
return false;
}
if (info->IsFilter(context.GetHotnessThreshold())) {
if (!SkipMethodFromBinary(header, addr, buffer, bufferSize)) {
return false;
}
continue;
}
if (!ParseMethodFromBinary(chunk, context, info, addr, buffer, bufferSize)) {
return false;
}
}
return true;
}
bool PGOMethodInfoMap::ProcessToBinary(PGOContext& context,
ProfileTypeRef recordProfileRef,
std::fstream& stream,
PGOProfilerHeader* const header) const
{
SectionInfo secInfo;
std::stringstream methodStream;
for (auto iter = methodInfos_.begin(); iter != methodInfos_.end(); iter++) {
auto curMethodInfo = iter->second;
if (curMethodInfo->IsFilter(context.GetHotnessThreshold())) {
continue;
}
methodStream.write(reinterpret_cast<char *>(curMethodInfo), curMethodInfo->Size());
if (header->SupportMethodChecksum()) {
auto checksumIter = methodsChecksum_.find(curMethodInfo->GetMethodId());
uint32_t checksum = 0;
if (checksumIter != methodsChecksum_.end()) {
checksum = checksumIter->second;
}
methodStream.write(reinterpret_cast<char *>(&checksum), sizeof(uint32_t));
}
if (header->SupportType()) {
auto typeInfoIter = methodTypeInfos_.find(curMethodInfo->GetMethodId());
if (typeInfoIter != methodTypeInfos_.end()) {
typeInfoIter->second->ProcessToBinary(context, methodStream);
} else {
uint32_t number = 0;
methodStream.write(reinterpret_cast<char *>(&number), sizeof(uint32_t));
}
}
secInfo.number_++;
}
if (secInfo.number_ > 0) {
secInfo.offset_ = sizeof(SectionInfo);
secInfo.size_ = static_cast<uint32_t>(methodStream.tellp());
stream.write(reinterpret_cast<char *>(&recordProfileRef), sizeof(uint32_t));
stream.write(reinterpret_cast<char *>(&secInfo), sizeof(SectionInfo));
stream << methodStream.rdbuf();
return true;
}
return false;
}
void PGOMethodInfoMap::ProcessToText(uint32_t threshold, const CString& recordName, TextFormatter& fmt) const
{
MethodStats stats = CollectStats(threshold);
fmt.SetLabelWidth(TextFormatter::LABEL_WIDTH_MEDIUM);
fmt.SectionLine().NewLine();
fmt.Indent().Label("Record").Value(recordName.c_str()).Pipe();
fmt.Label("Methods").Value(stats.hotMethods).Pipe();
fmt.Label("Total Calls").Value(stats.totalCalls).NewLine();
fmt.SectionLine().NewLine();
fmt.PushIndent();
for (auto methodInfoIter : methodInfos_) {
auto methodInfo = methodInfoIter.second;
if (methodInfo->IsFilter(threshold)) {
continue;
}
std::string checksumStr;
auto checksumIter = methodsChecksum_.find(methodInfo->GetMethodId());
if (checksumIter != methodsChecksum_.end()) {
checksumStr = TextFormatter::HexStr(checksumIter->second);
}
fmt.NewLine();
fmt.AutoIndent().Text("[Method]").Indent();
fmt.Label("Name").Value(methodInfo->GetMethodName()).Pipe();
fmt.Label("Method ID").Value(methodInfo->GetMethodId().GetOffset()).Pipe();
fmt.Label("Count").Value(methodInfo->GetCount()).Pipe();
fmt.Label("Mode").Value(methodInfo->GetSampleModeToString()).Pipe();
fmt.Label("Checksum").Value(checksumStr).NewLine();
auto iter = methodTypeInfos_.find(methodInfo->GetMethodId());
if (iter != methodTypeInfos_.end()) {
IndentScope indentScope(fmt);
fmt.AutoIndent().Text("[Types]");
iter->second->ProcessToText(fmt);
}
}
fmt.PopIndent();
fmt.NewLine();
}
PGOMethodInfoMap::MethodStats PGOMethodInfoMap::CollectStats(uint32_t threshold) const
{
MethodStats stats;
for (auto methodInfoIter: methodInfos_) {
auto methodInfo = methodInfoIter.second;
stats.totalMethods++;
uint32_t count = methodInfo->GetCount();
stats.totalCalls += count;
if (count > stats.maxCalls) {
stats.maxCalls = count;
}
if (!methodInfo->IsFilter(threshold)) {
stats.hotMethods++;
}
}
return stats;
}
void PGOMethodInfoMap::ProcessToJson(uint32_t threshold, ProfileType::jModuleType &jModule) const
{
std::vector<ProfileType::VariantMap> functionArray;
for (auto methodInfoIter : methodInfos_) {
auto methodInfo = methodInfoIter.second;
if (methodInfo->IsFilter(threshold)) {
continue;
}
ProfileType::VariantMap function;
methodInfo->ProcessToJson(function);
auto iter = methodTypeInfos_.find(methodInfo->GetMethodId());
if (iter != methodTypeInfos_.end()) {
ProfileType::VariantVector typeArray;
iter->second->ProcessToJson(typeArray);
function.insert(std::make_pair(DumpJsonUtils::TYPE, typeArray));
}
functionArray.push_back(function);
}
jModule.insert(std::make_pair(DumpJsonUtils::FUNCTION, functionArray));
}
bool PGOMethodIdSet::ParseFromBinary(PGOContext &context, void **buffer)
{
PGOProfilerHeader *const header = context.GetHeader();
ASSERT(header != nullptr);
SectionInfo secInfo = base::ReadBuffer<SectionInfo>(buffer);
for (uint32_t j = 0; j < secInfo.number_; j++) {
PGOMethodInfo *info = base::ReadBufferInSize<PGOMethodInfo>(buffer);
if (info->IsFilter(context.GetHotnessThreshold())) {
if (header->SupportMethodChecksum()) {
base::ReadBuffer<uint32_t>(buffer, sizeof(uint32_t));
}
if (header->SupportType()) {
PGOMethodTypeSet::SkipFromBinary(buffer);
}
continue;
}
uint32_t checksum = 0;
if (header->SupportMethodChecksum()) {
checksum = base::ReadBuffer<uint32_t>(buffer, sizeof(uint32_t));
}
auto ret = methodInfoMap_.try_emplace(info->GetMethodName(), chunk_);
auto methodNameSetIter = ret.first;
auto& methodInfo = methodNameSetIter->second.GetOrCreateMethodInfo(checksum, info->GetMethodId());
if (header->SupportType()) {
methodInfo.GetPGOMethodTypeSet().ParseFromBinary(context, buffer, PGOProfilerEncoder::MAX_AP_FILE_SIZE);
}
}
return !methodInfoMap_.empty();
}
void PGOMethodIdSet::GetMismatchResult(const CString &recordName, uint32_t &totalMethodCount,
uint32_t &mismatchMethodCount,
std::set<std::pair<std::string, CString>> &mismatchMethodSet) const
{
totalMethodCount += methodInfoMap_.size();
for (const auto &methodNameSet : methodInfoMap_) {
if (methodNameSet.second.IsMatch()) {
continue;
}
auto info = std::make_pair(methodNameSet.first, recordName);
mismatchMethodSet.emplace(info);
mismatchMethodCount++;
}
}
void PGOMethodIdSet::Merge(const PGOMethodIdSet &from)
{
for (const auto &methodNameSet : from.methodInfoMap_) {
auto iter = methodInfoMap_.find(methodNameSet.first);
if (iter == methodInfoMap_.end()) {
auto ret = methodInfoMap_.try_emplace(methodNameSet.first, chunk_);
iter = ret.first;
}
const_cast<PGOMethodNameSet &>(iter->second).Merge(methodNameSet.second);
}
}
void PGODecodeMethodInfo::Merge(const PGODecodeMethodInfo &from)
{
ASSERT(methodId_.IsValid() && from.methodId_.IsValid());
if (!(methodId_ == from.methodId_)) {
LOG_ECMA(ERROR) << "MethodId not match. " << methodId_ << " vs " << from.methodId_;
return;
}
pgoMethodTypeSet_.Merge(&from.pgoMethodTypeSet_);
}
PGORecordDetailInfos::PGORecordDetailInfos(uint32_t hotnessThreshold) : hotnessThreshold_(hotnessThreshold)
{
chunk_ = std::make_unique<Chunk>(&nativeAreaAllocator_);
InitSections();
};
PGORecordDetailInfos::~PGORecordDetailInfos()
{
Clear();
}
PGOMethodInfoMap *PGORecordDetailInfos::GetMethodInfoMap(ProfileType recordProfileType)
{
auto iter = recordInfos_.find(recordProfileType);
if (iter != recordInfos_.end()) {
return iter->second;
} else {
auto curMethodInfos = nativeAreaAllocator_.New<PGOMethodInfoMap>();
recordInfos_.emplace(recordProfileType, curMethodInfos);
return curMethodInfos;
}
}
bool PGORecordDetailInfos::AddMethod(const JSThread *thread, ProfileType recordProfileType, Method *jsMethod,
SampleMode mode)
{
auto curMethodInfos = GetMethodInfoMap(recordProfileType);
ASSERT(curMethodInfos != nullptr);
ASSERT(jsMethod != nullptr);
return curMethodInfos->AddMethod(thread, chunk_.get(), jsMethod, mode);
}
bool PGORecordDetailInfos::AddType(ProfileType recordProfileType, PGOMethodId methodId, int32_t offset,
PGOSampleType type)
{
auto curMethodInfos = GetMethodInfoMap(recordProfileType);
ASSERT(curMethodInfos != nullptr);
return curMethodInfos->AddType(chunk_.get(), methodId, offset, type);
}
bool PGORecordDetailInfos::AddCallTargetType(ProfileType recordProfileType, PGOMethodId methodId, int32_t offset,
PGOSampleType type)
{
auto curMethodInfos = GetMethodInfoMap(recordProfileType);
ASSERT(curMethodInfos != nullptr);
return curMethodInfos->AddCallTargetType(chunk_.get(), methodId, offset, type);
}
bool PGORecordDetailInfos::AddObjectInfo(
ProfileType recordProfileType, EntityId methodId, int32_t offset, const PGOObjectInfo &info)
{
auto curMethodInfos = GetMethodInfoMap(recordProfileType);
ASSERT(curMethodInfos != nullptr);
return curMethodInfos->AddObjectInfo(chunk_.get(), methodId, offset, info);
}
bool PGORecordDetailInfos::AddDefine(
ProfileType recordProfileType, PGOMethodId methodId, int32_t offset, PGODefineOpType type)
{
auto curMethodInfos = GetMethodInfoMap(recordProfileType);
ASSERT(curMethodInfos != nullptr);
curMethodInfos->AddDefine(chunk_.get(), methodId, offset, type);
PGOHClassTreeDesc descInfo(type.GetProfileType());
auto iter = hclassTreeDescInfos_.find(descInfo);
if (iter == hclassTreeDescInfos_.end()) {
descInfo.SetProtoPt(type.GetPrototypePt());
hclassTreeDescInfos_.emplace(descInfo);
} else {
const_cast<PGOHClassTreeDesc &>(*iter).SetProtoPt(type.GetPrototypePt());
}
return true;
}
bool PGORecordDetailInfos::AddRootLayout(const JSThread *thread, JSTaggedType hclass, ProfileType rootType)
{
PGOHClassTreeDesc descInfo(rootType);
auto iter = hclassTreeDescInfos_.find(descInfo);
if (iter != hclassTreeDescInfos_.end()) {
return const_cast<PGOHClassTreeDesc &>(*iter).DumpForRoot(thread, hclass, rootType);
} else {
if (!descInfo.DumpForRoot(thread, hclass, rootType)) {
return false;
}
hclassTreeDescInfos_.emplace(descInfo);
}
return true;
}
bool PGORecordDetailInfos::UpdateLayout(const JSThread *thread,
ProfileType rootType, JSTaggedType hclass, ProfileType curType)
{
PGOHClassTreeDesc descInfo(rootType);
auto iter = hclassTreeDescInfos_.find(descInfo);
if (iter != hclassTreeDescInfos_.end()) {
return const_cast<PGOHClassTreeDesc &>(*iter).UpdateLayout(thread, hclass, curType);
} else {
if (!descInfo.UpdateLayout(thread, hclass, curType)) {
return false;
}
hclassTreeDescInfos_.emplace(descInfo);
return false;
}
return true;
}
bool PGORecordDetailInfos::UpdateTransitionLayout(const JSThread *thread,
ProfileType rootType, JSTaggedType parent, ProfileType parentType, JSTaggedType child, ProfileType childType)
{
PGOHClassTreeDesc descInfo(rootType);
auto iter = hclassTreeDescInfos_.find(descInfo);
if (iter != hclassTreeDescInfos_.end()) {
return const_cast<PGOHClassTreeDesc &>(*iter).UpdateForTransition(thread, parent, parentType,
child, childType);
} else {
if (!descInfo.UpdateForTransition(thread, parent, parentType, child, childType)) {
return false;
}
hclassTreeDescInfos_.emplace(descInfo);
}
return true;
}
void PGORecordDetailInfos::AddRootPtType(ProfileType rootType, ProfileType ptType)
{
PGOHClassTreeDesc descInfo(rootType);
auto iter = hclassTreeDescInfos_.find(descInfo);
if (iter != hclassTreeDescInfos_.end()) {
const_cast<PGOHClassTreeDesc &>(*iter).SetProtoPt(ptType);
} else {
descInfo.SetProtoPt(ptType);
hclassTreeDescInfos_.emplace(descInfo);
}
}
bool PGORecordDetailInfos::IsDumped(ProfileType rootType, ProfileType curType) const
{
PGOHClassTreeDesc descInfo(rootType);
auto iter = hclassTreeDescInfos_.find(descInfo);
if (iter != hclassTreeDescInfos_.end()) {
return const_cast<PGOHClassTreeDesc &>(*iter).IsDumped(curType);
}
return false;
}
void PGORecordDetailInfos::Merge(const PGORecordDetailInfos &recordInfos)
{
const auto& methodInfos = recordInfos.recordInfos_;
for (auto& iter: methodInfos) {
auto recordType = iter.first;
auto fromMethodInfos = iter.second;
auto recordInfosIter = recordInfos_.find(recordType);
PGOMethodInfoMap *toMethodInfos = nullptr;
if (recordInfosIter == recordInfos_.end()) {
toMethodInfos = nativeAreaAllocator_.New<PGOMethodInfoMap>();
recordInfos_.emplace(recordType, toMethodInfos);
} else {
toMethodInfos = recordInfosIter->second;
}
ASSERT(toMethodInfos != nullptr);
toMethodInfos->Merge(chunk_.get(), fromMethodInfos);
}
recordPool_->Merge(*recordInfos.recordPool_);
protoTransitionPool_->Merge(*recordInfos.protoTransitionPool_);
const auto& hclassTreeDescInfos = recordInfos.hclassTreeDescInfos_;
for (auto& fromInfo: hclassTreeDescInfos) {
auto result = hclassTreeDescInfos_.find(fromInfo);
if (result == hclassTreeDescInfos_.end()) {
PGOHClassTreeDesc descInfo(fromInfo.GetProfileType());
descInfo.SetProtoPt(fromInfo.GetProtoPt());
descInfo.Merge(fromInfo);
hclassTreeDescInfos_.emplace(descInfo);
} else {
const_cast<PGOHClassTreeDesc &>(*result).Merge(fromInfo);
}
}
}
void PGORecordDetailInfos::MergeSafe(const PGORecordDetailInfos& recordInfos)
{
LockHolder lock(mutex_);
Merge(recordInfos);
}
bool PGORecordDetailInfos::ParseSectionsFromBinary(void* buffer, PGOProfilerHeader* const header)
{
PGOFileSectionInterface::ParseSectionFromBinary(*this, buffer, header, *profileTypePool_->GetPool());
if (!abcIdRemap_.empty()) {
LOG_ECMA(DEBUG) << "remap with abcRemapSize: " << abcIdRemap_.size();
profileTypePool_->Remap(*this);
}
PGOFileSectionInterface::ParseSectionFromBinary(*this, buffer, header, *protoTransitionPool_);
PGOFileSectionInterface::ParseSectionFromBinary(*this, buffer, header, *recordPool_);
return true;
}
bool PGORecordDetailInfos::ParseRecordTypeFromBinary(PGOProfilerHeader* header,
void** addr,
void* buffer,
size_t bufferSize,
ApEntityId& recordId,
ProfileType& recordType)
{
if (header->SupportProfileTypeWithAbcId()) {
auto recordTypeRef = ProfileTypeRef(base::ReadBuffer<ApEntityId>(addr, sizeof(ApEntityId)));
if (!base::CheckBufferBounds(*addr, buffer, bufferSize, "ProfileTypeRef")) {
return false;
}
auto res = ProfileType::CreateFromProfileTypeRef(*this, recordTypeRef);
if (!res.has_value()) {
LOG_ECMA(ERROR) << "ParseFromBinary failed, current addr: " << *addr << std::endl;
return false;
}
recordType = res.value();
recordId = recordType.GetId();
} else if (header->SupportRecordPool()) {
recordId = base::ReadBuffer<ApEntityId>(addr, sizeof(ApEntityId));
if (!base::CheckBufferBounds(*addr, buffer, bufferSize, "recordId")) {
return false;
}
} else {
auto* recordName = base::ReadBuffer(addr);
if (!base::CheckBufferBounds(*addr, buffer, bufferSize, "recordName")) {
return false;
}
recordPool_->Add(ProfileType(recordId), recordName);
}
recordType.UpdateId(recordId);
recordType.UpdateKind(ProfileType::Kind::RecordClassId);
return true;
}
bool PGORecordDetailInfos::ParseRecordInfosFromBinary(void* buffer, PGOProfilerHeader* header, size_t bufferSize)
{
SectionInfo* info = header->GetRecordInfoSection();
if (info == nullptr) {
LOG_PGO(ERROR) << "[ParseRecordInfosFromBinary] section info is nullptr";
return false;
}
void* addr = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(buffer) + info->offset_);
for (uint32_t i = 0; i < info->number_; i++) {
ApEntityId recordId(0);
ProfileType recordType;
if (!ParseRecordTypeFromBinary(header, &addr, buffer, bufferSize, recordId, recordType)) {
return false;
}
PGOMethodInfoMap *methodInfos = nativeAreaAllocator_.New<PGOMethodInfoMap>();
ASSERT(methodInfos != nullptr);
size_t newSize = bufferSize - (reinterpret_cast<uintptr_t>(addr) - reinterpret_cast<uintptr_t>(buffer));
if (!methodInfos->ParseFromBinary(chunk_.get(), *this, &addr, newSize)) {
return false;
}
if (!methodInfos->GetMethodInfos().empty()) {
recordInfos_.emplace(recordType, methodInfos);
} else {
nativeAreaAllocator_.Delete(methodInfos);
}
}
return true;
}
bool PGORecordDetailInfos::ParseFromBinary(void* buffer, PGOProfilerHeader* const header, size_t bufferSize)
{
header_ = header;
if (!ParseSectionsFromBinary(buffer, header)) {
return false;
}
if (!ParseRecordInfosFromBinary(buffer, header, bufferSize)) {
return false;
}
SectionInfo* info = header->GetLayoutDescSection();
if (info == nullptr) {
return false;
}
if (header->SupportTrackField()) {
void* addr = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(buffer) + info->offset_);
if (!ParseFromBinaryForLayout(&addr, buffer, bufferSize)) {
return false;
}
}
return true;
}
bool PGORecordDetailInfos::ParseFromBinaryForLayout(void** addr, void* buffer, size_t bufferSize)
{
SectionInfo secInfo = base::ReadBuffer<SectionInfo>(addr);
if (!base::CheckBufferBounds(*addr, buffer, bufferSize, "ParseFromBinaryForLayout")) {
return false;
}
for (uint32_t i = 0; i < secInfo.number_; i++) {
auto* info = base::ReadBufferInSize<PGOHClassTreeDescInnerRef>(addr);
if (!base::CheckBufferBounds(*addr, buffer, bufferSize, "PGOHClassTreeDescInnerRef")) {
return false;
}
if (info == nullptr) {
LOG_ECMA(INFO) << "Binary format error!";
continue;
}
hclassTreeDescInfos_.emplace(info->Convert(*this));
}
return true;
}
void PGORecordDetailInfos::ProcessToBinary(std::fstream& fileStream, PGOProfilerHeader* const header)
{
header_ = header;
auto info = header->GetRecordInfoSection();
info->number_ = 0;
info->offset_ = static_cast<uint32_t>(fileStream.tellp());
for (auto iter = recordInfos_.begin(); iter != recordInfos_.end(); iter++) {
auto recordId = iter->first;
auto curMethodInfos = iter->second;
if (curMethodInfos->ProcessToBinary(*this, ProfileTypeRef(*this, recordId), fileStream, header)) {
info->number_++;
}
}
info->size_ = static_cast<uint32_t>(fileStream.tellp()) - info->offset_;
info = header->GetLayoutDescSection();
if (info == nullptr) {
return;
}
info->number_ = 0;
info->offset_ = static_cast<uint32_t>(fileStream.tellp());
if (header->SupportType()) {
if (!ProcessToBinaryForLayout(const_cast<NativeAreaAllocator*>(&nativeAreaAllocator_), fileStream)) {
return;
}
info->number_++;
}
info->size_ = static_cast<uint32_t>(fileStream.tellp()) - info->offset_;
PGOFileSectionInterface::ProcessSectionToBinary(*this, fileStream, header, *recordPool_);
PGOFileSectionInterface::ProcessSectionToBinary(*this, fileStream, header, *protoTransitionPool_);
PGOFileSectionInterface::ProcessSectionToBinary(*this, fileStream, header, *profileTypePool_->GetPool());
}
bool PGORecordDetailInfos::ProcessToBinaryForLayout(NativeAreaAllocator* allocator, std::fstream& stream)
{
SectionInfo secInfo;
auto layoutBeginPosition = stream.tellp();
stream.seekp(sizeof(SectionInfo), std::ofstream::cur);
for (const auto& typeInfo: hclassTreeDescInfos_) {
auto profileType = PGOSampleType(typeInfo.GetProfileType());
size_t size = PGOHClassTreeDescInnerRef::CaculateSize(typeInfo);
if (size == 0) {
continue;
}
PGOSampleTypeRef classRef = PGOSampleTypeRef::ConvertFrom(*this, profileType);
auto protoSt = PGOSampleType(typeInfo.GetProtoPt());
PGOSampleTypeRef protoClassRef = PGOSampleTypeRef::ConvertFrom(*this, protoSt);
void *addr = allocator->Allocate(size);
auto descInfos = new (addr) PGOHClassTreeDescInnerRef(size, classRef, protoClassRef);
descInfos->Merge(typeInfo);
stream.write(reinterpret_cast<char *>(descInfos), size);
allocator->Delete(addr);
secInfo.number_++;
}
secInfo.offset_ = sizeof(SectionInfo);
secInfo.size_ = static_cast<uint32_t>(stream.tellp()) -
static_cast<uint32_t>(layoutBeginPosition) - sizeof(SectionInfo);
stream.seekp(layoutBeginPosition, std::ofstream::beg)
.write(reinterpret_cast<char *>(&secInfo), sizeof(SectionInfo))
.seekp(0, std::ofstream::end);
return true;
}
PGORecordDetailInfos::OverallStats PGORecordDetailInfos::CollectOverallStats() const
{
OverallStats stats;
for (auto iter = recordInfos_.begin(); iter != recordInfos_.end(); iter++) {
stats.totalRecords++;
auto methodStats = iter->second->CollectStats(hotnessThreshold_);
stats.totalMethods += methodStats.totalMethods;
stats.hotMethods += methodStats.hotMethods;
stats.totalCalls += methodStats.totalCalls;
if (methodStats.maxCalls > stats.maxCalls) {
stats.maxCalls = methodStats.maxCalls;
}
}
return stats;
}
void PGORecordDetailInfos::ProcessToText(TextFormatter& fmt) const
{
OverallStats stats = CollectOverallStats();
fmt.SectionLine().NewLine();
fmt.CenteredTitle("Methods Summary").NewLine();
fmt.SectionLine().NewLine();
fmt.SetLabelWidth(TextFormatter::LABEL_WIDTH_LARGE).LabelAlign();
fmt.Indent().Label("Total Records", true).Value(stats.totalRecords).NewLine();
fmt.Indent().Label("Total Methods", true).Value(stats.totalMethods).NewLine();
fmt.Indent()
.Label("Hotness Methods", true)
.Value(std::to_string(stats.hotMethods) + " (threshold: " + std::to_string(hotnessThreshold_) + ")")
.NewLine();
fmt.Indent().Label("Total Call Count", true).Value(stats.totalCalls).NewLine();
if (stats.totalMethods > 0) {
fmt.Indent().Label("Average Calls", true).Fixed(stats.GetAverageCalls()).NewLine();
}
fmt.Indent().Label("Max Calls", true).Value(stats.maxCalls).NewLine();
fmt.NewLine();
fmt.LabelReset();
if (!hclassTreeDescInfos_.empty()) {
fmt.SectionLine().NewLine();
fmt.CenteredTitle("HClass Tree Desc").NewLine();
fmt.SectionLine().NewLine();
for (auto layoutInfoIter: hclassTreeDescInfos_) {
fmt.Indent().Text(PGOHClassTreeDescInner::GetTypeString(layoutInfoIter)).NewLine();
}
fmt.NewLine();
}
for (auto iter = recordInfos_.begin(); iter != recordInfos_.end(); iter++) {
const CString recordName(recordPool_->GetName(iter->first));
if (recordName.empty()) {
LOG_ECMA(ERROR) << "record name is empty, " << iter->first.GetTypeString();
continue;
}
auto methodInfos = iter->second;
methodInfos->ProcessToText(hotnessThreshold_, recordName, fmt);
}
recordPool_->ProcessToText(fmt);
protoTransitionPool_->ProcessToText(fmt);
profileTypePool_->GetPool()->ProcessToText(fmt);
}
void PGORecordDetailInfos::InitSections()
{
recordPool_ = std::make_unique<PGORecordPool>();
protoTransitionPool_ = std::make_unique<PGOProtoTransitionPool>();
profileTypePool_ = std::make_unique<PGOProfileTypePool>();
}
void PGORecordDetailInfos::Clear()
{
for (auto iter : recordInfos_) {
iter.second->Clear();
nativeAreaAllocator_.Delete(iter.second);
}
for (auto iter : hclassTreeDescInfos_) {
iter.Clear();
}
hclassTreeDescInfos_.clear();
recordInfos_.clear();
recordPool_->Clear();
protoTransitionPool_->Clear();
profileTypePool_->Clear();
hclassTreeDescInfos_.clear();
abcIdRemap_.clear();
chunk_ = std::make_unique<Chunk>(&nativeAreaAllocator_);
InitSections();
}
void PGORecordDetailInfos::ClearSafe()
{
LockHolder lock(mutex_);
Clear();
}
bool PGORecordSimpleInfos::Match(const CString &abcNormalizedDesc, const CString &recordName, EntityId methodId)
{
auto abcMethodIds = methodIds_.find(abcNormalizedDesc);
if (abcMethodIds == methodIds_.end()) {
LOG_COMPILER(DEBUG) << "AbcDesc not found. abcNormalizedDesc: " << abcNormalizedDesc
<< ", methodIdsCount: " << methodIds_.size();
return false;
}
auto methodIdsIter = abcMethodIds->second.find(recordName);
if (methodIdsIter == abcMethodIds->second.end()) {
LOG_COMPILER(DEBUG) << "AbcDesc not found. recordName: " << recordName;
return false;
}
return methodIdsIter->second->Match(methodId);
}
void PGORecordSimpleInfos::ParseFromBinary(void *buffer, PGOProfilerHeader *const header,
std::shared_ptr<PGOAbcFilePool> &abcFilePool)
{
header_ = header;
if (!PGOFileSectionInterface::ParseSectionFromBinary(*this, buffer, header, *profileTypePool_->GetPool())) {
LOG_ECMA(ERROR) << "Parse from binary failed for profile type pool.";
return;
}
if (!abcIdRemap_.empty()) {
LOG_ECMA(DEBUG) << "remap with abcRemapSize: " << abcIdRemap_.size();
profileTypePool_->Remap(*this);
}
if (!PGOFileSectionInterface::ParseSectionFromBinary(*this, buffer, header, *protoTransitionPool_)) {
LOG_ECMA(ERROR) << "Parse from binary failed for proto transition pool.";
return;
}
if (!PGOFileSectionInterface::ParseSectionFromBinary(*this, buffer, header, *recordPool_)) {
LOG_ECMA(ERROR) << "Parse from binary failed for record pool.";
return;
}
SectionInfo *info = header->GetRecordInfoSection();
void *addr = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(buffer) + info->offset_);
for (uint32_t i = 0; i < info->number_; i++) {
CString recordName;
const char *abcDesc = "";
ProfileType recordType;
if (header->SupportProfileTypeWithAbcId()) {
auto recordTypeRef = ProfileTypeRef(base::ReadBuffer<ApEntityId>(&addr, sizeof(ApEntityId)));
recordType = ProfileType(*this, recordTypeRef);
recordName = recordPool_->GetName(recordType);
auto abcId = recordType.GetAbcId();
const auto *entry = abcFilePool->GetPool()->GetEntry(abcId);
if (entry != nullptr) {
abcDesc = entry->GetData().c_str();
}
} else if (header->SupportRecordPool()) {
auto recordId = base::ReadBuffer<ApEntityId>(&addr, sizeof(ApEntityId));
recordName = recordPool_->GetName(ProfileType(recordId));
} else {
recordName = base::ReadBuffer(&addr);
}
PGOMethodIdSet *methodIds = nativeAreaAllocator_.New<PGOMethodIdSet>(chunk_.get());
if (methodIds->ParseFromBinary(*this, &addr)) {
auto methodIdsResult = methodIds_.try_emplace(JSPandaFile::GetNormalizedFileDesc(abcDesc));
FrameworkHelper::GetRealRecordName(recordName);
(methodIdsResult.first->second).emplace(recordName, methodIds);
} else {
nativeAreaAllocator_.Delete(methodIds);
}
}
info = header->GetLayoutDescSection();
if (info == nullptr) {
return;
}
if (header->SupportTrackField()) {
ParseFromBinaryForLayout(&addr);
}
}
void PGORecordSimpleInfos::Merge(const PGORecordSimpleInfos &simpleInfos)
{
for (const auto &fromAbcMethodIds : simpleInfos.methodIds_) {
auto toAbcMethodIds = methodIds_.try_emplace(fromAbcMethodIds.first);
for (const auto &method : fromAbcMethodIds.second) {
auto result = toAbcMethodIds.first->second.find(method.first);
if (result == toAbcMethodIds.first->second.end()) {
PGOMethodIdSet *methodIds = nativeAreaAllocator_.New<PGOMethodIdSet>(chunk_.get());
auto ret = toAbcMethodIds.first->second.emplace(method.first, methodIds);
ASSERT(ret.second);
result = ret.first;
}
const_cast<PGOMethodIdSet &>(*result->second).Merge(*method.second);
}
}
recordPool_->Merge(*simpleInfos.recordPool_);
protoTransitionPool_->Merge(*simpleInfos.protoTransitionPool_);
for (const auto &hclassTreeDescInfo : simpleInfos.hclassTreeDescInfos_) {
auto result = hclassTreeDescInfos_.find(hclassTreeDescInfo);
if (result == hclassTreeDescInfos_.end()) {
PGOHClassTreeDesc descInfo(hclassTreeDescInfo.GetProfileType());
descInfo.SetProtoPt(hclassTreeDescInfo.GetProtoPt());
descInfo.Merge(hclassTreeDescInfo);
hclassTreeDescInfos_.emplace(descInfo);
} else {
const_cast<PGOHClassTreeDesc &>(*result).Merge(hclassTreeDescInfo);
}
}
}
bool PGORecordSimpleInfos::ParseFromBinaryForLayout(void **buffer)
{
SectionInfo secInfo = base::ReadBuffer<SectionInfo>(buffer);
for (uint32_t i = 0; i < secInfo.number_; i++) {
auto *info = base::ReadBufferInSize<PGOHClassTreeDescInnerRef>(buffer);
if (info == nullptr) {
LOG_ECMA(INFO) << "Binary format error!";
continue;
}
hclassTreeDescInfos_.emplace(info->Convert(*this));
}
return true;
}
void PGORecordSimpleInfos::InitSections()
{
recordPool_ = std::make_unique<PGORecordPool>();
protoTransitionPool_ = std::make_unique<PGOProtoTransitionPool>();
profileTypePool_ = std::make_unique<PGOProfileTypePool>();
}
void PGORecordSimpleInfos::Clear()
{
for (const auto &abcMethodIds: methodIds_) {
for (const auto &iter : abcMethodIds.second) {
iter.second->Clear();
nativeAreaAllocator_.Delete(iter.second);
}
}
for (auto iter : hclassTreeDescInfos_) {
iter.Clear();
}
hclassTreeDescInfos_.clear();
methodIds_.clear();
recordPool_->Clear();
profileTypePool_->Clear();
hclassTreeDescInfos_.clear();
abcIdRemap_.clear();
chunk_ = std::make_unique<Chunk>(&nativeAreaAllocator_);
InitSections();
}
PGORecordSimpleInfos::PGORecordSimpleInfos(uint32_t threshold) : hotnessThreshold_(threshold)
{
chunk_ = std::make_unique<Chunk>(&nativeAreaAllocator_);
InitSections();
}
PGORecordSimpleInfos::~PGORecordSimpleInfos()
{
Clear();
}
}