diff --git a/lisa/_kmod.py b/lisa/_kmod.py index 0183856ef8ff3d65ac67332d23e148f80282bf86..7f94604b799ece0c2ba48372739ff8ac5058ff90 100644 --- a/lisa/_kmod.py +++ b/lisa/_kmod.py @@ -2074,8 +2074,26 @@ class _KernelBuildEnv(Loggable, SerializeViaConstructor): except Exception: raise missing_configs(('CONFIG_IKCONFIG_PROC',)) + + target_kernel_sha1 = target.plat_info['kernel']['version'].sha1 + if target_kernel_sha1 is None: + kheaders_key = None + else: + with open(temp / 'config.gz', 'rb') as f: + config_checksum = checksum(f, 'sha256') + kheaders_key = ( + target_kernel_sha1, + config_checksum, + ) + try: - target.cached_pull('/sys/kernel/kheaders.tar.xz', str(temp), via_temp=True, as_root=True) + target.cached_pull( + '/sys/kernel/kheaders.tar.xz', + str(temp), + via_temp=True, + as_root=True, + key=kheaders_key, + ) except Exception: raise missing_configs(('CONFIG_IKHEADERS',)) @@ -3094,6 +3112,10 @@ class LISADynamicKmod(FtraceDynamicKmod): with tempfile.NamedTemporaryFile() as f: try: + # We do not attempt to cache the BTF blob beyond the lifetime + # of the target object. Any mistake in the caching key would + # result in incredibly hard-to-debug issues due to mismatching + # memory layout. target.cached_pull(btf_path, f.name, via_temp=True, as_root=True) except FileNotFoundError: raise FileNotFoundError(f'Could not find {btf_path} on the target. Ensure you compiled your kernel using CONFIG_DEBUG_INFO=y CONFIG_DEBUG_INFO_BTF=y CONFIG_DEBUG_INFO_REDUCED=n') diff --git a/lisa/platforms/platinfo.py b/lisa/platforms/platinfo.py index d12c2423a8f73e8e2b60b1cacd25b99baf03ad1e..44fdfb83abf70b6e726c35dbe2b93a36252f36bb 100644 --- a/lisa/platforms/platinfo.py +++ b/lisa/platforms/platinfo.py @@ -23,6 +23,7 @@ import functools import contextlib from collections.abc import Mapping import typing +import warnings from lisa.utils import HideExekallID, group_by_value, memoized from lisa.conf import ( @@ -136,7 +137,7 @@ class PlatformInfo(MultiSrcConf, HideExekallID): )) """Some keys have a reserved meaning with an associated type.""" - def add_target_src(self, target, rta_calib_res_dir, src='target', only_missing=True, **kwargs): + def add_target_src(self, target, rta_calib_res_dir=None, src='target', only_missing=True, **kwargs): """ Add source from a live :class:`lisa.target.Target`. @@ -158,6 +159,10 @@ class PlatformInfo(MultiSrcConf, HideExekallID): :Variable keyword arguments: Forwarded to :class:`lisa.conf.MultiSrcConf.add_src`. """ + + if rta_calib_res_dir is not None: + warnings.warn('rta_calib_res_dir parameter is deprecated and must be set to None', DeprecationWarning) + info = { 'nrg-model': lambda: EnergyModel.from_target(target), 'kernel': { diff --git a/lisa/target.py b/lisa/target.py index 9cb61d2c996e11bd9801d23e78ef4386c6a77c78..b237b8f53ecd5ce3a5caf115c3c7f4e1bb8db737 100644 --- a/lisa/target.py +++ b/lisa/target.py @@ -42,13 +42,14 @@ from types import ModuleType, FunctionType from operator import itemgetter import warnings import typing +import uuid import devlib from devlib.exception import TargetStableError from devlib.utils.misc import which, to_identifier from devlib.platform.gem5 import Gem5SimulationPlatform -from lisa.utils import Loggable, HideExekallID, resolve_dotted_name, get_subclasses, import_all_submodules, LISA_HOME, RESULT_DIR, LATEST_LINK, setup_logging, ArtifactPath, nullcontext, ExekallTaggable, memoized, destroyablecontextmanager, ContextManagerExit, update_params_from, delegate_getattr, DelegateToAttr +from lisa.utils import Loggable, HideExekallID, resolve_dotted_name, get_subclasses, import_all_submodules, LISA_HOME, RESULT_DIR, LATEST_LINK, setup_logging, ArtifactPath, nullcontext, ExekallTaggable, memoized, destroyablecontextmanager, ContextManagerExit, update_params_from, delegate_getattr, DelegateToAttr, DirCache from lisa._assets import ASSETS_PATH from lisa.conf import SimpleMultiSrcConf, KeyDesc, LevelKeyDesc, TopLevelKeyDesc, Configurable, DelegatedLevelKeyDesc, ConfigKeyError from lisa._kmod import _KernelBuildEnv, DynamicKmod, _KernelBuildEnvConf @@ -338,6 +339,9 @@ class Target( tools, plat_info, lazy_platinfo, devlib_excluded_modules, kernel_src, kmod_build_env, kmod_make_vars, kmod_overlay_backend, ): + self._uuid = uuid.uuid4().hex + self._user_res_dir = res_dir + # Set it temporarily to avoid breaking __getattr__ self._devlib_loadable_modules = set() @@ -346,19 +350,6 @@ class Target( logger = self.logger self.name = name - res_dir = res_dir if res_dir else self._get_res_dir( - root=os.path.join(LISA_HOME, RESULT_DIR), - relative='', - name=f'{self.__class__.__qualname__}-{self.name}', - append_time=True, - symlink=True - ) - - self._res_dir = res_dir - os.makedirs(self._res_dir, exist_ok=True) - if os.listdir(self._res_dir): - raise ValueError(f'res_dir must be empty: {self._res_dir}') - self._installed_tools = set() self.target = target @@ -382,10 +373,6 @@ class Target( self._init_plat_info(plat_info, name, deferred=lazy_platinfo, fallback=True) logger.info(f'Effective platform information:\n{self.plat_info}') - cache_dir = Path(self._res_dir).resolve() / '.lisa' / 'cache' - cache_dir.mkdir(parents=True) - self._cache_dir = cache_dir - self._kmod_build_env = None def _make_kernel_build_env_spec(*args, **kwargs): @@ -433,9 +420,7 @@ class Target( if name: plat_info.add_src('target-conf', dict(name=name)) - rta_calib_res_dir = ArtifactPath.join(self._res_dir, 'rta_calib') - os.makedirs(rta_calib_res_dir, exist_ok=True) - plat_info.add_target_src(self, rta_calib_res_dir, **kwargs) + plat_info.add_target_src(self, **kwargs) self.plat_info = plat_info def __getstate__(self): @@ -456,6 +441,22 @@ class Target( self._init_plat_info(deferred=True) self._kmod_build_env = None + @property + def _res_dir(self): + res_dir = self._user_res_dir if self._user_res_dir else self._get_res_dir( + root=os.path.join(LISA_HOME, RESULT_DIR), + relative='', + name=f'{self.__class__.__qualname__}-{self.name}', + append_time=True, + symlink=True + ) + + os.makedirs(res_dir, exist_ok=True) + if os.listdir(res_dir): + raise ValueError(f'res_dir must be empty: {self._res_dir}') + + return res_dir + def get_kmod(self, mod_cls=DynamicKmod, **kwargs): """ Build a :class:`lisa._kmod.DynamicKmod` instance of the given @@ -495,27 +496,47 @@ class Target( self._kmod_build_env = mod.kernel_build_env return mod - def cached_pull(self, src, dst, **kwargs): + + def cached_pull(self, src, dst, key=None, **kwargs): """ Same as ``lisa.target.Target.pull`` but will cache the file in the ``target.res_dir`` folder, based on the source path. + :param key: Cache key to check against when deciding to pull the file : + again. If ``None``, the file will be pulled once in the lifetime of the + :class:`Target` instance it is called on. + :type key: object or None + :Variable keyword arguments: Forwarded to ``Target.pull``. """ - cache = (self._cache_dir / 'pull') - cache.mkdir(parents=True, exist_ok=True) - m = hashlib.sha256() - m.update(src.encode('utf-8')) - key = m.hexdigest() - cached_path = cache / key / os.path.basename(src) + def populate(key, path): + src, kwargs, meta = key + self.pull(src, path / 'data', **kwargs) - if not cached_path.exists(): - self.pull(src, cached_path, **kwargs) + dir_cache = DirCache( + category='target_pull', + populate=populate, + ) + + src = Path(src) + dst = Path(dst) + + key = self._uuid if key is None else key + key = (src, kwargs, str(key)) + cached_path = dir_cache.get_entry(key) / 'data' if cached_path.is_dir(): - shutil.copytree(cached_path, dst) + # We would need to mirror devlib.target.Target.pull() behavior + # (when the destination exists as a folder, as a file, or does not + # exist yet) for folders, which is more complex and we don't really + # need it for now. + raise NotImplementedError('Folders are not supported in cached_pull() for now') else: + dst.parent.mkdir(parents=True, exist_ok=True) + if dst.is_dir(): + dst = dst / src.name + shutil.copy2(cached_path, dst) @property diff --git a/lisa/wlgen/rta.py b/lisa/wlgen/rta.py index 71f0a71f0a0bc840ef3678308ecba8c28c182f32..bbeb39c6dcb173a1eb81e9edc24ff4337f6c0d43 100644 --- a/lisa/wlgen/rta.py +++ b/lisa/wlgen/rta.py @@ -886,7 +886,7 @@ class RTA(Workload): @classmethod def _do_calibrate(cls, target, res_dir): - res_dir = res_dir if res_dir else target .get_res_dir( + res_dir = res_dir if res_dir else target.get_res_dir( "rta_calib", symlink=False ) logger = cls.get_logger()