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