361 lines
13 KiB
Python
361 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
##############################################################################
|
|
# MIT License
|
|
#
|
|
# Copyright (c) 2025 Advanced Micro Devices, Inc. All Rights Reserved.
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
# THE SOFTWARE.
|
|
|
|
##############################################################################
|
|
|
|
"""
|
|
Analysis Config Differentiation Script
|
|
Generates differences from curr arch directory to prev arch directory.
|
|
Output shows what needs to change in prev arch to match curr arch.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
try:
|
|
from . import utils as cm_utils
|
|
except Exception:
|
|
repo_root = Path(__file__).resolve().parents[1]
|
|
if str(repo_root) not in sys.path:
|
|
sys.path.insert(0, str(repo_root))
|
|
try:
|
|
import config_management.utils as cm_utils # type: ignore
|
|
except Exception:
|
|
import utils as cm_utils # type: ignore
|
|
|
|
AUTOGEN_TEXT = (
|
|
"# AUTOGENERATED FILE. Only edit for testing purposes, not for development. "
|
|
"Generated by tools/config_management/generate_config_deltas.py\n"
|
|
)
|
|
|
|
|
|
def get_metric_tables(data: dict) -> list[dict]:
|
|
"""Extract all metric tables from data source."""
|
|
tables: list[dict] = []
|
|
for item in data.get("Panel Config", {}).get("data source", []):
|
|
mt = item.get("metric_table")
|
|
if isinstance(mt, dict):
|
|
tables.append(mt)
|
|
return tables
|
|
|
|
|
|
def get_metric_descriptions(data: dict) -> dict:
|
|
"""Extract metric descriptions from panel config."""
|
|
return data.get("Panel Config", {}).get("metrics_description", {}) or {}
|
|
|
|
|
|
def compare_metrics(
|
|
prev_metrics: dict, curr_metrics: dict
|
|
) -> tuple[list[dict], list[dict], list[dict]]:
|
|
"""Compare metrics and return (additions, deletions, modifications)."""
|
|
prev_keys = set(prev_metrics.keys())
|
|
curr_keys = set(curr_metrics.keys())
|
|
|
|
additions = [{name: curr_metrics[name]} for name in sorted(curr_keys - prev_keys)]
|
|
deletions = [{name: prev_metrics[name]} for name in sorted(prev_keys - curr_keys)]
|
|
|
|
modifications: list[dict] = []
|
|
for name in sorted(prev_keys & curr_keys):
|
|
if prev_metrics[name] != curr_metrics[name]:
|
|
all_fields = set(prev_metrics[name].keys()) | set(curr_metrics[name].keys())
|
|
modified_fields = {
|
|
field: curr_metrics[name].get(field)
|
|
for field in all_fields
|
|
if prev_metrics[name].get(field) != curr_metrics[name].get(field)
|
|
}
|
|
if modified_fields:
|
|
modifications.append({name: modified_fields})
|
|
|
|
return additions, deletions, modifications
|
|
|
|
|
|
def compare_descriptions(
|
|
prev_descriptions: dict, curr_descriptions: dict
|
|
) -> tuple[dict, dict, dict]:
|
|
"""
|
|
Compare metric descriptions and return (additions, deletions, modifications).
|
|
Values are dicts with 'plain' and 'rst'.
|
|
"""
|
|
prev_keys = set(prev_descriptions.keys())
|
|
curr_keys = set(curr_descriptions.keys())
|
|
|
|
additions: dict = {}
|
|
deletions: dict = {}
|
|
modifications: dict = {}
|
|
|
|
for name in sorted(curr_keys - prev_keys):
|
|
desc = curr_descriptions[name]
|
|
additions[name] = (
|
|
desc if isinstance(desc, dict) else {"plain": desc, "rst": desc}
|
|
)
|
|
|
|
for name in sorted(prev_keys - curr_keys):
|
|
desc = prev_descriptions[name]
|
|
deletions[name] = (
|
|
desc if isinstance(desc, dict) else {"plain": desc, "rst": desc}
|
|
)
|
|
|
|
for name in sorted(prev_keys & curr_keys):
|
|
prev_desc = prev_descriptions[name]
|
|
curr_desc = curr_descriptions[name]
|
|
|
|
prev_plain = (
|
|
prev_desc if isinstance(prev_desc, str) else prev_desc.get("plain", "")
|
|
)
|
|
curr_plain = (
|
|
curr_desc if isinstance(curr_desc, str) else curr_desc.get("plain", "")
|
|
)
|
|
|
|
prev_rst = (
|
|
prev_desc
|
|
if isinstance(prev_desc, str)
|
|
else prev_desc.get("rst", prev_plain)
|
|
)
|
|
curr_rst = (
|
|
curr_desc
|
|
if isinstance(curr_desc, str)
|
|
else curr_desc.get("rst", curr_plain)
|
|
)
|
|
|
|
if prev_plain != curr_plain or prev_rst != curr_rst:
|
|
modifications[name] = {"plain": curr_plain, "rst": curr_rst}
|
|
|
|
return additions, deletions, modifications
|
|
|
|
|
|
def compare_tables(
|
|
prev_tables: list[dict], curr_tables: list[dict]
|
|
) -> tuple[list[dict], list[dict], list[dict]]:
|
|
"""Compare tables and return (additions, deletions, modifications)."""
|
|
prev_dict = {t["id"]: t for t in prev_tables}
|
|
curr_dict = {t["id"]: t for t in curr_tables}
|
|
|
|
prev_ids = set(prev_dict.keys())
|
|
curr_ids = set(curr_dict.keys())
|
|
|
|
additions: list[dict] = []
|
|
deletions: list[dict] = []
|
|
modifications: list[dict] = []
|
|
|
|
additions.extend(curr_dict[tid] for tid in sorted(curr_ids - prev_ids))
|
|
deletions.extend(prev_dict[tid] for tid in sorted(prev_ids - curr_ids))
|
|
|
|
for tid in sorted(prev_ids & curr_ids):
|
|
prev_metrics = prev_dict[tid].get("metric", {}) or {}
|
|
curr_metrics = curr_dict[tid].get("metric", {}) or {}
|
|
|
|
metric_adds, metric_dels, metric_mods = compare_metrics(
|
|
prev_metrics, curr_metrics
|
|
)
|
|
|
|
if metric_adds:
|
|
additions.append({
|
|
"id": tid,
|
|
"title": curr_dict[tid].get("title"),
|
|
"metrics": metric_adds,
|
|
})
|
|
if metric_dels:
|
|
deletions.append({
|
|
"id": tid,
|
|
"title": prev_dict[tid].get("title"),
|
|
"metrics": metric_dels,
|
|
})
|
|
if metric_mods:
|
|
modifications.append({
|
|
"id": tid,
|
|
"title": curr_dict[tid].get("title"),
|
|
"metrics": metric_mods,
|
|
})
|
|
|
|
return additions, deletions, modifications
|
|
|
|
|
|
def format_metric_fields(metric_data: dict) -> list[str]:
|
|
"""Format metric fields as YAML lines."""
|
|
lines: list[str] = []
|
|
for field_name, field_value in metric_data.items():
|
|
if isinstance(field_value, str) and (
|
|
"\n" in field_value or len(field_value) > 80
|
|
):
|
|
lines.append(f" {field_name}: |")
|
|
lines.extend(
|
|
f" {line}" for line in field_value.split("\n")
|
|
)
|
|
else:
|
|
lines.append(f" {field_name}: {field_value}")
|
|
return lines
|
|
|
|
|
|
def format_description_fields(desc_data: dict) -> list[str]:
|
|
"""Format description fields as YAML lines."""
|
|
lines: list[str] = []
|
|
for field_name, field_value in desc_data.items():
|
|
if isinstance(field_value, str) and (
|
|
"\n" in field_value or len(field_value) > 80
|
|
):
|
|
lines.append(f" {field_name}: |")
|
|
lines.extend(f" {line}" for line in field_value.split("\n"))
|
|
else:
|
|
lines.append(f" {field_name}: {field_value}")
|
|
return lines
|
|
|
|
|
|
def format_output(combined_diff: dict) -> str:
|
|
"""Format the diff dictionary into a YAML string."""
|
|
lines: list[str] = []
|
|
for category in ("Addition", "Deletion", "Modification"):
|
|
lines.append(f"{category}:")
|
|
if not combined_diff.get(category):
|
|
lines.append(" []")
|
|
lines.append("")
|
|
continue
|
|
|
|
for panel_item in combined_diff[category]:
|
|
pc = panel_item["panel_config"]
|
|
lines.extend([
|
|
" - Panel Config:",
|
|
f" id: {pc['id']}",
|
|
f" title: {pc['title']}",
|
|
])
|
|
|
|
if panel_item.get("metric_tables"):
|
|
lines.append(" metric_tables:")
|
|
for mt in panel_item["metric_tables"]:
|
|
lines.extend([
|
|
" - metric_table:",
|
|
f" id: {mt['id']}",
|
|
f" title: {mt['title']}",
|
|
" metrics:",
|
|
])
|
|
metrics_to_format = mt.get("metrics") or [
|
|
{name: data} for name, data in (mt.get("metric") or {}).items()
|
|
]
|
|
for metric in metrics_to_format:
|
|
for metric_name, metric_data in metric.items():
|
|
lines.append(f" - {metric_name}:")
|
|
lines.extend(format_metric_fields(metric_data))
|
|
|
|
if panel_item.get("metric_descriptions"):
|
|
lines.append(" metric_descriptions:")
|
|
for metric_name, desc_data in panel_item["metric_descriptions"].items():
|
|
lines.append(f" {metric_name}:")
|
|
lines.extend(format_description_fields(desc_data))
|
|
|
|
lines.append("")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def main() -> None:
|
|
if len(sys.argv) != 3:
|
|
print("Usage: python generate_config_deltas.py <curr_arch_dir> <prev_arch_dir>")
|
|
sys.exit(1)
|
|
|
|
curr_arch_dir = Path(sys.argv[1])
|
|
prev_arch_dir = Path(sys.argv[2])
|
|
|
|
if not curr_arch_dir.is_dir() or not prev_arch_dir.is_dir():
|
|
print("Error: Both arguments must be directories")
|
|
sys.exit(1)
|
|
|
|
curr_files = {f.name for f in curr_arch_dir.glob("*.yaml")}
|
|
prev_files = {f.name for f in prev_arch_dir.glob("*.yaml")}
|
|
common_files = curr_files & prev_files
|
|
|
|
if not common_files:
|
|
print("Error: No common YAML files found")
|
|
sys.exit(1)
|
|
|
|
print(f"Comparing {len(common_files)} files...")
|
|
|
|
combined_diff = {"Addition": [], "Deletion": [], "Modification": []}
|
|
|
|
for filename in sorted(common_files):
|
|
curr_data = cm_utils.load_yaml(curr_arch_dir / filename)
|
|
prev_data = cm_utils.load_yaml(prev_arch_dir / filename)
|
|
|
|
curr_pc = curr_data.get("Panel Config", {}) or {}
|
|
prev_pc = prev_data.get("Panel Config", {}) or {}
|
|
|
|
curr_tables = get_metric_tables(curr_data)
|
|
prev_tables = get_metric_tables(prev_data)
|
|
|
|
curr_descriptions = get_metric_descriptions(curr_data)
|
|
prev_descriptions = get_metric_descriptions(prev_data)
|
|
|
|
table_adds, table_dels, table_mods = compare_tables(prev_tables, curr_tables)
|
|
desc_adds, desc_dels, desc_mods = compare_descriptions(
|
|
prev_descriptions, curr_descriptions
|
|
)
|
|
|
|
if table_adds or desc_adds:
|
|
entry = {
|
|
"panel_config": {"id": curr_pc.get("id"), "title": curr_pc.get("title")}
|
|
}
|
|
if table_adds:
|
|
entry["metric_tables"] = table_adds
|
|
if desc_adds:
|
|
entry["metric_descriptions"] = desc_adds
|
|
combined_diff["Addition"].append(entry)
|
|
|
|
if table_dels or desc_dels:
|
|
entry = {
|
|
"panel_config": {"id": prev_pc.get("id"), "title": prev_pc.get("title")}
|
|
}
|
|
if table_dels:
|
|
entry["metric_tables"] = table_dels
|
|
if desc_dels:
|
|
entry["metric_descriptions"] = desc_dels
|
|
combined_diff["Deletion"].append(entry)
|
|
|
|
if table_mods or desc_mods:
|
|
entry = {
|
|
"panel_config": {"id": curr_pc.get("id"), "title": curr_pc.get("title")}
|
|
}
|
|
if table_mods:
|
|
entry["metric_tables"] = table_mods
|
|
if desc_mods:
|
|
entry["metric_descriptions"] = desc_mods
|
|
combined_diff["Modification"].append(entry)
|
|
|
|
output = AUTOGEN_TEXT + format_output(combined_diff)
|
|
|
|
print("\n" + "=" * 80)
|
|
print("COMBINED DIFF OUTPUT:")
|
|
print("=" * 80)
|
|
print(output)
|
|
|
|
output_dir = prev_arch_dir / "config_delta"
|
|
output_dir.mkdir(exist_ok=True)
|
|
output_file = output_dir / f"{curr_arch_dir.name}_diff.yaml"
|
|
with open(output_file, "w") as f:
|
|
f.write(output)
|
|
|
|
print(f"\nDiff written to: {output_file}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|