diff --git a/doc/conf.py b/doc/conf.py index 8fd481d41e8b95fb1b7942a298af178e6e3ee4c3..a8d81f2eab3e6f6630bfefa24d75bb2046429664 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -478,6 +478,13 @@ ignored_refs.update( ignored_refs = set(map(re.compile, ignored_refs)) +# Workaround for: https://github.com/jupyter-widgets/ipywidgets/issues/3930 +# Suggested: https://github.com/sphinx-doc/sphinx/issues/12585#issuecomment-2228420035 +suppress_warnings = [ + 'intersphinx.external', +] + + class CustomPythonDomain(PythonDomain): def find_obj(self, env, modname, classname, name, type, searchmode=0): refs = super().find_obj(env, modname, classname, name, type, searchmode) diff --git a/lisa/target.py b/lisa/target.py index 69efd5778b720034bd713be129bbbd4c06d35b84..362cba87bb37c8d897bd7e812046cee019116649 100644 --- a/lisa/target.py +++ b/lisa/target.py @@ -817,12 +817,23 @@ class Target(Loggable, HideExekallID, ExekallTaggable, Configurable): adb_as_root=(username == 'root'), ) elif kind == 'linux': + def resolve_strict_host_check(strict_host_check): + strict_host_check = True if strict_host_check is None else strict_host_check + + # Expand the path to known_hosts so that the devlib objects can + # be reused unchanged in another user namespace where the home + # directory would not expand to the expected folder. + if strict_host_check and isinstance(strict_host_check, bool): + strict_host_check = str(Path('~/.ssh/known_hosts').expanduser().resolve()) + + return strict_host_check + devlib_target_cls = devlib.LinuxTarget conn_settings.update( username=resolved_username, port=port or cls.SSH_PORT_DEFAULT, host=host, - strict_host_check=True if strict_host_check is None else strict_host_check, + strict_host_check=resolve_strict_host_check(strict_host_check), use_scp=False if use_scp is None else use_scp, ) diff --git a/lisa/trace.py b/lisa/trace.py index 5f0f42a5f260de5a869db5cfd588ddb899734b00..3bfdbe340ba825418e74dea9199199613ae572f0 100644 --- a/lisa/trace.py +++ b/lisa/trace.py @@ -360,6 +360,21 @@ def _lazyframe_rewrite(df, update_plan): return df +class _CacheDataDescEncodable(abc.ABC): + """ + Inheriting from this class allows encoding a value in JSON for a cache + desc. + """ + + @abc.abstractmethod + def json_encode(self): + """ + Returns a more basic object that can readily be encoded by an + unmodified json serializer. + """ + pass + + CPU = newtype(int, 'CPU', doc='Alias to ``int`` used for CPU IDs') @@ -3399,11 +3414,12 @@ class _ProcessTraceView(_TraceViewBase): @property def trace_state(self): f = self._process_df + return ( super().trace_state, # This likely will be a value that cannot be serialized to JSON if # it was user-provided. This will prevent caching as it should. - None if f is None else f, + f, ) def _internal_df_event(self, event, **kwargs): @@ -3586,6 +3602,22 @@ class _NamespaceTraceView(_TraceViewBase): return super()._internal_df_event(event, **kwargs) +class _TimeOffsetter(_CacheDataDescEncodable): + def __init__(self, offset): + assert isinstance(offset, Timestamp) + offset_ns = offset.as_nanoseconds + self._offset_ns = offset_ns + self._offset_polars = _polars_duration_expr(offset_ns, unit='ns', rounding='down') + + def json_encode(self): + return self._offset_ns + + def __call__(self, event, df): + return df.with_columns( + pl.col('Time') - self._offset_polars + ) + + class _NormalizedTimeTraceView(_TraceViewBase): def __init__(self, trace, window, **kwargs): window = window or (trace.start, None) @@ -3611,14 +3643,10 @@ class _NormalizedTimeTraceView(_TraceViewBase): def _with_time_offset(cls, trace, start): # Round down to avoid ending up with negative Time for anything that # does not actually happen before the start - offset = _polars_duration_expr(start, rounding='down') - def time_offset(event, df): - return df.with_columns( - pl.col('Time') - offset - ) + start = Timestamp(start, rounding='down') return trace.get_view( - process_df=time_offset + process_df=_TimeOffsetter(start) ) @property @@ -3929,10 +3957,23 @@ class _CacheDataSwapEntry: Return a mapping suitable for JSON serialization. """ desc = self.cache_desc_nf.to_json_map() + + class Encoder(json.JSONEncoder): + def default(self, o): + if isinstance(o, _CacheDataDescEncodable): + cls = o.__class__ + return { + 'module': cls.__module__, + 'cls': cls.__qualname__, + 'value': o.json_encode(), + } + else: + return super().default(o) + try: # Use json.dumps() here to fail early if the descriptor cannot be # dumped to JSON - desc = json.dumps(desc) + desc = Encoder().encode(desc) except TypeError as e: raise _CannotWriteSwapEntry(e) @@ -4477,15 +4518,15 @@ class _TraceCache(Loggable): if self._estimate_data_swap_size(data) + self._swap_size > self.max_swap_size: self.scrub_swap() - def log_error(e): - self.logger.error(f'Could not write {cache_desc} to swap: {e}') - # Write the Parquet file and update the write speed try: self._write_data(cache_desc.fmt, data, data_path) except Exception as e: if best_effort: - log_error(e) + # Do not log the error, as it could be an expected one + # (e.g. we have an object column in a dataframe that cannot + # be converted to arrow. + pass else: raise e else: @@ -4497,7 +4538,9 @@ class _TraceCache(Loggable): ) # We have a swap entry that cannot be written to the swap, # probably because the descriptor includes something that - # cannot be serialized to JSON. + # cannot be serialized to JSON. This may happen under + # normal operations, e.g. with a user-defined process_df + # function passed to _ProcessTraceView. except _CannotWriteSwapEntry as e: self.logger.debug(f'Could not write {cache_desc} to swap: {e}') swap_entry.written = False