1 /**
2 * Copyright (c) 2025 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 "samples_record.h"
17 #include "libpandafile/debug_helpers.h"
18 #include "libpandafile/class_data_accessor.h"
19
20 namespace ark::tooling::sampler {
21 static constexpr size_t ROOT_NODE_ID = 1;
22 static constexpr size_t PROGRAM_NODE_ID = 2;
23 static constexpr size_t IDLE_NODE_ID = 3;
24 static const uintptr_t ROOT_NODE_PTR = (reinterpret_cast<uintptr_t>(reinterpret_cast<void *>(INT_MAX - ROOT_NODE_ID)));
25 static const uintptr_t PROGRAM_NODE_PTR =
26 (reinterpret_cast<uintptr_t>(reinterpret_cast<void *>(INT_MAX - PROGRAM_NODE_ID)));
27 static const uintptr_t IDLE_NODE_PTR = (reinterpret_cast<uintptr_t>(reinterpret_cast<void *>(INT_MAX - IDLE_NODE_ID)));
NodeInit(ProfileInfo & profileInfo)28 void SamplesRecord::NodeInit(ProfileInfo &profileInfo)
29 {
30 NodeKey nodeKey;
31 CpuProfileNode methodNode;
32
33 nodeKey.methodKey.frameId.pandaFilePtr = ROOT_NODE_PTR;
34 nodesMap_.emplace(nodeKey, nodesMap_.size() + 1);
35 methodNode.parentId = 0;
36 methodNode.codeEntry.functionName = "(root)";
37 methodNode.id = ROOT_NODE_ID;
38 profileInfo.nodes[profileInfo.nodeCount++] = methodNode;
39
40 nodeKey.methodKey.frameId.pandaFilePtr = PROGRAM_NODE_PTR;
41 nodesMap_.emplace(nodeKey, nodesMap_.size() + 1);
42 methodNode.codeEntry.functionName = "(program)";
43 methodNode.id = PROGRAM_NODE_ID;
44 profileInfo.nodes[profileInfo.nodeCount++] = methodNode;
45 profileInfo.nodes[0].children.push_back(methodNode.id);
46
47 nodeKey.methodKey.frameId.pandaFilePtr = IDLE_NODE_PTR;
48 nodesMap_.emplace(nodeKey, nodesMap_.size() + 1);
49 methodNode.codeEntry.functionName = "(idle)";
50 methodNode.id = IDLE_NODE_ID;
51 profileInfo.nodes[profileInfo.nodeCount++] = methodNode;
52 profileInfo.nodes[0].children.push_back(methodNode.id);
53 }
54
55 /**
56 * Builds a stack information map (stackInfoMap_) by parsing the stack information
57 * from the provided sampleInfo. Each stack frame (frameId) is processed to generate
58 * a corresponding FrameInfo object, which is then stored in stackInfoMap_.
59 * @param sampleInfo A SampleInfo object containing the stack information.
60 */
BuildStackInfoMap(const SampleInfo & sampleInfo)61 void SamplesRecord::BuildStackInfoMap(const SampleInfo &sampleInfo)
62 {
63 FrameInfo frameInfo;
64 // Iterate over the managedStack array in sampleInfo.
65 for (size_t index = 0; index < sampleInfo.stackInfo.managedStackSize; ++index) {
66 const auto &frameId = sampleInfo.stackInfo.managedStack[index];
67
68 // Check if the frameId already exists in stackInfoMap_. If it does, skip processing.
69 if (stackInfoMap_.find(frameId) != stackInfoMap_.end()) {
70 continue;
71 }
72 // If frameId's pandaFilePtr is null, the frame is invalid, so skip processing.
73 if (frameId.pandaFilePtr == 0) {
74 LOG(DEBUG, PROFILER) << "Profiler collected an invalid pandFilePtr";
75 continue;
76 }
77
78 auto pfId = reinterpret_cast<panda_file::File *>(frameId.pandaFilePtr);
79 panda_file::File::EntityId fileId(frameId.fileId);
80 // Use MethodDataAccessor to access method data and retrieve the module name and function name.
81 panda_file::MethodDataAccessor mda(*pfId, fileId);
82
83 // Use ClassDataAccessor to get url.
84 panda_file::ClassDataAccessor cda(*pfId, mda.GetClassId());
85 auto sourceFileId = cda.GetSourceFileId();
86 if (!sourceFileId) {
87 frameInfo.url = "<unknown>";
88 } else {
89 frameInfo.url = reinterpret_cast<const char *>(pfId->GetStringData(sourceFileId.value()).data);
90 }
91
92 // Check if the URL already exists in scriptIdMap_. If not, assign a new scriptId.
93 auto iter = scriptIdMap_.find(frameInfo.url);
94 if (iter == scriptIdMap_.end()) {
95 auto scriptId = scriptIdMap_.size() + 1;
96 scriptIdMap_.emplace(frameInfo.url, scriptId);
97 frameInfo.scriptId = static_cast<int>(scriptId);
98 } else {
99 frameInfo.scriptId = static_cast<int>(iter->second);
100 }
101 frameInfo.moduleName = mda.GetClassName();
102 frameInfo.functionName = mda.GetFullName();
103 frameInfo.lineNumber =
104 static_cast<int>(panda_file::debug_helpers::GetLineNumber(mda, frameId.bcOffset, pfId)) - 1;
105 stackInfoMap_.emplace(frameId, frameInfo);
106 }
107 }
108
109 /**
110 * Retrieves the FrameInfo associated with the given frameId.
111 * @param frameId The unique identifier of the stack frame.
112 * @return A pointer to the corresponding FrameInfo, or nullptr if not found.
113 * Caller must check for nullptr before dereferencing.
114 */
GetFrameInfoByFrameId(const SampleInfo::ManagedStackFrameId & frameId)115 struct FrameInfo *SamplesRecord::GetFrameInfoByFrameId(const SampleInfo::ManagedStackFrameId &frameId)
116 {
117 auto itor = stackInfoMap_.find(frameId);
118 if (itor != stackInfoMap_.end()) {
119 return &itor->second;
120 }
121 return nullptr;
122 }
123
124 /**
125 * Processes a single call stack data from the sampleInfo and populates the profileInfo.
126 * This function iterates over the managed stack frames in reverse order, builds nodes for each frame,
127 * and updates the profileInfo object. It also calculates time deltas between samples and updates timestamps.
128 * @param sampleInfo The SampleInfo object containing the stack information and timestamp.
129 * @param profileInfo The ProfileInfo object to be updated with processed data.
130 * @param prevTimeStamp Reference to the previous timestamp for calculating time deltas.
131 */
ProcessSingleCallStackData(const SampleInfo & sampleInfo,ProfileInfo & profileInfo,uint64_t & prevTimeStamp)132 void SamplesRecord::ProcessSingleCallStackData(const SampleInfo &sampleInfo, ProfileInfo &profileInfo,
133 uint64_t &prevTimeStamp)
134 {
135 int nodeId = 1;
136 // Iterate over the managed stack frames in reverse order.
137 for (auto index = static_cast<int>(sampleInfo.stackInfo.managedStackSize - 1); index >= 0; --index) {
138 const auto ¤tFrame = &sampleInfo.stackInfo.managedStack[index];
139 auto frameInfo = GetFrameInfoByFrameId(*currentFrame);
140 // Skip processing if FrameInfo is invalid (e.g., null pointer or missing pandaFilePtr).
141 if (frameInfo == nullptr) {
142 LOG(DEBUG, PROFILER) << "Profiler encountered an invalid pandaFilePtr";
143 continue;
144 }
145
146 // Generate a unique NodeKey for the current frame.
147 NodeKey nodeKey;
148 nodeKey.parentId = nodeId;
149 nodeKey.methodKey.lineNumber = frameInfo->lineNumber;
150 nodeKey.methodKey.frameId = *currentFrame;
151
152 // Check if the node already exists in the nodesMap_.
153 if (nodesMap_.find(nodeKey) == nodesMap_.end()) {
154 // Create a new CpuProfileNode if it doesn't exist.
155 CpuProfileNode methodNode;
156 methodNode.id = nodeId = static_cast<int>(nodesMap_.size() + 1);
157 nodesMap_.emplace(nodeKey, nodeId);
158 methodNode.codeEntry = *frameInfo;
159 profileInfo.nodes[profileInfo.nodeCount++] = methodNode;
160 } else {
161 // If the node already exists, retrieve its ID.
162 nodeId = nodesMap_.at(nodeKey);
163 }
164
165 // Process the topmost frame (index == 0) to update hit count and add a sample.
166 if (index == 0) {
167 profileInfo.nodes[nodeId - 1].hitCount++;
168 profileInfo.samples.push_back(nodeId);
169 }
170
171 // Update the parent node's children list.
172 auto &children = profileInfo.nodes[nodeKey.parentId - 1].children;
173 if (std::find(children.begin(), children.end(), nodeId) == children.end()) {
174 children.push_back(nodeId);
175 }
176 }
177
178 // Calculate the time delta between the current sample and the previous one.
179 int timeDelta;
180 if (prevTimeStamp == 0) {
181 profileInfo.tid = sampleInfo.threadInfo.threadId;
182 profileInfo.startTime = threadStartTime_;
183 timeDelta = static_cast<int>(sampleInfo.timeStamp - threadStartTime_);
184 } else {
185 timeDelta = static_cast<int>(sampleInfo.timeStamp - prevTimeStamp);
186 }
187
188 // Update the timeDeltas array and the previous timestamp.
189 profileInfo.timeDeltas.push_back(timeDelta);
190 prevTimeStamp = sampleInfo.timeStamp;
191
192 // Update the stop time for the profileInfo.
193 profileInfo.stopTime = sampleInfo.timeStamp;
194 }
195
196 /**
197 * Generates a ProfileInfo object for a single thread by processing a collection of SampleInfo objects.
198 * @param sampleInfos A vector of unique pointers to `SampleInfo` objects representing profiling data for a
199 * single thread.
200 * @return A unique pointer to a `ProfileInfo` object containing the processed profiling data for the thread.
201 * Returns `nullptr` if the input `sampleInfos` vector is empty.
202 */
GetSingleThreadProfileInfo(const SampleInfoVector & sampleInfos)203 std::unique_ptr<ProfileInfo> SamplesRecord::GetSingleThreadProfileInfo(const SampleInfoVector &sampleInfos)
204 {
205 if (sampleInfos.empty()) {
206 return nullptr;
207 }
208 auto singleThreadProfileInfo = std::make_unique<ProfileInfo>();
209 NodeInit(*singleThreadProfileInfo);
210 uint64_t prevTimeStamp = 0;
211 for (const auto &sampleInfo : sampleInfos) {
212 BuildStackInfoMap(*sampleInfo);
213 ProcessSingleCallStackData(*sampleInfo, *singleThreadProfileInfo, prevTimeStamp);
214 }
215 return singleThreadProfileInfo;
216 }
217
218 /**
219 * Generates a collection of ProfileInfo objects for all threads profiling data.
220 * @return A unique pointer to a vector of unique pointers to `ProfileInfo` objects, containing profiling data
221 * for all threads. Returns `nullptr` if the `tidToSampleInfosMap_` is empty.
222 */
GetAllThreadsProfileInfos()223 std::unique_ptr<std::vector<std::unique_ptr<ProfileInfo>>> SamplesRecord::GetAllThreadsProfileInfos()
224 {
225 if (tidToSampleInfosMap_.empty()) {
226 return nullptr;
227 }
228 auto allThreadsProfileInfos = std::make_unique<std::vector<std::unique_ptr<ProfileInfo>>>();
229 for (const auto &pair : tidToSampleInfosMap_) {
230 auto profileInfo = GetSingleThreadProfileInfo(pair.second);
231 if (profileInfo) {
232 allThreadsProfileInfos->emplace_back(std::move(profileInfo));
233 }
234 }
235 return allThreadsProfileInfos;
236 }
237 } // namespace ark::tooling::sampler