Merge branch 'ci/fix_bin_size_report_generation' into 'master'

ci: fix bin size report generation

See merge request espressif/esp-idf!41065
This commit is contained in:
Aleksei Apaseev
2025-08-14 13:12:01 +08:00
3 changed files with 24 additions and 22 deletions

View File

@@ -23,7 +23,7 @@ def main() -> None:
parser: argparse.ArgumentParser = setup_argument_parser() parser: argparse.ArgumentParser = setup_argument_parser()
args: argparse.Namespace = parser.parse_args() args: argparse.Namespace = parser.parse_args()
report_actions: t.Dict[str, t.Callable[[argparse.Namespace], None]] = { report_actions: dict[str, t.Callable[[argparse.Namespace], None]] = {
'build': generate_build_report, 'build': generate_build_report,
'target_test': generate_target_test_report, 'target_test': generate_target_test_report,
'job': generate_jobs_report, 'job': generate_jobs_report,
@@ -42,7 +42,7 @@ def setup_argument_parser() -> argparse.ArgumentParser:
'--report-type', choices=['build', 'target_test', 'job'], required=True, help='Type of report to generate' '--report-type', choices=['build', 'target_test', 'job'], required=True, help='Type of report to generate'
) )
report_type_args: argparse.Namespace report_type_args: argparse.Namespace
remaining_args: t.List[str] remaining_args: list[str]
report_type_args, remaining_args = report_type_parser.parse_known_args() report_type_args, remaining_args = report_type_parser.parse_known_args()
parser: argparse.ArgumentParser = argparse.ArgumentParser( parser: argparse.ArgumentParser = argparse.ArgumentParser(
@@ -105,7 +105,7 @@ def generate_build_report(args: argparse.Namespace) -> None:
def generate_target_test_report(args: argparse.Namespace) -> None: def generate_target_test_report(args: argparse.Namespace) -> None:
test_cases: t.List[t.Any] = parse_testcases_from_filepattern(args.junit_report_filepattern) test_cases: list[t.Any] = parse_testcases_from_filepattern(args.junit_report_filepattern)
report_generator = TargetTestReportGenerator( report_generator = TargetTestReportGenerator(
args.project_id, args.project_id,
args.mr_iid, args.mr_iid,
@@ -123,7 +123,7 @@ def generate_target_test_report(args: argparse.Namespace) -> None:
def generate_jobs_report(args: argparse.Namespace) -> None: def generate_jobs_report(args: argparse.Namespace) -> None:
jobs: t.List[t.Any] = fetch_failed_jobs(args.commit_id) jobs: list[t.Any] = fetch_failed_jobs(args.commit_id)
if not jobs: if not jobs:
return return

View File

@@ -3,7 +3,6 @@
import glob import glob
import os import os
import re import re
import typing as t
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from urllib.parse import quote from urllib.parse import quote
from urllib.parse import urlencode from urllib.parse import urlencode
@@ -20,7 +19,7 @@ from .models import GitlabJob
from .models import TestCase from .models import TestCase
def parse_testcases_from_filepattern(junit_report_filepattern: str) -> t.List[TestCase]: def parse_testcases_from_filepattern(junit_report_filepattern: str) -> list[TestCase]:
""" """
Parses test cases from XML files matching the provided file pattern. Parses test cases from XML files matching the provided file pattern.
@@ -38,12 +37,12 @@ def parse_testcases_from_filepattern(junit_report_filepattern: str) -> t.List[Te
return test_cases return test_cases
def load_known_failure_cases() -> t.Optional[t.Set[str]]: def load_known_failure_cases() -> set[str] | None:
known_failures_file = os.getenv('KNOWN_FAILURE_CASES_FILE_NAME', '') known_failures_file = os.getenv('KNOWN_FAILURE_CASES_FILE_NAME', '')
if not known_failures_file: if not known_failures_file:
return None return None
try: try:
with open(known_failures_file, 'r') as f: with open(known_failures_file) as f:
file_content = f.read() file_content = f.read()
pattern = re.compile(r'^(.*?)\s+#\s+([A-Z]+)-\d+', re.MULTILINE) pattern = re.compile(r'^(.*?)\s+#\s+([A-Z]+)-\d+', re.MULTILINE)
@@ -66,7 +65,7 @@ def is_url(string: str) -> bool:
return bool(parsed.scheme) and bool(parsed.netloc) return bool(parsed.scheme) and bool(parsed.netloc)
def fetch_failed_jobs(commit_id: str) -> t.List[GitlabJob]: def fetch_failed_jobs(commit_id: str) -> list[GitlabJob]:
""" """
Fetches a list of jobs from the specified commit_id using an API request to ci-dashboard-api. Fetches a list of jobs from the specified commit_id using an API request to ci-dashboard-api.
:param commit_id: The commit ID for which to fetch jobs. :param commit_id: The commit ID for which to fetch jobs.
@@ -110,7 +109,7 @@ def fetch_failed_jobs(commit_id: str) -> t.List[GitlabJob]:
return combined_jobs return combined_jobs
def fetch_failed_testcases_failure_ratio(failed_testcases: t.List[TestCase], branches_filter: dict) -> t.List[TestCase]: def fetch_failed_testcases_failure_ratio(failed_testcases: list[TestCase], branches_filter: dict) -> list[TestCase]:
""" """
Fetches info about failure rates of testcases using an API request to ci-dashboard-api. Fetches info about failure rates of testcases using an API request to ci-dashboard-api.
:param failed_testcases: The list of failed testcases models. :param failed_testcases: The list of failed testcases models.
@@ -140,13 +139,14 @@ def fetch_failed_testcases_failure_ratio(failed_testcases: t.List[TestCase], bra
def fetch_app_metrics( def fetch_app_metrics(
source_commit_sha: str, source_commit_sha: str,
target_commit_sha: str, target_commit_sha: str,
) -> t.Dict: ) -> dict:
""" """
Fetches the app metrics for the given source commit SHA and target branch SHA. Fetches the app metrics for the given source commit SHA and target branch SHA.
:param source_commit_sha: The source commit SHA. :param source_commit_sha: The source commit SHA.
:param target_branch_sha: The commit SHA of the branch to compare app sizes against. :param target_branch_sha: The commit SHA of the branch to compare app sizes against.
:return: A dict of sizes of built binaries. :return: A dict of sizes of built binaries.
""" """
print(f'Fetching bin size info: {source_commit_sha=} {target_commit_sha=}')
build_info_map = dict() build_info_map = dict()
response = requests.post( response = requests.post(
f'{CI_DASHBOARD_API}/apps/metrics', f'{CI_DASHBOARD_API}/apps/metrics',
@@ -174,7 +174,7 @@ def load_file(file_path: str) -> str:
:param file_path: The path to the file needs to be loaded. :param file_path: The path to the file needs to be loaded.
:return: The content of the file as a string. :return: The content of the file as a string.
""" """
with open(file_path, 'r') as file: with open(file_path) as file:
return file.read() return file.read()

View File

@@ -9,6 +9,7 @@ from dynamic_pipelines.constants import BINARY_SIZE_METRIC_NAME
from idf_build_apps import App from idf_build_apps import App
from idf_build_apps import CMakeApp from idf_build_apps import CMakeApp
from idf_build_apps.utils import rmdir from idf_build_apps.utils import rmdir
from idf_ci_utils import idf_relpath
if t.TYPE_CHECKING: if t.TYPE_CHECKING:
pass pass
@@ -53,17 +54,17 @@ class Metrics:
def __init__( def __init__(
self, self,
source_value: t.Optional[float] = None, source_value: float | None = None,
target_value: t.Optional[float] = None, target_value: float | None = None,
difference: t.Optional[float] = None, difference: float | None = None,
difference_percentage: t.Optional[float] = None, difference_percentage: float | None = None,
) -> None: ) -> None:
self.source_value = source_value or 0.0 self.source_value = source_value or 0.0
self.target_value = target_value or 0.0 self.target_value = target_value or 0.0
self.difference = difference or 0.0 self.difference = difference or 0.0
self.difference_percentage = difference_percentage or 0.0 self.difference_percentage = difference_percentage or 0.0
def to_dict(self) -> t.Dict[str, t.Any]: def to_dict(self) -> dict[str, t.Any]:
""" """
Converts the Metrics object to a dictionary. Converts the Metrics object to a dictionary.
""" """
@@ -76,7 +77,7 @@ class Metrics:
class AppWithMetricsInfo(IdfCMakeApp): class AppWithMetricsInfo(IdfCMakeApp):
metrics: t.Dict[str, Metrics] metrics: dict[str, Metrics]
is_new_app: bool is_new_app: bool
def __init__(self, **kwargs: t.Any) -> None: def __init__(self, **kwargs: t.Any) -> None:
@@ -90,13 +91,13 @@ class AppWithMetricsInfo(IdfCMakeApp):
def enrich_apps_with_metrics_info( def enrich_apps_with_metrics_info(
app_metrics_info_map: t.Dict[str, t.Dict[str, t.Any]], apps: t.List[App] app_metrics_info_map: dict[str, dict[str, t.Any]], apps: list[App]
) -> t.List[AppWithMetricsInfo]: ) -> list[AppWithMetricsInfo]:
def _get_full_attributes(obj: App) -> t.Dict[str, t.Any]: def _get_full_attributes(obj: App) -> dict[str, t.Any]:
""" """
Retrieves all attributes of an object, including properties and computed fields. Retrieves all attributes of an object, including properties and computed fields.
""" """
attributes: t.Dict[str, t.Any] = obj.__dict__.copy() attributes: dict[str, t.Any] = obj.__dict__.copy()
for attr in dir(obj): for attr in dir(obj):
if not attr.startswith('_'): # Skip private/internal attributes if not attr.startswith('_'): # Skip private/internal attributes
try: try:
@@ -120,6 +121,7 @@ def enrich_apps_with_metrics_info(
apps_with_metrics_info = [] apps_with_metrics_info = []
for app in apps: for app in apps:
app.app_dir = idf_relpath(app.app_dir)
key = f'{app.app_dir}_{app.config_name}_{app.target}' key = f'{app.app_dir}_{app.config_name}_{app.target}'
app_attributes = _get_full_attributes(app) app_attributes = _get_full_attributes(app)