tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 79e88998d6c2ddf916cfb7d749cfa21c5868cd05
parent c39fee754e453a6ebeac9c254473ce01ec0b8f7a
Author: Florian Quèze <florian@queze.net>
Date:   Thu,  9 Oct 2025 21:56:22 +0000

Bug 1993214 - Show test_status and log message stacks on profiler markers, r=ahal,mstange.

Differential Revision: https://phabricator.services.mozilla.com/D267453

Diffstat:
Mtesting/mozbase/mozsystemmonitor/mozsystemmonitor/resourcemonitor.py | 131++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
1 file changed, 106 insertions(+), 25 deletions(-)

diff --git a/testing/mozbase/mozsystemmonitor/mozsystemmonitor/resourcemonitor.py b/testing/mozbase/mozsystemmonitor/mozsystemmonitor/resourcemonitor.py @@ -677,10 +677,9 @@ class SystemResourceMonitor: time_sec = data["time"] / 1000 timestamp = SystemResourceMonitor.instance.convert_to_monotonic_time(time_sec) - action = data.get("action") marker_data = {"type": "TestStatus"} - if action == "process_output": + if data.get("action") == "process_output": # Process output uses "output" as marker name marker_name = "output" message = data.get("data") @@ -697,19 +696,20 @@ class SystemResourceMonitor: elif status == "ERROR": marker_data["color"] = "red" - subtest = data.get("subtest") - if subtest: + if subtest := data.get("subtest"): marker_data["subtest"] = subtest message = data.get("message") - test_name = data.get("test") - if test_name: + if test_name := data.get("test"): marker_data["test"] = test_name if message: marker_data["message"] = message + if stack := data.get("stack"): + marker_data["stack"] = stack + SystemResourceMonitor.record_event(marker_name, timestamp, marker_data) @staticmethod @@ -1465,6 +1465,75 @@ class SystemResourceMonitor: stringArray.append(string) return len(stringArray) - 1 + def parse_stack(stack_string): + """Parse a JavaScript stack trace into structured format. + + Supports two formats: + 1. JavaScript Error.stack format: "func@file:line:col\nfunc@file:line:col\n..." + 2. Normalized nsIStackFrame format: "file:func:line\nfile:func:line\n..." + Returns an array of frame dicts. + """ + if not stack_string: + return None + + frames = [] + for line in stack_string.strip().split("\n"): + if not line: + continue + + file_name = None + func_part = None + line_num = None + col_num = None + + # Parse "func@file:line:col" (JavaScript Error.stack format) + if "@" in line: + func_part, location = line.rsplit("@", 1) + func_part = func_part.strip() + + # Parse "file:line:col" + parts = location.rsplit(":", 2) + if len(parts) == 3: + file_name, line_str, col_str = parts + try: + line_num = int(line_str) + col_num = int(col_str) + except ValueError: + pass + elif len(parts) == 2: + file_name, line_str = parts + try: + line_num = int(line_str) + except ValueError: + pass + else: + file_name = location + else: + # Parse "file:func:line" (normalized nsIStackFrame format) + parts = line.rsplit(":", 2) + if len(parts) == 3: + file_name, func_part, line_str = parts + try: + line_num = int(line_str) + except ValueError: + func_part = line.strip() + file_name = None + else: + func_part = line.strip() + + frame_dict = {"is_js": True} + if func_part: + frame_dict["function"] = func_part + if file_name: + frame_dict["file"] = file_name + if line_num is not None: + frame_dict["line"] = line_num + if col_num is not None: + frame_dict["column"] = col_num + frames.append(frame_dict) + + return frames + def get_stack_index(stack_frames): """Get a stack index from a structured stack (array of frame dicts). @@ -1473,8 +1542,10 @@ class SystemResourceMonitor: - module: module/library name (optional) - file: source file path (optional) - line: line number (optional) + - column: column number (optional) - offset: hex offset for unsymbolicated frames (optional) - inlined: boolean indicating if this is an inlined frame (optional) + - is_js: boolean indicating if this is a JavaScript frame (optional) Returns the index of the innermost stack frame, or None if stack_frames is empty. """ @@ -1502,6 +1573,8 @@ class SystemResourceMonitor: module_name = frame_data.get("module") file_name = frame_data.get("file") line_num = frame_data.get("line") + col_num = frame_data.get("column") + is_js = frame_data.get("is_js", False) # Get offsets - different handling for native vs JIT frames module_offset = frame_data.get("module_offset") @@ -1512,24 +1585,26 @@ class SystemResourceMonitor: if not func_name and (offset := module_offset or raw_offset): func_name = hex(offset) - # Get or create resource for the module + # Get or create resource for the module or file resource_index = -1 - if module_name: + resource_name = module_name or (file_name if is_js else None) + if resource_name: # Find existing resource for i, name_idx in enumerate(resourceTable["name"]): - if firstThread["stringArray"][name_idx] == module_name: + if firstThread["stringArray"][name_idx] == resource_name: resource_index = i break else: # Create new resource if not found resource_index = resourceTable["length"] resourceTable["lib"].append(None) - resourceTable["name"].append(get_string_index(module_name)) + resourceTable["name"].append(get_string_index(resource_name)) resourceTable["host"].append(None) # Possible resourceTypes: # 0 = unknown, 1 = library, 2 = addon, 3 = webhost, 4 = otherhost, 5 = url # https://github.com/firefox-devtools/profiler/blob/32cb6672c7ed47311e9d84963023d51f5147042b/src/profile-logic/data-structures.ts#L322 - resourceTable["type"].append(1) + resource_type = 1 if module_name else (5 if is_js else 0) + resourceTable["type"].append(resource_type) resourceTable["length"] += 1 # Create native symbol for unsymbolicated frames @@ -1574,13 +1649,13 @@ class SystemResourceMonitor: break else: func_index = funcTable["length"] - funcTable["isJS"].append(False) - funcTable["relevantForJS"].append(False) + funcTable["isJS"].append(is_js) + funcTable["relevantForJS"].append(is_js) funcTable["name"].append(func_name_index) funcTable["resource"].append(resource_index) funcTable["fileName"].append(file_name_index) funcTable["lineNumber"].append(line_num) - funcTable["columnNumber"].append(None) + funcTable["columnNumber"].append(col_num) funcTable["length"] += 1 # Get or create frame index @@ -1590,6 +1665,7 @@ class SystemResourceMonitor: if ( func_idx == func_index and frameTable["line"][i] == line_num + and frameTable["column"][i] == col_num and frameTable["inlineDepth"][i] == inline_depth and frameTable["nativeSymbol"][i] == native_symbol_index and frameTable["address"][i] == frame_address @@ -1607,7 +1683,7 @@ class SystemResourceMonitor: frameTable["innerWindowID"].append(0) frameTable["implementation"].append(None) frameTable["line"].append(line_num) - frameTable["column"].append(None) + frameTable["column"].append(col_num) frameTable["length"] += 1 # Create stack entry @@ -1642,19 +1718,24 @@ class SystemResourceMonitor: markers["category"].append(category_index) markers["name"].append(name_index) - # Extract and process stack if present (structured format: array of frame dicts) + # Extract and process stack if present stack_index = None if isinstance(data, dict) and "stack" in data: stack = data["stack"] - if isinstance(stack, list): - stack_index = get_stack_index(stack) - del data["stack"] - # Add cause object to marker data for processed profile format - if stack_index is not None: - data["cause"] = { - "time": markers["startTime"][-1], - "stack": stack_index, - } + del data["stack"] + + # Convert string stack to structured format if needed + if isinstance(stack, str): + stack = parse_stack(stack) + + stack_index = get_stack_index(stack) + + # Add cause object to marker data for processed profile format + if stack_index is not None: + data["cause"] = { + "time": markers["startTime"][-1], + "stack": stack_index, + } markers["data"].append(data) markers["stack"].append(stack_index)