* Copyright (c) 2025 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 "stack_printer.h"
#include <iostream>
#include <mutex>
#include <sstream>
#include <sys/prctl.h>
#include "dfx_frame.h"
#include "dfx_frame_formatter.h"
#include "unique_stack_table.h"
namespace OHOS {
namespace HiviewDFX {
namespace {
constexpr uint64_t SEC_TO_NANOSEC = 1000000000;
constexpr uint64_t MICROSEC_TO_NANOSEC = 1000;
constexpr int FORMAT_TIME_LEN = 20;
constexpr int MICROSEC_LEN = 6;
constexpr size_t MAX_SAMPLE_TIDS = 10;
constexpr int MAX_TID_VALUE = 65536;
}
struct StackRecord {
uint64_t stackId {0};
std::vector<uint64_t> snapshotTimes;
};
struct StackItem {
uintptr_t pc {0};
int32_t pcCount {0};
uint32_t level {0};
uint64_t stackId {0};
std::shared_ptr<DfxFrame> current {nullptr};
std::shared_ptr<StackItem> child {nullptr};
std::shared_ptr<StackItem> siblings {nullptr};
};
std::ostream& operator<<(std::ostream& os, const SampledFrame& frame)
{
os << frame.indent << " " << frame.count << " " << frame.level << " " << frame.pc << " " << frame.isLeaf << " " <<
frame.timestamps.size();
for (auto timestamp : frame.timestamps) {
os << " " << timestamp;
}
return os;
}
std::istream& operator>>(std::istream& is, SampledFrame& frame)
{
size_t timestampsSize;
is >> frame.indent >> frame.count >> frame.level >> frame.pc >> frame.isLeaf >> timestampsSize;
if (timestampsSize > 0) {
frame.timestamps.resize(timestampsSize);
for (size_t i = 0; i < timestampsSize; i++) {
is >> frame.timestamps[i];
}
}
return is;
}
static std::string TimeFormat(uint64_t time)
{
uint64_t nsec = time % SEC_TO_NANOSEC;
time_t sec = static_cast<time_t>(time / SEC_TO_NANOSEC);
char timeChars[FORMAT_TIME_LEN];
struct tm localTime;
if (localtime_r(&sec, &localTime) == nullptr) {
return "";
}
size_t sz = strftime(timeChars, FORMAT_TIME_LEN, "%Y-%m-%d-%H-%M-%S", &localTime);
if (sz == 0) {
return "";
}
std::string s = timeChars;
uint64_t usec = nsec / MICROSEC_TO_NANOSEC;
std::string usecStr = std::to_string(usec);
while (usecStr.size() < MICROSEC_LEN) {
usecStr = "0" + usecStr;
}
s = s + "." + usecStr;
return s;
}
static std::vector<StackRecord> TimeFilter(const std::vector<StackRecord>& stackRecordVec, uint64_t beginTime,
uint64_t endTime)
{
if ((beginTime == 0 && endTime == 0) || (beginTime > endTime)) {
return stackRecordVec;
}
std::vector<StackRecord> vec;
for (const auto& stackRecord : stackRecordVec) {
StackRecord record;
for (auto t : stackRecord.snapshotTimes) {
if (t >= beginTime && t < endTime) {
record.snapshotTimes.emplace_back(t);
}
}
if (!record.snapshotTimes.empty()) {
record.stackId = stackRecord.stackId;
vec.emplace_back(std::move(record));
}
}
return vec;
}
class StackPrinter::Impl {
public:
Impl() : root_(nullptr)
{}
inline void SetUnwindInfo(const std::shared_ptr<Unwinder>& unwinder, const std::shared_ptr<DfxMaps>& maps)
{
unwinder_ = unwinder;
maps_ = maps;
}
bool InitUniqueTable(pid_t pid, uint32_t size, std::string name = "unique_stack_table");
bool PutPcsInTable(const std::vector<uintptr_t>& pcs, int tid, uint64_t snapshotTime);
std::string GetFullStack(const std::vector<TimeStampedPcs>& timeStampedPcsVec);
std::map<int, std::vector<SampledFrame>> GetThreadSampledFrames(uint64_t beginTime = 0, uint64_t endTime = 0);
std::string GetTreeStack(int tid, bool printTimes = false, uint64_t beginTime = 0, uint64_t endTime = 0);
std::string GetHeaviestStack(int tid, uint64_t beginTime = 0, uint64_t endTime = 0);
static std::string PrintTreeStackBySampledStack(const std::vector<SampledFrame>& sampledFrameVec, bool printTimes,
const std::shared_ptr<Unwinder>& unwinder,
const std::shared_ptr<DfxMaps>& maps);
static void SerializeSampledFrameMap(const std::map<int, std::vector<SampledFrame>>& sampledFrameMap,
std::ostream& os);
static std::map<int, std::vector<SampledFrame>> DeserializeSampledFrameMap(std::istream& is);
private:
void Insert(const std::vector<uintptr_t>& pcs, int32_t count, StackId stackId);
std::shared_ptr<StackItem> InsertImpl(std::shared_ptr<StackItem> curNode, uintptr_t pc, int32_t count,
uint64_t level, std::shared_ptr<StackItem> acientNode);
std::shared_ptr<StackItem> AdjustSiblings(std::shared_ptr<StackItem> acient, std::shared_ptr<StackItem> cur,
std::shared_ptr<StackItem> node);
void BuildStackTree(std::vector<StackRecord>& stackRecordVec);
std::vector<SampledFrame> GenerateTreeStackFrames(const std::vector<StackRecord>& stackRecordVec);
std::vector<SampledFrame> GetSampledFramesByTid(int tid, uint64_t beginTime, uint64_t endTime);
static void PrintTimesStr(std::string& frameStr, const std::vector<uint64_t>& snapshotTimes);
static std::string PrintStackByPcs(const std::vector<uintptr_t>& pcs, const std::shared_ptr<Unwinder>& unwinder,
const std::shared_ptr<DfxMaps>& maps);
std::shared_ptr<StackItem> root_;
std::shared_ptr<Unwinder> unwinder_ {nullptr};
std::shared_ptr<DfxMaps> maps_ {nullptr};
std::unique_ptr<UniqueStackTable> uniqueStackTable_ {nullptr};
std::map<int, std::vector<StackRecord>> tidStackRecordMap_;
std::mutex mutex_;
};
StackPrinter::StackPrinter() : impl_(std::make_shared<Impl>())
{}
void StackPrinter::SetUnwindInfo(const std::shared_ptr<Unwinder>& unwinder, const std::shared_ptr<DfxMaps>& maps)
{
impl_->SetUnwindInfo(unwinder, maps);
}
bool StackPrinter::PutPcsInTable(const std::vector<uintptr_t>& pcs, int tid, uint64_t snapshotTime)
{
return impl_->PutPcsInTable(pcs, tid, snapshotTime);
}
std::string StackPrinter::GetFullStack(const std::vector<TimeStampedPcs>& timeStampedPcsVec)
{
return impl_->GetFullStack(timeStampedPcsVec);
}
std::map<int, std::vector<SampledFrame>> StackPrinter::GetThreadSampledFrames(uint64_t beginTime, uint64_t endTime)
{
return impl_->GetThreadSampledFrames(beginTime, endTime);
}
std::string StackPrinter::GetHeaviestStack(int tid, uint64_t beginTime, uint64_t endTime)
{
return impl_->GetHeaviestStack(tid, beginTime, endTime);
}
bool StackPrinter::InitUniqueTable(pid_t pid, uint32_t size, std::string name)
{
return impl_->InitUniqueTable(pid, size, name);
}
std::string StackPrinter::PrintTreeStackBySampledStack(const std::vector<SampledFrame>& sampledFrameVec,
bool printTimes, const std::shared_ptr<Unwinder>& unwinder,
const std::shared_ptr<DfxMaps>& maps)
{
return Impl::PrintTreeStackBySampledStack(sampledFrameVec, printTimes, unwinder, maps);
}
std::string StackPrinter::GetTreeStack(int tid, bool printTimes, uint64_t beginTime, uint64_t endTime)
{
return impl_->GetTreeStack(tid, printTimes, beginTime, endTime);
}
void StackPrinter::SerializeSampledFrameMap(const std::map<int, std::vector<SampledFrame>>& sampledFrameMap,
std::ostream& os)
{
Impl::SerializeSampledFrameMap(sampledFrameMap, os);
}
std::map<int, std::vector<SampledFrame>> StackPrinter::DeserializeSampledFrameMap(std::istream& is)
{
return Impl::DeserializeSampledFrameMap(is);
}
std::shared_ptr<StackItem> StackPrinter::Impl::InsertImpl(std::shared_ptr<StackItem> curNode, uintptr_t pc,
int32_t pcCount, uint64_t level,
std::shared_ptr<StackItem> acientNode)
{
if (curNode == nullptr) {
return nullptr;
}
if (level == curNode-> level && curNode->pc == pc) {
curNode->pcCount += pcCount;
return curNode;
}
if (curNode->pc == 0) {
curNode->pc = pc;
curNode->pcCount += pcCount;
return curNode;
}
if (level > curNode->level) {
acientNode = curNode;
if (curNode->child == nullptr) {
curNode->child = std::make_shared<StackItem>();
curNode->child->level = curNode->level + 1;
return InsertImpl(curNode->child, pc, pcCount, level, acientNode);
}
if (curNode->child->pc == pc) {
curNode->child->pcCount += pcCount;
return curNode->child;
}
curNode = curNode->child;
}
if (curNode->siblings == nullptr) {
curNode->siblings = std::make_shared<StackItem>();
curNode->siblings->level = curNode->level;
std::shared_ptr<StackItem> node = InsertImpl(curNode->siblings, pc, pcCount, level, acientNode);
curNode = AdjustSiblings(acientNode, curNode, node);
return curNode;
}
if (curNode->siblings->pc == pc) {
curNode->siblings->pcCount += pcCount;
curNode = AdjustSiblings(acientNode, curNode, curNode->siblings);
return curNode;
}
return InsertImpl(curNode->siblings, pc, pcCount, level, acientNode);
}
void StackPrinter::Impl::Insert(const std::vector<uintptr_t>& pcs, int32_t pcCount, StackId stackId)
{
if (root_ == nullptr) {
root_ = std::make_shared<StackItem>();
root_->level = 0;
}
std::shared_ptr<StackItem> dummyNode = std::make_shared<StackItem>();
std::shared_ptr<StackItem> acientNode = dummyNode;
acientNode->child = root_;
std::shared_ptr<StackItem> curNode = root_;
uint64_t level = 0;
for (auto iter = pcs.rbegin(); iter != pcs.rend(); ++iter) {
curNode = InsertImpl(curNode, *iter, pcCount, level, acientNode);
level++;
}
curNode->stackId = stackId.value;
}
std::shared_ptr<StackItem> StackPrinter::Impl::AdjustSiblings(std::shared_ptr<StackItem> acient,
std::shared_ptr<StackItem> cur,
std::shared_ptr<StackItem> node)
{
std::shared_ptr<StackItem> dummy = std::make_shared<StackItem>();
dummy->siblings = acient->child;
std::shared_ptr<StackItem> p = dummy;
while (p->siblings != node && p->siblings->pcCount >= node->pcCount) {
p = p->siblings;
}
if (p->siblings != node) {
cur->siblings = node->siblings;
node->siblings = p->siblings;
if (p == dummy) {
acient->child = node;
}
p->siblings = node;
}
return node;
}
bool StackPrinter::Impl::PutPcsInTable(const std::vector<uintptr_t>& pcs, int tid, uint64_t snapshotTime)
{
if (uniqueStackTable_ == nullptr) {
return false;
}
uint64_t stackId = 0;
auto stackIdPtr = reinterpret_cast<StackId*>(&stackId);
uniqueStackTable_->PutPcsInTable(stackIdPtr, pcs.data(), pcs.size());
std::lock_guard<std::mutex> lock(mutex_);
auto& stackRecordVec = tidStackRecordMap_[tid];
auto it = std::find_if(stackRecordVec.begin(), stackRecordVec.end(), [&stackId](const auto& stackRecord) {
return stackRecord.stackId == stackId;
});
if (it == stackRecordVec.end()) {
StackRecord record;
record.stackId = stackId;
record.snapshotTimes.emplace_back(snapshotTime);
stackRecordVec.emplace_back(record);
} else {
it->snapshotTimes.emplace_back(snapshotTime);
}
return true;
}
std::string StackPrinter::Impl::GetFullStack(const std::vector<TimeStampedPcs>& timeStampedPcsVec)
{
std::stringstream stack;
for (const auto& timeStampedPcs : timeStampedPcsVec) {
std::string snapshotTimeStr = TimeFormat(timeStampedPcs.snapshotTime);
stack << "SnapshotTime:" << snapshotTimeStr << "\n";
std::vector<uintptr_t> pcs = timeStampedPcs.pcVec;
stack << PrintStackByPcs(pcs, unwinder_, maps_);
stack << "\n";
}
return stack.str();
}
std::vector<SampledFrame> StackPrinter::Impl::GetSampledFramesByTid(int tid, uint64_t beginTime, uint64_t endTime)
{
std::lock_guard<std::mutex> lock(mutex_);
auto& stackRecordVec = tidStackRecordMap_[tid];
std::vector<StackRecord> vec = TimeFilter(stackRecordVec, beginTime, endTime);
BuildStackTree(vec);
return GenerateTreeStackFrames(vec);
}
std::string StackPrinter::Impl::GetTreeStack(int tid, bool printTimes, uint64_t beginTime, uint64_t endTime)
{
std::vector<SampledFrame> sampledFrameVec = GetSampledFramesByTid(tid, beginTime, endTime);
return PrintTreeStackBySampledStack(sampledFrameVec, printTimes, unwinder_, maps_);
}
std::string StackPrinter::Impl::GetHeaviestStack(int tid, uint64_t beginTime, uint64_t endTime)
{
if (unwinder_ == nullptr || maps_ == nullptr) {
return std::string("");
}
std::lock_guard<std::mutex> lock(mutex_);
auto& stackRecordVec = tidStackRecordMap_[tid];
std::vector<StackRecord> vec = TimeFilter(stackRecordVec, beginTime, endTime);
if (vec.empty()) {
return std::string("");
}
std::sort(vec.begin(), vec.end(), [](const auto& a, const auto& b) {
return a.snapshotTimes.size() > b.snapshotTimes.size();
});
auto it = vec.begin();
std::vector<uintptr_t> pcs;
StackId stackId;
stackId.value = it->stackId;
uniqueStackTable_->GetPcsByStackId(stackId, pcs);
std::stringstream heaviestStack;
heaviestStack << "heaviest stack: \nstack counts: " << std::to_string(it->snapshotTimes.size()) << "\n";
heaviestStack << PrintStackByPcs(pcs, unwinder_, maps_);
return heaviestStack.str();
}
bool StackPrinter::Impl::InitUniqueTable(pid_t pid, uint32_t size, std::string name)
{
uniqueStackTable_ = std::make_unique<UniqueStackTable>(pid, size);
if (!uniqueStackTable_->Init()) {
return false;
}
void* uniqueTableBufMMap = reinterpret_cast<void*>(uniqueStackTable_->GetHeadNode());
if (!name.empty()) {
prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, uniqueTableBufMMap, size, name.c_str());
}
return true;
}
void StackPrinter::Impl::BuildStackTree(std::vector<StackRecord>& stackRecordVec)
{
std::sort(stackRecordVec.begin(), stackRecordVec.end(), [](const auto& a, const auto& b) {
return a.snapshotTimes.size() > b.snapshotTimes.size();
});
for (const auto& record : stackRecordVec) {
std::vector<uintptr_t> pcs;
StackId stackId;
stackId.value = record.stackId;
if (uniqueStackTable_->GetPcsByStackId(stackId, pcs)) {
Insert(pcs, record.snapshotTimes.size(), stackId);
}
}
}
void StackPrinter::Impl::PrintTimesStr(std::string& frameStr, const std::vector<uint64_t>& snapshotTimes)
{
auto pos = frameStr.find("\n");
if (pos != std::string::npos) {
frameStr.replace(pos, 1, "");
}
for (auto t : snapshotTimes) {
frameStr.append(" ");
frameStr.append(TimeFormat(t));
}
frameStr.append("\n");
}
std::string StackPrinter::Impl::PrintTreeStackBySampledStack(const std::vector<SampledFrame>& sampledFrameVec,
bool printTimes, const std::shared_ptr<Unwinder>& unwinder,
const std::shared_ptr<DfxMaps>& maps)
{
if (unwinder == nullptr || maps == nullptr) {
return std::string("");
}
std::stringstream ss;
for (const auto& sampledFrame : sampledFrameVec) {
std::string space(sampledFrame.indent, ' ');
DfxFrame frame;
frame.index = sampledFrame.level;
if (!unwinder->GetFrameByPc(sampledFrame.pc, maps, frame)) {
frame.pc = 0;
}
std::string frameStr = DfxFrameFormatter::GetFrameStr(frame);
if (sampledFrame.isLeaf && printTimes) {
PrintTimesStr(frameStr, sampledFrame.timestamps);
}
ss << space << sampledFrame.count << " " << frameStr;
}
return ss.str();
}
std::vector<SampledFrame> StackPrinter::Impl::GenerateTreeStackFrames(const std::vector<StackRecord>& stackRecordVec)
{
std::vector<SampledFrame> sampledFrameVec;
std::vector<std::shared_ptr<StackItem>> nodes;
if (root_ != nullptr) {
nodes.emplace_back(root_);
}
const uint32_t indent = 2;
while (!nodes.empty()) {
std::shared_ptr<StackItem> back = nodes.back();
SampledFrame sampledFrame;
sampledFrame.indent = indent * back->level;
sampledFrame.count = back->pcCount;
sampledFrame.level = back->level;
sampledFrame.pc = back->pc;
nodes.pop_back();
if (back->siblings != nullptr) {
nodes.emplace_back(back->siblings);
}
if (back->child != nullptr) {
nodes.emplace_back(back->child);
sampledFrame.isLeaf = false;
} else {
sampledFrame.isLeaf = true;
uint64_t stackId = back->stackId;
auto it = std::find_if(stackRecordVec.begin(), stackRecordVec.end(), [&stackId](const auto& record) {
return record.stackId == stackId;
});
if (it != stackRecordVec.end()) {
sampledFrame.timestamps = it->snapshotTimes;
}
}
sampledFrameVec.emplace_back(sampledFrame);
}
root_ = nullptr;
return sampledFrameVec;
}
std::string StackPrinter::Impl::PrintStackByPcs(const std::vector<uintptr_t>& pcs,
const std::shared_ptr<Unwinder>& unwinder,
const std::shared_ptr<DfxMaps>& maps)
{
std::stringstream ss;
for (size_t i = 0; i < pcs.size(); i++) {
DfxFrame frame;
unwinder->GetFrameByPc(pcs[i], maps, frame);
frame.index = i;
auto frameStr = DfxFrameFormatter::GetFrameStr(frame);
ss << frameStr;
}
return ss.str();
}
std::map<int, std::vector<SampledFrame>> StackPrinter::Impl::GetThreadSampledFrames(uint64_t beginTime,
uint64_t endTime)
{
std::map<int, std::vector<SampledFrame>> sampledFrameMap;
for (const auto& [tid, _] : tidStackRecordMap_) {
sampledFrameMap[tid] = GetSampledFramesByTid(tid, beginTime, endTime);
}
return sampledFrameMap;
}
void StackPrinter::Impl::SerializeSampledFrameMap(const std::map<int, std::vector<SampledFrame>>& sampledFrameMap,
std::ostream& os)
{
os << sampledFrameMap.size() << "\n";
for (const auto& [tid, sampledFrameVec] : sampledFrameMap) {
os << tid << " " << sampledFrameVec.size();
for (const auto& frame : sampledFrameVec) {
os << " " << frame;
}
os << "\n";
}
}
std::map<int, std::vector<SampledFrame>> StackPrinter::Impl::DeserializeSampledFrameMap(std::istream& is)
{
std::map<int, std::vector<SampledFrame>> sampledFrameMap;
size_t mapSize;
if (!(is >> mapSize)) {
is.setstate(std::ios::failbit);
return std::map<int, std::vector<SampledFrame>>();
}
if (mapSize > MAX_SAMPLE_TIDS) {
return std::map<int, std::vector<SampledFrame>>();
}
is.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
for (size_t i = 0; i < mapSize; i++) {
int tid;
size_t vecSize;
if (!(is >> tid >> vecSize)) {
is.setstate(std::ios::failbit);
return std::map<int, std::vector<SampledFrame>>();
}
std::vector<SampledFrame> sampledFrameVec;
for (size_t j = 0; j < vecSize; j++) {
SampledFrame frame;
if (!(is >> frame)) {
is.setstate(std::ios::failbit);
return std::map<int, std::vector<SampledFrame>>();
}
sampledFrameVec.emplace_back(frame);
}
is.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
if (tid < 0 || tid > MAX_TID_VALUE) {
return std::map<int, std::vector<SampledFrame>>();
}
sampledFrameMap[tid] = sampledFrameVec;
}
return sampledFrameMap;
}
}
}