Skip to content

Commit

Permalink
Add CPU vulnerabilities to showdiff
Browse files Browse the repository at this point in the history
The code reponsible for generating profile headers and metadata
in showdiff was unified.
  • Loading branch information
JiriPavela committed Nov 3, 2024
1 parent 8ed8f17 commit e44bee2
Show file tree
Hide file tree
Showing 20 changed files with 336 additions and 217 deletions.
11 changes: 11 additions & 0 deletions perun/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
ExternalEditorErrorException,
)
from perun.utils.structs.common_structs import Executable
from perun.utils.structs.diff_structs import HeaderDisplayStyle
from perun import fuzz as fuzz
import perun.postprocess
import perun.profile.helpers as profiles
Expand Down Expand Up @@ -704,6 +705,16 @@ def show(ctx: click.Context, profile: Profile, **_: Any) -> None:
default=False,
help="Creates self-contained outputs usable in offline environments (default=False).",
)
@click.option(
"--display-style",
"-d",
type=click.Choice(HeaderDisplayStyle.supported()),
default=HeaderDisplayStyle.default(),
callback=cli_kit.set_config_option_from_flag(perun_config.runtime, "showdiff.display_style"),
help="Selects the display style of profile header. The 'full' option displays all provided "
"headers, while the 'diff' option shows only headers with different values "
f"(default={HeaderDisplayStyle.default()}).",
)
@click.pass_context
def showdiff(ctx: click.Context, **kwargs: Any) -> None:
"""Interprets the difference of selected two profiles.
Expand Down
2 changes: 1 addition & 1 deletion perun/profile/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ from .helpers import (
get_default_independent_variable as get_default_independent_variable,
get_default_dependent_variable as get_default_dependent_variable,
ProfileInfo as ProfileInfo,
ProfileMetadata as ProfileMetadata,
ProfileHeaderEntry as ProfileHeaderEntry,
)

from .imports import (
Expand Down
4 changes: 2 additions & 2 deletions perun/profile/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,13 +462,13 @@ def all_stats(self) -> Iterable[stats.ProfileStat]:
for stat in self._storage.get("stats", {}):
yield stats.ProfileStat.from_profile(stat)

def all_metadata(self) -> Iterable[helpers.ProfileMetadata]:
def all_metadata(self) -> Iterable[helpers.ProfileHeaderEntry]:
"""Iterates through all the metadata records in the profile.
:return: iterable of all metadata records
"""
for entry in self._storage.get("metadata", {}):
yield helpers.ProfileMetadata.from_profile(entry)
yield helpers.ProfileHeaderEntry.from_profile(entry)

# TODO: discuss the intent of __len__ and possibly merge?
def resources_size(self) -> int:
Expand Down
53 changes: 33 additions & 20 deletions perun/profile/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import os
import re
import time
from typing import Any, TYPE_CHECKING
from typing import Any, TYPE_CHECKING, Union

# Third-Party Imports

Expand All @@ -44,6 +44,9 @@
if TYPE_CHECKING:
import types

# A tuple representation of the ProfileHeaderEntry object
ProfileHeaderTuple = tuple[str, Union[str, float], str, dict[str, Union[str, float]]]


PROFILE_COUNTER: int = 0
DEFAULT_SORT_KEY: str = "time"
Expand Down Expand Up @@ -613,41 +616,51 @@ def is_compatible_with_profile(self, profile: profiles.Profile) -> bool:


@dataclasses.dataclass
class ProfileMetadata:
"""A representation of a single profile metadata entry.
class ProfileHeaderEntry:
"""A representation of a single profile header entry.
:ivar name: the name (key) of the metadata entry
:ivar value: the value of the metadata entry
:ivar description: detailed description of the metadata entry
:ivar name: the name (key) of the header entry
:ivar value: the value of the header entry
:ivar description: detailed description of the header entry
:ivar details: nested key: value data
"""

name: str
value: str | float
description: str = ""
details: dict[str, str | float] = dataclasses.field(default_factory=dict)

@classmethod
def from_string(cls, metadata: str) -> ProfileMetadata:
"""Constructs a ProfileMetadata object from a string representation.
def from_string(cls, header: str) -> ProfileHeaderEntry:
"""Constructs a `ProfileHeaderRecord` object from a string representation.
:param metadata: the string representation of a metadata entry
:param header: the string representation of a header entry
:return: the constructed ProfileMetadata object
:return: the constructed ProfileHeaderRecord object
"""
return cls(*metadata.split("|"))
split = header.split("|")
name = split[0]
value = common_kit.try_convert(split[1] if len(split) > 1 else "[empty]", [float, str])
desc = split[2] if len(split) > 2 else ProfileHeaderEntry.description
details: dict[str, str | float] = {}
for detail in split[4:]:
detail_key, detail_value = detail.split(maxsplit=1)
details[detail_key] = common_kit.try_convert(detail_value, [float, str])
return cls(name, value, desc, details)

@classmethod
def from_profile(cls, metadata: dict[str, Any]) -> ProfileMetadata:
"""Constructs a ProfileMetadata object from a dictionary representation used in Profile.
def from_profile(cls, header: dict[str, Any]) -> ProfileHeaderEntry:
"""Constructs a ProfileHeaderEntry object from a dictionary representation used in Profile.
:param metadata: the dictionary representation of a metadata entry
:param header: the dictionary representation of a header entry
:return: the constructed ProfileMetadata object
:return: the constructed ProfileHeaderEntry object
"""
return cls(**metadata)
return cls(**header)

def as_tuple(self) -> tuple[str, str | float, str]:
"""Converts the metadata object into a tuple.
def as_tuple(self) -> ProfileHeaderTuple:
"""Converts the header object into a tuple.
:return: the tuple representation of a metadata entry
:return: the tuple representation of a header entry
"""
return self.name, self.value, self.description
return self.name, self.value, self.description, self.details
36 changes: 20 additions & 16 deletions perun/profile/imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def import_elk_from_json(
import_dir = Path(config.lookup_key_recursively("import.dir", os.getcwd()))
resources: list[dict[str, Any]] = []
# Load the CLI-supplied metadata, if any
elk_metadata: dict[str, profile.ProfileMetadata] = {
elk_metadata: dict[str, profile.ProfileHeaderEntry] = {
data.name: data for data in _import_metadata(metadata, import_dir)
}

Expand Down Expand Up @@ -233,7 +233,7 @@ def import_perf_profile(

def import_elk_profile(
resources: list[dict[str, Any]],
metadata: dict[str, profile.ProfileMetadata],
metadata: dict[str, profile.ProfileHeaderEntry],
minor_version: MinorVersion,
save_to_index: bool = False,
**kwargs: Any,
Expand Down Expand Up @@ -319,7 +319,7 @@ def load_perf_file(filepath: Path) -> str:

def extract_from_elk(
elk_query: list[dict[str, Any]]
) -> tuple[list[dict[str, Any]], dict[str, profile.ProfileMetadata]]:
) -> tuple[list[dict[str, Any]], dict[str, profile.ProfileHeaderEntry]]:
"""For the given elk query, extracts resources and metadata.
For metadata, we consider any key that has only single value through the profile,
Expand All @@ -340,7 +340,7 @@ def extract_from_elk(
if not k.startswith("metric") and not k.startswith("benchmarking") and len(v) == 1
}

metadata = {k: profile.ProfileMetadata(k, res_counter[k].pop()) for k in metadata_keys}
metadata = {k: profile.ProfileHeaderEntry(k, res_counter[k].pop()) for k in metadata_keys}
resources = [
{
k: common_kit.try_convert(v, [int, float, str])
Expand Down Expand Up @@ -384,7 +384,7 @@ def get_machine_info(machine_info: str, import_dir: Path) -> dict[str, Any]:


def extract_machine_info_from_elk_metadata(
metadata: dict[str, profile.ProfileMetadata]
metadata: dict[str, profile.ProfileHeaderEntry]
) -> dict[str, Any]:
"""Extracts the parts of the profile that correspond to machine info.
Expand All @@ -396,19 +396,21 @@ def extract_machine_info_from_elk_metadata(
:return: machine info extracted from the profiles.
"""
machine_info: dict[str, Any] = {
"architecture": metadata.get("machine.arch", profile.ProfileMetadata("", "?")).value,
"architecture": metadata.get("machine.arch", profile.ProfileHeaderEntry("", "?")).value,
"system": str(
metadata.get("machine.os", profile.ProfileMetadata("", "?")).value
metadata.get("machine.os", profile.ProfileHeaderEntry("", "?")).value
).capitalize(),
"release": metadata.get("extra.machine.platform", profile.ProfileMetadata("", "?")).value,
"host": metadata.get("machine.hostname", profile.ProfileMetadata("", "?")).value,
"release": metadata.get(
"extra.machine.platform", profile.ProfileHeaderEntry("", "?")
).value,
"host": metadata.get("machine.hostname", profile.ProfileHeaderEntry("", "?")).value,
"cpu": {
"physical": "?",
"total": metadata.get("machine.cpu-cores", profile.ProfileMetadata("", "?")).value,
"total": metadata.get("machine.cpu-cores", profile.ProfileHeaderEntry("", "?")).value,
"frequency": "?",
},
"memory": {
"total_ram": metadata.get("machine.ram", profile.ProfileMetadata("", "?")).value,
"total_ram": metadata.get("machine.ram", profile.ProfileHeaderEntry("", "?")).value,
"swap": "?",
},
"boot_info": "?",
Expand All @@ -419,15 +421,17 @@ def extract_machine_info_from_elk_metadata(
return machine_info


def _import_metadata(metadata: tuple[str, ...], import_dir: Path) -> list[profile.ProfileMetadata]:
def _import_metadata(
metadata: tuple[str, ...], import_dir: Path
) -> list[profile.ProfileHeaderEntry]:
"""Parse the metadata entries from CLI and convert them to our internal representation.
:param import_dir: the import directory to use for relative metadata file paths.
:param metadata: a collection of metadata entries or JSON files.
:return: a collection of parsed and converted metadata objects
"""
p_metadata: list[profile.ProfileMetadata] = []
p_metadata: list[profile.ProfileHeaderEntry] = []
# Normalize the metadata string for parsing and/or opening the file
for metadata_str in map(str.strip, metadata):
if metadata_str.lower().endswith(".json"):
Expand All @@ -436,13 +440,13 @@ def _import_metadata(metadata: tuple[str, ...], import_dir: Path) -> list[profil
else:
# Add a single metadata entry parsed from its string representation
try:
p_metadata.append(profile.ProfileMetadata.from_string(metadata_str))
p_metadata.append(profile.ProfileHeaderEntry.from_string(metadata_str))
except TypeError:
log.warn(f"Ignoring invalid profile metadata string '{metadata_str}'.")
return p_metadata


def _parse_metadata_json(metadata_path: Path) -> list[profile.ProfileMetadata]:
def _parse_metadata_json(metadata_path: Path) -> list[profile.ProfileHeaderEntry]:
"""Parse a metadata JSON file into the metadata objects.
If the JSON file contains nested dictionaries, the hierarchical keys will be flattened.
Expand All @@ -458,7 +462,7 @@ def _parse_metadata_json(metadata_path: Path) -> list[profile.ProfileMetadata]:
return []
# Make sure we flatten the input
metadata_list = [
profile.ProfileMetadata(k, v)
profile.ProfileHeaderEntry(k, v)
for k, v in profile.all_items_of(json.load(metadata_handle))
]
log.minor_success(log.path_style(str(metadata_path)), "parsed")
Expand Down
9 changes: 7 additions & 2 deletions perun/templates/diff_view_datatables.html.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,14 @@

<div class="left column">
<h2 class="column-head">{{ lhs_tag }}</h2>
{{ profile_overview.overview_table('toggleSpecificationCollapse', 'left-specification-info', lhs_header, "Profile Specification") }}
{{ profile_overview.nested_overview_table('toggleSpecificationCollapse', 'left-specification-info', lhs_header, "Profile Specification") }}
<div style="margin: 0 10px;">&nbsp;</div>
</div>


<div class="right column">
<h2 class="column-head">{{ rhs_tag }}</h2>
{{ profile_overview.overview_table('toggleSpecificationCollapse', 'right-specification-info', rhs_header, "Profile Specification") }}
{{ profile_overview.nested_overview_table('toggleSpecificationCollapse', 'right-specification-info', rhs_header, "Profile Specification") }}
<div style="margin: 0 10px;">&nbsp;</div>
</div>

Expand Down Expand Up @@ -198,6 +198,11 @@
{% include 'dataTables.min.js' %}
{%- endif %}
{{ profile_overview.toggle_script('toggleSpecificationCollapse', 'left-specification-info', 'right-specification-info') }}
{% for (key, value, title, nested_values) in lhs_header %}
{% if nested_values %}
{{ profile_overview.toggle_nested_table('toggleSpecificationCollapse' ~ loop.index0, 'left-specification-info' ~ loop.index0, 'right-specification-info' ~ loop.index0) }}
{% endif %}
{% endfor %}
$(document).ready( function () {
var lhs = $("#table1").DataTable({
data: lhs_data.data,
Expand Down
18 changes: 14 additions & 4 deletions perun/templates/diff_view_flamegraph.html.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -105,24 +105,24 @@

<div class="left column">
<h2 class="column-head">{{ lhs_tag }}</h2>
{{ profile_overview.overview_table('toggleSpecificationCollapse', 'left-specification-info', lhs_header, "Profile Specification") }}
{{ profile_overview.nested_overview_table('toggleSpecificationCollapse', 'left-specification-info', lhs_header, "Profile Specification") }}
<div style="margin: 0 10px;">&nbsp;</div>
{{ profile_overview.nested_overview_table('toggleStatsCollapse', 'left-stats-info', lhs_stats, "Profile Stats") }}
<div style="margin: 0 10px;">&nbsp;</div>
{%- if rhs_metadata%}
{{ profile_overview.overview_table('toggleMetadataCollapse', 'left-metadata-info', lhs_metadata, "Profile Metadata") }}
{{ profile_overview.nested_overview_table('toggleMetadataCollapse', 'left-metadata-info', lhs_metadata, "Profile Metadata") }}
<div style="margin: 0 10px;">&nbsp;</div>
{%- endif %}
</div>

<div class="right column">
<h2 class="column-head">{{ rhs_tag }}</h2>
{{ profile_overview.overview_table('toggleSpecificationCollapse', 'right-specification-info', rhs_header, "Profile Specification") }}
{{ profile_overview.nested_overview_table('toggleSpecificationCollapse', 'right-specification-info', rhs_header, "Profile Specification") }}
<div style="margin: 0 10px;">&nbsp;</div>
{{ profile_overview.nested_overview_table('toggleStatsCollapse', 'right-stats-info', rhs_stats, "Profile Stats") }}
<div style="margin: 0 10px;">&nbsp;</div>
{%- if rhs_metadata%}
{{ profile_overview.overview_table('toggleMetadataCollapse', 'right-metadata-info', rhs_metadata, "Profile Metadata") }}
{{ profile_overview.nested_overview_table('toggleMetadataCollapse', 'right-metadata-info', rhs_metadata, "Profile Metadata") }}
<div style="margin: 0 10px;">&nbsp;</div>
{%- endif %}
</div>
Expand Down Expand Up @@ -187,6 +187,16 @@
{{ profile_overview.toggle_nested_table('toggleStatsCollapse' ~ loop.index0, 'left-stats-info' ~ loop.index0, 'right-stats-info' ~ loop.index0) }}
{% endif %}
{% endfor %}
{% for (key, value, title, nested_values) in lhs_header %}
{% if nested_values %}
{{ profile_overview.toggle_nested_table('toggleSpecificationCollapse' ~ loop.index0, 'left-specification-info' ~ loop.index0, 'right-specification-info' ~ loop.index0) }}
{% endif %}
{% endfor %}
{% for (key, value, title, nested_values) in lhs_header %}
{% if nested_values %}
{{ profile_overview.toggle_nested_table('toggleMetadataCollapse' ~ loop.index0, 'left-metadata-info' ~ loop.index0, 'right-metadata-info' ~ loop.index0) }}
{% endif %}
{% endfor %}
{{ accordion.script("table-row") }}
function switch_type() {
Expand Down
18 changes: 14 additions & 4 deletions perun/templates/diff_view_report.html.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -181,24 +181,24 @@

<div class="left column">
<h2 class="column-head">{{ lhs_tag }}</h2>
{{ profile_overview.overview_table('toggleSpecificationCollapse', 'left-specification-info', lhs_header, "Profile Specification") }}
{{ profile_overview.nested_overview_table('toggleSpecificationCollapse', 'left-specification-info', lhs_header, "Profile Specification") }}
<div style="margin: 0 10px;">&nbsp;</div>
{{ profile_overview.nested_overview_table('toggleStatsCollapse', 'left-stats-info', lhs_stats, "Profile Stats") }}
<div style="margin: 0 10px;">&nbsp;</div>
{%- if rhs_metadata%}
{{ profile_overview.overview_table('toggleMetadataCollapse', 'left-metadata-info', lhs_metadata, "Profile Metadata") }}
{{ profile_overview.nested_overview_table('toggleMetadataCollapse', 'left-metadata-info', lhs_metadata, "Profile Metadata") }}
<div style="margin: 0 10px;">&nbsp;</div>
{%- endif %}
</div>

<div class="right column">
<h2 class="column-head">{{ rhs_tag }}</h2>
{{ profile_overview.overview_table('toggleSpecificationCollapse', 'right-specification-info', rhs_header, "Profile Specification") }}
{{ profile_overview.nested_overview_table('toggleSpecificationCollapse', 'right-specification-info', rhs_header, "Profile Specification") }}
<div style="margin: 0 10px;">&nbsp;</div>
{{ profile_overview.nested_overview_table('toggleStatsCollapse', 'right-stats-info', rhs_stats, "Profile Stats") }}
<div style="margin: 0 10px;">&nbsp;</div>
{%- if rhs_metadata%}
{{ profile_overview.overview_table('toggleMetadataCollapse', 'right-metadata-info', rhs_metadata, "Profile Metadata") }}
{{ profile_overview.nested_overview_table('toggleMetadataCollapse', 'right-metadata-info', rhs_metadata, "Profile Metadata") }}
<div style="margin: 0 10px;">&nbsp;</div>
{%- endif %}
</div>
Expand Down Expand Up @@ -540,6 +540,16 @@
{{ profile_overview.toggle_nested_table('toggleStatsCollapse' ~ loop.index0, 'left-stats-info' ~ loop.index0, 'right-stats-info' ~ loop.index0) }}
{% endif %}
{% endfor %}
{% for (key, value, title, nested_values) in lhs_header %}
{% if nested_values %}
{{ profile_overview.toggle_nested_table('toggleSpecificationCollapse' ~ loop.index0, 'left-specification-info' ~ loop.index0, 'right-specification-info' ~ loop.index0) }}
{% endif %}
{% endfor %}
{% for (key, value, title, nested_values) in lhs_header %}
{% if nested_values %}
{{ profile_overview.toggle_nested_table('toggleMetadataCollapse' ~ loop.index0, 'left-metadata-info' ~ loop.index0, 'right-metadata-info' ~ loop.index0) }}
{% endif %}
{% endfor %}
{% for index in range(0, stat_list|length ) %}
{{ widgets.toggle_help('toggleFlameHelp_' + index|string, 'flame-help-' + index|string) }}
{% endfor %}
Expand Down
Loading

0 comments on commit e44bee2

Please sign in to comment.