• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2023 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/frames.h"
19 
20 namespace panda::ecmascript {
HeapSampling(const EcmaVM * vm,Heap * const heap,uint64_t interval,int stackDepth)21 HeapSampling::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 
~HeapSampling()35 HeapSampling::~HeapSampling()
36 {
37     heap_->ClearAllocationInspectorFromAllSpaces();
38     vm_->GetJSThread()->SetIsStartHeapSampling(false);
39 }
40 
GetAllocationProfile()41 const struct SamplingInfo *HeapSampling::GetAllocationProfile()
42 {
43     CalNodeSelfSize(&samplingInfo_->head_);
44     return samplingInfo_.get();
45 }
46 
ImplementSampling(Address addr,size_t size)47 void 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 
PushStackInfo(const struct MethodKey & methodKey)55 bool 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 
PushFrameInfo(const FrameInfoTemp & frameInfoTemp)64 bool 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 
ResetFrameLength()73 void HeapSampling::ResetFrameLength()
74 {
75     frameInfoTemps_.clear();
76     frameStack_.clear();
77 }
78 
GetStack()79 void 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 
FillScriptIdAndStore()130 void 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 
AddRunningState(char * functionName,RunningState state,kungfu::DeoptType type)157 std::string HeapSampling::AddRunningState(char *functionName, RunningState state, kungfu::DeoptType type)
158 {
159     std::string result = functionName;
160     if (state == RunningState::AOT && type != kungfu::DeoptType::NOTCHECK) {
161         state = RunningState::AINT;
162     }
163     if (state == RunningState::BUILTIN) {
164         result.append("(BUILTIN)");
165     }
166     return result;
167 }
168 
PushAndGetNode()169 SamplingNode *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 
GetMethodInfo(const MethodKey & methodKey)180 struct 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 
FindOrAddNode(struct SamplingNode * node,const MethodKey & methodKey)190 struct 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 
CreateNodeId()206 uint32_t HeapSampling::CreateNodeId()
207 {
208     return ++nodeId_;
209 }
210 
CreateSampleId()211 uint64_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*.
AdjustSampleCount(size_t size,unsigned int count) const221 unsigned 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 
CalNodeSelfSize(SamplingNode * node)227 void 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