forked from espressif/esp-idf
151 lines
5.0 KiB
Python
151 lines
5.0 KiB
Python
# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
import os
|
|
import sys
|
|
import typing as t
|
|
from typing import Literal
|
|
|
|
from dynamic_pipelines.constants import BINARY_SIZE_METRIC_NAME
|
|
from idf_build_apps import App
|
|
from idf_build_apps import CMakeApp
|
|
from idf_build_apps import json_to_app
|
|
|
|
from idf_ci_local.uploader import AppUploader
|
|
from idf_ci_local.uploader import get_app_uploader
|
|
|
|
|
|
class IdfCMakeApp(CMakeApp):
|
|
uploader: t.ClassVar[t.Optional['AppUploader']] = get_app_uploader()
|
|
build_system: Literal['idf_cmake'] = 'idf_cmake'
|
|
|
|
def _initialize_hook(self, **kwargs: t.Any) -> None:
|
|
# ensure this env var exists
|
|
os.environ['IDF_TARGET'] = self.target
|
|
|
|
super()._initialize_hook(**kwargs)
|
|
|
|
def _post_build(self) -> None:
|
|
super()._post_build()
|
|
|
|
if self.uploader:
|
|
self.uploader.upload_app(self.build_path)
|
|
|
|
|
|
class Metrics:
|
|
"""
|
|
Represents a metric and its values for source, target, and the differences.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
source_value: t.Optional[float] = None,
|
|
target_value: t.Optional[float] = None,
|
|
difference: t.Optional[float] = None,
|
|
difference_percentage: t.Optional[float] = None,
|
|
) -> None:
|
|
self.source_value = source_value or 0.0
|
|
self.target_value = target_value or 0.0
|
|
self.difference = difference or 0.0
|
|
self.difference_percentage = difference_percentage or 0.0
|
|
|
|
def to_dict(self) -> t.Dict[str, t.Any]:
|
|
"""
|
|
Converts the Metrics object to a dictionary.
|
|
"""
|
|
return {
|
|
'source_value': self.source_value,
|
|
'target_value': self.target_value,
|
|
'difference': self.difference,
|
|
'difference_percentage': self.difference_percentage,
|
|
}
|
|
|
|
|
|
class AppWithMetricsInfo(IdfCMakeApp):
|
|
metrics: t.Dict[str, Metrics]
|
|
is_new_app: bool
|
|
|
|
def __init__(self, **kwargs: t.Any) -> None:
|
|
super().__init__(**kwargs)
|
|
|
|
self.metrics = {metric_name: metric_data for metric_name, metric_data in kwargs.get('metrics', {}).items()}
|
|
self.is_new_app = kwargs.get('is_new_app', False)
|
|
|
|
class Config:
|
|
arbitrary_types_allowed = True
|
|
|
|
|
|
def dump_apps_to_txt(apps: t.List[App], output_filepath: str) -> None:
|
|
with open(output_filepath, 'w') as fw:
|
|
for app in apps:
|
|
fw.write(app.model_dump_json() + '\n')
|
|
|
|
|
|
def import_apps_from_txt(input_filepath: str) -> t.List[App]:
|
|
apps: t.List[App] = []
|
|
with open(input_filepath) as fr:
|
|
for line in fr:
|
|
if line := line.strip():
|
|
try:
|
|
apps.append(json_to_app(line, extra_classes=[IdfCMakeApp]))
|
|
except Exception: # noqa
|
|
print('Failed to deserialize app from line: %s' % line)
|
|
sys.exit(1)
|
|
|
|
return apps
|
|
|
|
|
|
def enrich_apps_with_metrics_info(
|
|
app_metrics_info_map: t.Dict[str, t.Dict[str, t.Any]], apps: t.List[App]
|
|
) -> t.List[AppWithMetricsInfo]:
|
|
def _get_full_attributes(obj: App) -> t.Dict[str, t.Any]:
|
|
"""
|
|
Retrieves all attributes of an object, including properties and computed fields.
|
|
"""
|
|
attributes: t.Dict[str, t.Any] = obj.__dict__.copy()
|
|
for attr in dir(obj):
|
|
if not attr.startswith('_'): # Skip private/internal attributes
|
|
try:
|
|
value = getattr(obj, attr)
|
|
# Include only if it's not already in __dict__
|
|
if attr not in attributes:
|
|
attributes[attr] = value
|
|
except Exception:
|
|
# Skip attributes that raise exceptions (e.g., methods needing args)
|
|
pass
|
|
return attributes
|
|
|
|
default_metrics_structure = {
|
|
BINARY_SIZE_METRIC_NAME: Metrics(
|
|
source_value=0,
|
|
target_value=0,
|
|
difference=0,
|
|
difference_percentage=0.0,
|
|
),
|
|
}
|
|
|
|
apps_with_metrics_info = []
|
|
for app in apps:
|
|
key = f'{app.app_dir}_{app.config_name}_{app.target}'
|
|
app_attributes = _get_full_attributes(app)
|
|
|
|
metrics = {metric_name: default_metric for metric_name, default_metric in default_metrics_structure.items()}
|
|
is_new_app = False
|
|
|
|
if key in app_metrics_info_map:
|
|
info = app_metrics_info_map[key]
|
|
for metric_name, metric_data in info.get('metrics', {}).items():
|
|
metrics[metric_name] = Metrics(
|
|
source_value=metric_data.get('source_value', 0),
|
|
target_value=metric_data.get('target_value', 0),
|
|
difference=metric_data.get('difference', 0),
|
|
difference_percentage=metric_data.get('difference_percentage', 0.0),
|
|
)
|
|
|
|
is_new_app = info.get('is_new_app', False)
|
|
|
|
app_attributes.update({'metrics': metrics, 'is_new_app': is_new_app})
|
|
|
|
apps_with_metrics_info.append(AppWithMetricsInfo(**app_attributes))
|
|
|
|
return apps_with_metrics_info
|