1/*
2 * Copyright (c) 2023-2024 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 "ecmascript/dfx/hprof/heap_sampling.h"
17
18#include "ecmascript/base/number_helper.h"
19
20namespace panda::ecmascript {
21HeapSampling::HeapSampling(const EcmaVM *vm, Heap *const heap, uint64_t interval, int stackDepth)
22    : vm_(vm),
23      heap_(heap),
24      rate_(interval),
25      stackDepth_(stackDepth),
26      allocationInspector_(heap_, rate_, this)
27{
28    samplingInfo_ = std::make_unique<struct SamplingInfo>();
29    samplingInfo_->head_.callFrameInfo_.functionName_ = "(root)";
30    samplingInfo_->head_.id_ = CreateNodeId();
31    heap_->AddAllocationInspectorToAllSpaces(&allocationInspector_);
32    vm_->GetJSThread()->SetIsStartHeapSampling(true);
33}
34
35HeapSampling::~HeapSampling()
36{
37    heap_->ClearAllocationInspectorFromAllSpaces();
38    vm_->GetJSThread()->SetIsStartHeapSampling(false);
39}
40
41const struct SamplingInfo *HeapSampling::GetAllocationProfile()
42{
43    CalNodeSelfSize(&samplingInfo_->head_);
44    return samplingInfo_.get();
45}
46
47void HeapSampling::ImplementSampling([[maybe_unused]] Address addr, size_t size)
48{
49    GetStack();
50    SamplingNode *node = PushAndGetNode();
51    node->allocations_[size]++;
52    samplingInfo_->samples_.emplace_back(Sample(size, node->id_, CreateSampleId(), AdjustSampleCount(size, 1)));
53}
54
55bool HeapSampling::PushStackInfo(const struct MethodKey &methodKey)
56{
57    if (UNLIKELY(frameStack_.size() >= static_cast<size_t>(stackDepth_))) {
58        return false;
59    }
60    frameStack_.emplace_back(methodKey);
61    return true;
62}
63
64bool HeapSampling::PushFrameInfo(const FrameInfoTemp &frameInfoTemp)
65{
66    if (UNLIKELY(frameInfoTemps_.size() >= static_cast<size_t>(stackDepth_))) {
67        return false;
68    }
69    frameInfoTemps_.emplace_back(frameInfoTemp);
70    return true;
71}
72
73void HeapSampling::ResetFrameLength()
74{
75    frameInfoTemps_.clear();
76    frameStack_.clear();
77}
78
79void HeapSampling::GetStack()
80{
81    ResetFrameLength();
82    JSThread *thread = vm_->GetAssociatedJSThread();
83    JSTaggedType *frame = const_cast<JSTaggedType *>(thread->GetCurrentFrame());
84    if (frame == nullptr) {
85        return;
86    }
87    if (JsStackGetter::CheckFrameType(thread, frame)) {
88        FrameHandler frameHandler(thread);
89        FrameIterator it(frameHandler.GetSp(), thread);
90        bool topFrame = true;
91        int stackCounter = 0;
92        for (; !it.Done() && stackCounter < stackDepth_; it.Advance<>()) {
93            auto method = it.CheckAndGetMethod();
94            if (method == nullptr) {
95                continue;
96            }
97            bool isNative = method->IsNativeWithCallField();
98            struct MethodKey methodKey;
99            if (topFrame) {
100                methodKey.state = JsStackGetter::GetRunningState(it, vm_, isNative, true);
101                topFrame = false;
102            } else {
103                methodKey.state = JsStackGetter::GetRunningState(it, vm_, isNative, false);
104            }
105            void *methodIdentifier = JsStackGetter::GetMethodIdentifier(method, it);
106            if (methodIdentifier == nullptr) {
107                continue;
108            }
109            methodKey.methodIdentifier = methodIdentifier;
110            if (stackInfoMap_.count(methodKey) == 0) {
111                struct FrameInfoTemp codeEntry;
112                if (UNLIKELY(!JsStackGetter::ParseMethodInfo(methodKey, it, vm_, codeEntry))) {
113                    continue;
114                }
115                if (UNLIKELY(!PushFrameInfo(codeEntry))) {
116                    return;
117                }
118            }
119            if (UNLIKELY(!PushStackInfo(methodKey))) {
120                return;
121            }
122            ++stackCounter;
123        }
124        if (!it.Done()) {
125            LOG_ECMA(INFO) << "Heap sampling actual stack depth is greater than the setted depth: " << stackDepth_;
126        }
127    }
128}
129
130void HeapSampling::FillScriptIdAndStore()
131{
132    size_t len = frameInfoTemps_.size();
133    if (len == 0) {
134        return;
135    }
136    struct CallFrameInfo callframeInfo;
137    for (size_t i = 0; i < len; ++i) {
138        callframeInfo.url_ = frameInfoTemps_[i].url;
139        auto iter = scriptIdMap_.find(callframeInfo.url_);
140        if (iter == scriptIdMap_.end()) {
141            scriptIdMap_.emplace(callframeInfo.url_, scriptIdMap_.size() + 1); // scriptId start from 1
142            callframeInfo.scriptId_ = static_cast<int>(scriptIdMap_.size());
143        } else {
144            callframeInfo.scriptId_ = iter->second;
145        }
146        callframeInfo.functionName_ = AddRunningState(frameInfoTemps_[i].functionName,
147                                                      frameInfoTemps_[i].methodKey.state,
148                                                      frameInfoTemps_[i].methodKey.deoptType);
149        callframeInfo.codeType_ = frameInfoTemps_[i].codeType;
150        callframeInfo.columnNumber_ = frameInfoTemps_[i].columnNumber;
151        callframeInfo.lineNumber_ = frameInfoTemps_[i].lineNumber;
152        stackInfoMap_.emplace(frameInfoTemps_[i].methodKey, callframeInfo);
153    }
154    frameInfoTemps_.clear();
155}
156
157std::string HeapSampling::AddRunningState(char *functionName, RunningState state, kungfu::DeoptType type)
158{
159    std::string result = functionName;
160    if (state == RunningState::AOT && type != kungfu::DeoptType::NONE) {
161        state = RunningState::AINT;
162    }
163    if (state == RunningState::BUILTIN) {
164        result.append("(BUILTIN)");
165    }
166    return result;
167}
168
169SamplingNode *HeapSampling::PushAndGetNode()
170{
171    FillScriptIdAndStore();
172    SamplingNode *node = &(samplingInfo_->head_);
173    int frameLen = static_cast<int>(frameStack_.size()) - 1;
174    for (; frameLen >= 0; frameLen--) {
175        node = FindOrAddNode(node, frameStack_[frameLen]);
176    }
177    return node;
178}
179
180struct CallFrameInfo HeapSampling::GetMethodInfo(const MethodKey &methodKey)
181{
182    struct CallFrameInfo frameInfo;
183    auto iter = stackInfoMap_.find(methodKey);
184    if (iter != stackInfoMap_.end()) {
185        frameInfo = iter->second;
186    }
187    return frameInfo;
188}
189
190struct SamplingNode *HeapSampling::FindOrAddNode(struct SamplingNode *node, const MethodKey &methodKey)
191{
192    struct SamplingNode *childNode = nullptr;
193    if (node->children_.count(methodKey) != 0) {
194        childNode = node->children_[methodKey].get();
195    }
196    if (childNode == nullptr) {
197        std::unique_ptr<struct SamplingNode> tempNode = std::make_unique<struct SamplingNode>();
198        tempNode->callFrameInfo_ = GetMethodInfo(methodKey);
199        tempNode->id_ = CreateNodeId();
200        node->children_.emplace(methodKey, std::move(tempNode));
201        return node->children_[methodKey].get();
202    }
203    return childNode;
204}
205
206uint32_t HeapSampling::CreateNodeId()
207{
208    return ++nodeId_;
209}
210
211uint64_t HeapSampling::CreateSampleId()
212{
213    return ++sampleId_;
214}
215
216// We collect samples according to a Poisson Process. Because sampling can not record
217// all allocations, we need estimate real allocations of all spaces based on the collected
218// samples. Given that sampling rate is R, the probability sampling an allocation of size S
219// is 1-exp(-S/R). So when collect *count* samples with size *size*, we can use the above
220// probability to approximate the real count of allocations with size *size*.
221unsigned int HeapSampling::AdjustSampleCount(size_t size, unsigned int count) const
222{
223    double scale = 1.0 / (1.0 - std::exp(-static_cast<double>(size) / rate_));
224    return static_cast<unsigned int>(count * scale + base::HALF);
225}
226
227void HeapSampling::CalNodeSelfSize(SamplingNode *node)
228{
229    node->selfSize_ = 0;
230    for (const auto &alloc : node->allocations_) {
231        unsigned int realCount = AdjustSampleCount(alloc.first, alloc.second);
232        node->selfSize_ += alloc.first * realCount;
233    }
234    for (auto &child : node->children_) {
235        CalNodeSelfSize(child.second.get());
236    }
237}
238}  // namespace panda::ecmascript
239