commit 196adee60bc254da57bd343c4e8aec1797a6619f
parent fdf2c194a37b424be8e39bb6e6d408f8ee6bca48
Author: Segun Famisa <sfamisa@mozilla.com>
Date: Fri, 5 Dec 2025 15:36:14 +0000
Bug 1968045 - Upload memory leak traces as artifacts r=aaronmt,tthibaud
This patch introduces the ability to collect memory leak artifacts from Firebase Test Lab (FTL) after a test run.
The `test-lab.py` script is updated with a new `--artifact_type` argument. This allows specifying the type of artifact to be collected, making the process more generic. The `copy-artifacts-from-ftl.py` script is enhanced to recognize and process `memory_leaks` as a new artifact type, copying any found leak files to the designated worker directory.
Differential Revision: https://phabricator.services.mozilla.com/D270305
Diffstat:
3 files changed, 46 insertions(+), 9 deletions(-)
diff --git a/taskcluster/kinds/ui-test-apk/kind.yml b/taskcluster/kinds/ui-test-apk/kind.yml
@@ -349,7 +349,7 @@ tasks:
path: mobile/android/test_infra/.firebase_token.json
json: true
commands:
- - [python3, taskcluster/scripts/tests/test-lab.py, fenix/arm64-v8a-detect-leaks.yml, /builds/worker/fetches/target.arm64-v8a.apk, --apk_test, /builds/worker/fetches/target.noarch.apk]
+ - [python3, taskcluster/scripts/tests/test-lab.py, fenix/arm64-v8a-detect-leaks.yml, /builds/worker/fetches/target.arm64-v8a.apk, --apk_test, /builds/worker/fetches/target.noarch.apk, --artifact_type, "memory_leaks"]
treeherder:
platform: 'fenix-android-all/opt'
symbol: fenix-debug(detect-leaks-arm)
diff --git a/taskcluster/scripts/tests/copy-artifacts-from-ftl.py b/taskcluster/scripts/tests/copy-artifacts-from-ftl.py
@@ -60,6 +60,7 @@ class Worker(Enum):
BASELINE_PROFILE_DIR = "/builds/worker/workspace/baselineProfile"
MACROBENCHMARK_DEST = "/builds/worker/artifacts/build/macrobenchmark.json"
MACROBENCHMARK_DIR = "/builds/worker/artifacts/build/macrobenchmark"
+ MEMORY_LEAKS_DIR = "/builds/worker/artifacts/build/memory_leaks"
ARTIFACTS_DIR = "/builds/worker/artifacts/build"
@@ -76,6 +77,7 @@ class ArtifactType(Enum):
"artifacts/sdcard/Android/media/org.mozilla.fenix.benchmark/*benchmarkData.json"
)
MATRIX_IDS = "matrix_ids.json"
+ MEMORY_LEAKS = "artifacts/sdcard/Download/memory_leaks/*.txt"
def load_matrix_ids_artifact(matrix_file_path):
@@ -254,6 +256,8 @@ def process_artifacts(artifact_type):
return process_baseline_profile_artifacts(root_gcs_path, device_names)
elif artifact_type == ArtifactType.MACROBENCHMARK:
return process_macrobenchmark_artifact(root_gcs_path, device_names)
+ elif artifact_type == ArtifactType.MEMORY_LEAKS:
+ return process_memory_leaks_artifacts(root_gcs_path, device_names)
else:
return process_crash_artifacts(root_gcs_path, device_names)
@@ -313,6 +317,21 @@ def process_macrobenchmark_artifact(root_gcs_path, device_names):
downloaded_files.append(dest_path)
+def process_memory_leaks_artifacts(root_gcs_path, device_names):
+ for device in device_names:
+ artifacts = fetch_artifacts(
+ root_gcs_path, device, ArtifactType.MEMORY_LEAKS.value
+ )
+ if not artifacts:
+ logging.info(f"No artifacts found for device: {device}")
+ continue
+ for artifact in artifacts:
+ base_name = os.path.basename(artifact)
+ dest_path = os.path.join(Worker.MEMORY_LEAKS_DIR.value, f"leak_{base_name}")
+
+ gsutil_cp(artifact, dest_path)
+
+
def process_crash_artifacts(root_gcs_path, failed_device_names):
crashes_reported = 0
for device in failed_device_names:
@@ -350,8 +369,12 @@ def main():
process_artifacts(ArtifactType.MACROBENCHMARK)
elif artifact_type_arg == "crash_log":
process_artifacts(ArtifactType.CRASH_LOG)
+ elif artifact_type_arg == "memory_leaks":
+ process_artifacts(ArtifactType.MEMORY_LEAKS)
else:
- logging.error("Invalid artifact type. Use 'baseline_profile' or 'crash_log'.")
+ logging.error(
+ "Invalid artifact type. Use one of 'baseline_profile', 'macrobenchmark', 'crash_log or 'memory_leaks."
+ )
sys.exit(1)
diff --git a/taskcluster/scripts/tests/test-lab.py b/taskcluster/scripts/tests/test-lab.py
@@ -148,24 +148,25 @@ def execute_tests(
return exit_code
-def process_results(flank_config: str, test_type: str = "instrumentation") -> None:
+def process_results(
+ flank_config: str, test_type: str = "instrumentation", artifact_type: str = None
+) -> None:
"""Process and parse test results.
Args:
flank_config: The YML configuration for Flank to use e.g, automation/taskcluster/androidTest/flank-<config>.yml
test_type: The type of test executed: 'instrumentation' or 'robo'
+ artifact_type: The type of the artifacts to copy after the test run
"""
parse_junit_results_artifact = os.path.join(SCRIPT_DIR, "parse-junit-results.py")
- copy_robo_crash_artifacts_script = os.path.join(
- SCRIPT_DIR, "copy-artifacts-from-ftl.py"
- )
+ copy_artifacts_script = os.path.join(SCRIPT_DIR, "copy-artifacts-from-ftl.py")
generate_flaky_report_script = os.path.join(
SCRIPT_DIR, "generate-flaky-report-from-ftl.py"
)
os.chmod(parse_junit_results_artifact, 0o755)
- os.chmod(copy_robo_crash_artifacts_script, 0o755)
+ os.chmod(copy_artifacts_script, 0o755)
os.chmod(generate_flaky_report_script, 0o755)
# Process the results differently based on the test type: instrumentation or robo
@@ -183,8 +184,12 @@ def process_results(flank_config: str, test_type: str = "instrumentation") -> No
"flank.log",
)
+ # Copy artifacts if specified
+ if artifact_type:
+ run_command([copy_artifacts_script, artifact_type])
+
if test_type == "robo":
- run_command([copy_robo_crash_artifacts_script, "crash_log"])
+ run_command([copy_artifacts_script, "crash_log"])
def main():
@@ -205,6 +210,11 @@ def main():
help="Absolute path to a Android APK androidTest package",
default=None,
)
+ parser.add_argument(
+ "--artifact_type",
+ help="Type of artifact to copy after running the tests",
+ default=None,
+ )
args = parser.parse_args()
setup_environment()
@@ -219,7 +229,11 @@ def main():
# Determine the instrumentation type to process the results differently
instrumentation_type = "instrumentation" if args.apk_test else "robo"
- process_results(flank_config=args.flank_config, test_type=instrumentation_type)
+ process_results(
+ flank_config=args.flank_config,
+ test_type=instrumentation_type,
+ artifact_type=args.artifact_type,
+ )
sys.exit(exit_code)