252 lines
9.4 KiB
Python
252 lines
9.4 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.
|
|
|
|
##############################################################################
|
|
|
|
"""
|
|
Apply delta YAML to base architecture to produce target architecture.
|
|
Usage: python apply_config_deltas.py <base_arch_dir> <delta_yaml> <output_dir>
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import shutil
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any, Optional, Union
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
|
if str(PROJECT_ROOT) not in sys.path:
|
|
sys.path.insert(0, str(PROJECT_ROOT))
|
|
|
|
from config_management import utils_ruamel as cm_utils # noqa: E402
|
|
|
|
|
|
def find_table(config: dict, table_id: Any) -> Optional[dict]:
|
|
"""Find and return the table with given id, or None."""
|
|
for item in config.get("Panel Config", {}).get("data source", []):
|
|
table = item.get("metric_table")
|
|
if isinstance(table, dict) and table.get("id") == table_id:
|
|
return table
|
|
return None
|
|
|
|
|
|
def add_table(config: dict, metric_table: dict) -> None:
|
|
"""Add entire new table to config."""
|
|
config.setdefault("Panel Config", {}).setdefault("data source", []).append({
|
|
"metric_table": metric_table
|
|
})
|
|
print(f"Added table: {metric_table.get('id')} - {metric_table.get('title')}")
|
|
|
|
|
|
def add_metrics(config: dict, table_id: Any, metrics: list[dict]) -> None:
|
|
"""Add metrics to existing table."""
|
|
table = find_table(config, table_id)
|
|
if table is None:
|
|
print(f"WARNING: Table {table_id} not found for metric addition")
|
|
return
|
|
|
|
table.setdefault("metric", {})
|
|
for metric_dict in metrics:
|
|
for metric_name, metric_data in metric_dict.items():
|
|
table["metric"][metric_name] = metric_data
|
|
print(f"Added metric: {metric_name} to table {table_id}")
|
|
|
|
|
|
def delete_table(config: dict, table_id: Any) -> None:
|
|
"""Remove entire table from config."""
|
|
data_source = config.get("Panel Config", {}).get("data source", [])
|
|
for idx, item in enumerate(list(data_source)):
|
|
table = item.get("metric_table")
|
|
if isinstance(table, dict) and table.get("id") == table_id:
|
|
del data_source[idx]
|
|
print(f"Deleted table: {table_id}")
|
|
return
|
|
print(f"WARNING: Table {table_id} not found for deletion")
|
|
|
|
|
|
def delete_metrics(config: dict, table_id: Any, metrics: list[dict]) -> None:
|
|
"""Remove specific metrics from table."""
|
|
table = find_table(config, table_id)
|
|
if table is None or "metric" not in table:
|
|
print(f"WARNING: Table {table_id} not found or has no metrics")
|
|
return
|
|
|
|
for metric_dict in metrics:
|
|
for metric_name in metric_dict.keys():
|
|
if metric_name in table["metric"]:
|
|
del table["metric"][metric_name]
|
|
print(f"Deleted metric: {metric_name} from table {table_id}")
|
|
|
|
|
|
def modify_metrics(config: dict, table_id: Any, metrics: list[dict]) -> None:
|
|
"""Modify specific fields in existing metrics."""
|
|
table = find_table(config, table_id)
|
|
if table is None or "metric" not in table:
|
|
print(f"WARNING: Table {table_id} not found or has no metrics")
|
|
return
|
|
|
|
for metric_dict in metrics:
|
|
for metric_name, new_fields in metric_dict.items():
|
|
if metric_name not in table["metric"]:
|
|
print(f"WARNING: Metric '{metric_name}' not found in table {table_id}")
|
|
continue
|
|
for field_name, field_value in new_fields.items():
|
|
table["metric"][metric_name][field_name] = field_value
|
|
print(f"Modified {metric_name}.{field_name} in table {table_id}")
|
|
|
|
|
|
def add_descriptions(config: dict, descriptions: dict) -> None:
|
|
"""Add metric descriptions to config."""
|
|
md = config["Panel Config"].setdefault("metrics_description", {})
|
|
|
|
for metric_name, desc_data in descriptions.items():
|
|
md[metric_name] = dict(desc_data) if isinstance(desc_data, dict) else desc_data
|
|
|
|
print(f"Added description: {metric_name}")
|
|
|
|
|
|
def delete_descriptions(config: dict, descriptions: dict) -> None:
|
|
"""Remove metric descriptions from config."""
|
|
md = config["Panel Config"].setdefault("metrics_description", {})
|
|
for metric_name in descriptions.keys():
|
|
if metric_name in md:
|
|
del md[metric_name]
|
|
print(f"Deleted description: {metric_name}")
|
|
|
|
|
|
def modify_descriptions(config: dict, descriptions: dict) -> None:
|
|
"""Modify metric descriptions in config."""
|
|
md = config["Panel Config"].setdefault("metrics_description", {})
|
|
|
|
for metric_name, desc_data in descriptions.items():
|
|
if isinstance(desc_data, dict):
|
|
new_dict = {}
|
|
for k, v in desc_data.items():
|
|
new_dict[k] = v
|
|
md[metric_name] = new_dict
|
|
else:
|
|
md[metric_name] = desc_data
|
|
|
|
print(f"Added description: {metric_name}")
|
|
|
|
|
|
def apply_changes(config: dict, changes: list[dict], category: str) -> None:
|
|
"""Apply delta changes to configuration."""
|
|
for change in changes:
|
|
mt = change.get("metric_table")
|
|
if mt:
|
|
table_id = mt.get("id")
|
|
|
|
if category == "Addition":
|
|
if "metrics" in mt:
|
|
add_metrics(config, table_id, mt["metrics"])
|
|
elif "metric" in mt:
|
|
add_table(config, mt)
|
|
|
|
elif category == "Deletion":
|
|
if "metrics" in mt:
|
|
delete_metrics(config, table_id, mt["metrics"])
|
|
else:
|
|
delete_table(config, table_id)
|
|
|
|
elif category == "Modification":
|
|
if "metrics" in mt:
|
|
modify_metrics(config, table_id, mt["metrics"])
|
|
|
|
descriptions = change.get("metric_descriptions", {})
|
|
if descriptions:
|
|
if category == "Addition":
|
|
add_descriptions(config, descriptions)
|
|
elif category == "Deletion":
|
|
delete_descriptions(config, descriptions)
|
|
elif category == "Modification":
|
|
modify_descriptions(config, descriptions)
|
|
|
|
|
|
def apply_delta(
|
|
base_dir: Union[str, Path],
|
|
delta_file: Union[str, Path],
|
|
output_dir: Union[str, Path],
|
|
) -> None:
|
|
"""Apply delta YAML to all files in base directory."""
|
|
delta = cm_utils.load_yaml(delta_file, round_trip=True)
|
|
output_path = Path(output_dir)
|
|
output_path.mkdir(parents=True, exist_ok=True)
|
|
|
|
changes_by_panel: dict[Any, dict[str, list[dict]]] = {}
|
|
for category in ("Addition", "Deletion", "Modification"):
|
|
for change in delta.get(category, []):
|
|
panel_id = change.get("Panel Config", {}).get("id")
|
|
panel_bucket = changes_by_panel.setdefault(
|
|
panel_id, {"Addition": [], "Deletion": [], "Modification": []}
|
|
)
|
|
panel_bucket[category].append(change)
|
|
|
|
base_path = Path(base_dir)
|
|
for yaml_file in base_path.glob("*.yaml"):
|
|
config = cm_utils.load_yaml(yaml_file, round_trip=True)
|
|
panel_id = config.get("Panel Config", {}).get("id")
|
|
|
|
if panel_id in changes_by_panel:
|
|
print(f"\nApplying deltas to {yaml_file.name} (Panel ID: {panel_id})")
|
|
# Process in safe order: deletions -> modifications -> additions
|
|
for category in ("Deletion", "Modification", "Addition"):
|
|
if changes_by_panel[panel_id][category]:
|
|
apply_changes(
|
|
config, changes_by_panel[panel_id][category], category
|
|
)
|
|
|
|
cm_utils.strip_existing_header(config)
|
|
cm_utils.save_yaml(config, output_path / yaml_file.name)
|
|
print(f"Saved: {yaml_file.name}")
|
|
else:
|
|
shutil.copy(yaml_file, output_path / yaml_file.name)
|
|
|
|
|
|
def main() -> None:
|
|
if len(sys.argv) != 4:
|
|
print(
|
|
"Usage: python apply_config_deltas.py "
|
|
"<base_arch_dir> <delta_yaml> <output_dir>"
|
|
)
|
|
sys.exit(1)
|
|
|
|
base_dir, delta_file, output_dir = sys.argv[1:4]
|
|
|
|
if not Path(base_dir).is_dir():
|
|
print(f"Error: {base_dir} is not a directory")
|
|
sys.exit(1)
|
|
|
|
if not Path(delta_file).is_file():
|
|
print(f"Error: {delta_file} is not a file")
|
|
sys.exit(1)
|
|
|
|
apply_delta(base_dir, delta_file, output_dir)
|
|
print("\nDelta application complete!")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|