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