Skip to content

k4bench.provenance.diff

k4bench.provenance.diff

Diff two Key4hep stacks — which packages moved, and to what.

The unit of comparison is the release (the nightly tag), not the day a benchmark ran. The nightly build lags: on a day with no new nightly the benchmark sources the newest one available, so several consecutive run dates routinely share one identical stack. Comparing run dates would invent changes that never happened.

An empty diff means no recorded changes. Callers distinguish complete captures from missing provenance before claiming that the endpoint stacks are identical.

PackageChange dataclass

PackageChange(name: str, base_commit: str | None, head_commit: str | None, version: str = '', repo_url: str | None = None, base_version: str | None = None, head_version: str | None = None, head_githash: str | None = None, kind: str = 'head', loaded: bool | None = None)

One package's difference between two stacks.

A recorded version or commit establishes presence at an endpoint. HEAD revisions can be unknown even when a manifest records the package.

repo property

repo: RepoRef | None

The upstream repository, or None on an unrecognized forge.

compare_url property

compare_url: str | None

HEAD forge comparison or loaded pin's filtered build history.

package_key

package_key(name: str) -> str

A package name as both stacks can agree on it.

Spack lower-cases and hyphenates (dd4hep, fcc-config, k4projecttemplate) where LCG spells names as upstream does (DD4hep, fcc_config, k4_project_template); a window spanning a platform migration compares one of each.

Source code in k4bench/provenance/diff.py
def package_key(name: str) -> str:
    """A package name as both stacks can agree on it.

    Spack lower-cases and hyphenates (``dd4hep``, ``fcc-config``,
    ``k4projecttemplate``) where LCG spells names as upstream does (``DD4hep``,
    ``fcc_config``, ``k4_project_template``); a window spanning a platform
    migration compares one of each."""
    return name.lower().replace("-", "").replace("_", "")

diff_packages

diff_packages(base: dict, head: dict) -> list[PackageChange]

Packages that differ between two k4h_packages maps.

Changed packages come first (they are what a regression hunt is looking for), then added, then removed; alphabetical within each group. An empty result means the tracked git commits are identical.

Source code in k4bench/provenance/diff.py
def diff_packages(base: dict, head: dict) -> list[PackageChange]:
    """Packages that differ between two ``k4h_packages`` maps.

    Changed packages come first (they are what a regression hunt is looking
    for), then added, then removed; alphabetical within each group. An empty
    result means the tracked git commits are identical.
    """
    changes: list[PackageChange] = []
    for name, before, after in _paired(base, head):
        base_commit = (before or {}).get("commit")
        head_commit = (after or {}).get("commit")
        if _same_commit(base_commit, head_commit):
            continue
        # Prefer the head's metadata: it describes the stack as it is now.
        meta = after or before or {}
        changes.append(PackageChange(
            name=name,
            base_commit=base_commit,
            head_commit=head_commit,
            version=str(meta.get("version") or ""),
            repo_url=(after or {}).get("repo_url") or (before or {}).get("repo_url"),
        ))
    order = {CHANGED: 0, ADDED: 1, REMOVED: 2}
    return sorted(changes, key=lambda c: (order[c.status], c.name))

unchanged_packages

unchanged_packages(base: dict, head: dict) -> list[str]

Packages present in both stacks at the same commit.

Reported as a count rather than a table: "11 repos unchanged" is context a reader needs to size the diff, but eleven identical rows are not.

Source code in k4bench/provenance/diff.py
def unchanged_packages(base: dict, head: dict) -> list[str]:
    """Packages present in both stacks at the same commit.

    Reported as a count rather than a table: "11 repos unchanged" is context a
    reader needs to size the diff, but eleven identical rows are not.
    """
    return sorted(
        name for name, before, after in _paired(base, head)
        if before is not None and after is not None
        and _same_commit((before or {}).get("commit"), (after or {}).get("commit"))
    )

diff_stacks

diff_stacks(base: StackSnapshot, head: StackSnapshot) -> list[PackageChange]

Version/commit changes only; build hashes alone never manufacture a row.

Loaded means observed at either endpoint in this benchmark scope. If either capture is absent, an unobserved package's load status remains unknown.

Source code in k4bench/provenance/diff.py
def diff_stacks(base: StackSnapshot, head: StackSnapshot) -> list[PackageChange]:
    """Version/commit changes only; build hashes alone never manufacture a row.

    Loaded means observed at either endpoint in this benchmark scope. If either
    capture is absent, an unobserved package's load status remains unknown.
    """
    loaded_names = (base.loaded or frozenset()) | (head.loaded or frozenset())
    loaded = {package_key(name) for name in loaded_names}
    unobserved = False if base.loaded is not None and head.loaded is not None else None
    changes = []
    for name, before, after in _paired(base.packages, head.packages):
        if before is not None and after is not None:
            if _code_changed(before, after) is not True:
                continue
        # A HEAD/pin transition is represented by versions and any known commit.
        versions = {p.get("version") for p in (before, after) if p is not None}
        b, h = before or {}, after or {}
        changes.append(PackageChange(
            name=name, base_commit=b.get("commit"), head_commit=h.get("commit"),
            version=h.get("version", b.get("version", "")),
            repo_url=h.get("repo_url") or b.get("repo_url"),
            base_version=b.get("version"), head_version=h.get("version"),
            head_githash=h.get("githash"), kind="head" if versions == {"HEAD"} else "pin",
            loaded=True if package_key(name) in loaded else unobserved,
        ))
    if base.datasets is not None and head.datasets is not None:
        for name in sorted(base.datasets.keys() | head.datasets.keys()):
            before, after = base.datasets.get(name), head.datasets.get(name)
            if before != after:
                changes.append(PackageChange(
                    name, None, None, base_version=before, head_version=after, kind="dataset",
                    loaded=True if "geant4" in loaded else unobserved,
                ))
    if base.os and head.os and base.os != head.os:
        changes.append(PackageChange(
            "OS", None, None, base_version=base.os, head_version=head.os, kind="os", loaded=True,
        ))
    order = {CHANGED: 0, ADDED: 1, REMOVED: 2}
    return sorted(changes, key=lambda c: (c.loaded is not True, order[c.status], c.name.lower()))

hash_only_rebuilds

hash_only_rebuilds(base: StackSnapshot, head: StackSnapshot) -> int

Count changed build hashes only when the recorded code stayed the same.

Source code in k4bench/provenance/diff.py
def hash_only_rebuilds(base: StackSnapshot, head: StackSnapshot) -> int:
    """Count changed build hashes only when the recorded code stayed the same."""
    count = 0
    for _, before, after in _paired(base.packages, head.packages):
        if before is None or after is None:
            continue
        base_hash, head_hash = before.get("hash"), after.get("hash")
        if not base_hash or not head_hash or base_hash == head_hash:
            continue
        if _code_changed(before, after) is False:
            count += 1
    return count