diff --git a/result_parser.py b/result_parser.py index 9fb41fab3feadf926986e80b853f0904d0c42e96..578cdaed3ddd302828e0c326329c75d7ce8fb9c6 100644 --- a/result_parser.py +++ b/result_parser.py @@ -14,7 +14,7 @@ from typing import Literal, Optional, TypedDict, Union, cast from dateutil.parser import parse as parse_date -from utils import UrlOrPath +from .utils import UrlOrPath DETAILS_RE = re.compile(r"(?P<avg>\d+) \(± (?P<var>\d+)\) (?P<unit>\w+)") @@ -55,13 +55,14 @@ class RawMeasurement(TypedDict): details: str -class RawImageInfo(TypedDict): - """A info about an image.""" +class RawImageMetadata(TypedDict): + """Metadata about an image.""" image: str id: str versions: list[str] created: Optional[str] + compliant: Optional[bool] class RawResult(TypedDict): @@ -73,7 +74,7 @@ class RawResult(TypedDict): servers: list[str] clients: list[str] urls: dict[str, str] - versions: Optional[dict[str, RawImageInfo]] + images: Optional[dict[str, RawImageMetadata]] tests: dict[str, Union[RawTestDescr, RawMeasurementDescr]] quic_draft: int quic_version: str @@ -97,9 +98,27 @@ class Implementation: url: str role: ImplementationRole + image: Optional[str] = None image_id: Optional[str] = None - versions: Optional[list[str]] = None - created: Optional[datetime] = None + image_versions: Optional[list[str]] = None + image_created: Optional[datetime] = None + compliant: Optional[bool] = None + + def img_metadata_json(self) -> Optional[RawImageMetadata]: + if not self.image or not self.image_id: + return None + + return { + "image": self.image, + "id": self.image_id, + "versions": self.image_versions or [], + "created": ( + self.image_created.strftime("%Y-%m-%d %H:%M") + if self.image_created + else None + ), + "compliant": self.compliant, + } @dataclass(frozen=True) @@ -374,13 +393,13 @@ class Result: def log_dir(self, value: UrlOrPath): self.raw_data["log_dir"] = str(value) - def get_image_info( + def get_image_metadata( self, impl: Union[str, Implementation] - ) -> Union[RawImageInfo, dict]: - versions = self.raw_data.get("versions", {}) or {} + ) -> Union[RawImageMetadata, dict]: + images = self.raw_data.get("images", {}) or {} impl_name = impl if isinstance(impl, str) else impl.name - return versions.get(impl_name, {}) + return images.get(impl_name, {}) def _update_implementations( self, value: list[Implementation], role: ImplementationRole @@ -388,7 +407,7 @@ class Result: assert role != ImplementationRole.BOTH for impl in value: - img_info = self.get_image_info(impl.name) + img_info = self.get_image_metadata(impl.name) if impl.name not in self.raw_data["urls"]: self.raw_data["urls"][impl.name] = impl.url @@ -429,18 +448,18 @@ class Result: ) for name in lookup: - versions = self.raw_data.get("versions") or {} - img_info = versions.get(name, {}) - created_raw: Optional[str] = img_info.get("created") + img_metadata = self.get_image_metadata(name) + created_raw: Optional[str] = img_metadata.get("created") created = parse_date(created_raw) if created_raw else None ret.append( Implementation( name=name, url=self.raw_data["urls"][name], role=ImplementationRole.BOTH if name in lookup_other else role, - image_id=img_info.get("id"), - versions=img_info.get("versions"), - created=created, + image_id=img_metadata.get("id"), + image_versions=img_metadata.get("versions"), + image_created=created, + compliant=img_metadata.get("compliant"), ) ) @@ -467,10 +486,13 @@ class Result: self._update_implementations(value, role=ImplementationRole.CLIENT) @property - def implementations(self) -> frozenset[Implementation]: - """Return a list of involved implementations.""" + def implementations(self) -> dict[str, Implementation]: + """Return a mapping of involved implementations.""" - return frozenset(self.servers) | frozenset(self.clients) + return { + **{impl.name: impl for impl in self.servers}, + **{impl.name: impl for impl in self.clients}, + } @property def tests(self) -> dict[str, Union[TestDescription, MeasurmentDescription]]: @@ -841,10 +863,41 @@ class Result: [test.to_raw() for test in meass_merged[server][client].values()] ) - urls = { - **{impl.name: impl.url for impl in self.implementations}, - **{impl.name: impl.url for impl in other.implementations}, - } + urls = dict[str, str]() + images = dict[str, RawImageMetadata]() + + for name, impl in self.implementations.items(): + own_img_metadata = impl.img_metadata_json() + common_img_metadata: Optional[RawImageMetadata] = None + + if name in other.implementations.keys(): + other_impl = other.implementations[name] + assert impl.url == other_impl.url + other_img_metadata = other_impl.img_metadata_json() + + if own_img_metadata and other_img_metadata: + assert own_img_metadata == other_img_metadata + common_img_metadata = own_img_metadata + elif own_img_metadata and not other_img_metadata: + common_img_metadata = own_img_metadata + elif not own_img_metadata and other_img_metadata: + common_img_metadata = other_img_metadata + else: + common_img_metadata = own_img_metadata + + urls[name] = impl.url + + if common_img_metadata: + images[name] = common_img_metadata + + for name, impl in other.implementations.items(): + if name not in self.implementations.keys(): + urls[name] = impl.url + img_metadata = impl.img_metadata_json() + + if img_metadata: + images[name] = img_metadata + test_descriptions: dict[str, Union[RawTestDescr, RawMeasurementDescr]] = { **{abbr: test.to_raw() for abbr, test in self.tests.items()}, **{abbr: test.to_raw() for abbr, test in other.tests.items()}, @@ -857,6 +910,7 @@ class Result: "servers": servers_merged, "clients": clients_merged, "urls": urls, + "images": images, "tests": test_descriptions, "quic_draft": self.quic_draft, "quic_version": hex(other.quic_version),