The stack has been created for storing func and dso from the callchain. The sample has been added to a specific thread. It first checks if the thread exists in the Thread class. Then it call _add_sample function which is responsible for appending a new entry to the samples list. Also callchain parsing and storing part is implemented. Moreover removed the comment from thread.
Showing the changes to tools/perf/scripts/python (1 of 1 file). Show all
Unified · Split
| @@ -40,6 +40,9 @@ CATEGORIES = None · 39 unmodified lines | ||
| 40 | 40 | # The product name is used by the profiler UI to show the Operating system and Processor. |
| 41 | 41 | PRODUCT = os.popen('uname -op').read().strip() |
| 42 | 42 | |
| 43 | +# Here key = tid, value = Thread | |
| 44 | +tid_to_thread = dict() | |
| 45 | + | |
| 43 | 46 | # The category index is used by the profiler UI to show the color of the flame graph. |
| 44 | 47 | USER_CATEGORY_INDEX = 0 |
| 45 | 48 | KERNEL_CATEGORY_INDEX = 1 |
| @@ -153,6 +156,25 @@ class Thread: · 107 unmodified lines | ||
| 153 | 156 | )) |
| 154 | 157 | return frame_id |
| 155 | 158 | |
| 159 | + def _add_sample(self, comm: str, stack: List[str], time_ms: Milliseconds) -> None: | |
| 160 | + """Add a timestamped stack trace sample to the thread builder. | |
| 161 | + Args: | |
| 162 | + comm: command-line (name) of the thread at this sample | |
| 163 | + stack: sampled stack frames. Root first, leaf last. | |
| 164 | + time_ms: timestamp of sample in milliseconds. | |
| 165 | + """ | |
| 166 | + # Ihreads may not set their names right after they are created. | |
| 167 | + # Instead, they might do it later. In such situations, to use the latest name they have set. | |
| 168 | + if self.comm != comm: | |
| 169 | + self.comm = comm | |
| 170 | + | |
| 171 | + prefix_stack_id = reduce(lambda prefix_id, frame: self._intern_stack | |
| 172 | + (self._intern_frame(frame), prefix_id), stack, None) | |
| 173 | + if prefix_stack_id is not None: | |
| 174 | + self.samples.append(Sample(stack_id=prefix_stack_id, | |
| 175 | + time_ms=time_ms, | |
| 176 | + responsiveness=0)) | |
| 177 | + | |
| 156 | 178 | def _to_json_dict(self) -> Dict: |
| 157 | 179 | """Converts current Thread to GeckoThread JSON format.""" |
| 158 | 180 | # Gecko profile format is row-oriented data as List[List], |
| @@ -231,9 +253,36 @@ def process_event(param_dict: Dict) -> None: · 72 unmodified lines | ||
| 231 | 253 | if not start_time: |
| 232 | 254 | start_time = time_stamp |
| 233 | 255 | |
| 256 | + # Parse and append the callchain of the current sample into a stack. | |
| 257 | + stack = [] | |
| 258 | + if param_dict['callchain']: | |
| 259 | + for call in param_dict['callchain']: | |
| 260 | + if 'sym' not in call: | |
| 261 | + continue | |
| 262 | + stack.append(f'{call["sym"]["name"]} (in {call["dso"]})') | |
| 263 | + if len(stack) != 0: | |
| 264 | + # Reverse the stack, as root come first and the leaf at the end. | |
| 265 | + stack = stack[::-1] | |
| 266 | + | |
| 267 | + # During perf record if -g is not used, the callchain is not available. | |
| 268 | + # In that case, the symbol and dso are available in the event parameters. | |
| 269 | + else: | |
| 270 | + func = param_dict['symbol'] if 'symbol' in param_dict else '[unknown]' | |
| 271 | + dso = param_dict['dso'] if 'dso' in param_dict else '[unknown]' | |
| 272 | + stack.append(f'{func} (in {dso})') | |
| 273 | + | |
| 274 | + # Add sample to the specific thread. | |
| 275 | + thread = tid_to_thread.get(tid) | |
| 276 | + if thread is None: | |
| 277 | + thread = Thread(comm=comm, pid=pid, tid=tid) | |
| 278 | + tid_to_thread[tid] = thread | |
| 279 | + thread._add_sample(comm=comm, stack=stack, time_ms=time_stamp) | |
| 280 | + | |
| 234 | 281 | # Trace_end runs at the end and will be used to aggregate |
| 235 | 282 | # the data into the final json object and print it out to stdout. |
| 236 | 283 | def trace_end() -> None: |
| 284 | + threads = [thread._to_json_dict() for thread in tid_to_thread.values()] | |
| 285 | + | |
| 237 | 286 | # Schema: https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L305 |
| 238 | 287 | gecko_profile_with_meta = { |
| 239 | 288 | "meta": { |
| @@ -252,8 +301,7 @@ def trace_end() -> None: · 12 unmodified lines | ||
| 252 | 301 | "markerSchema": [], |
| 253 | 302 | }, |
| 254 | 303 | "libs": [], |
| 255 | − # threads will be implemented in later commits. | |
| 256 | − # "threads": threads, | |
| 304 | + "threads": threads, | |
| 257 | 305 | "processes": [], |
| 258 | 306 | "pausedRanges": [], |
| 259 | 307 | } |