• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 &currentFrame = &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