[PATCH v2] config-tools: generate config_summary.rst


Li, Ziheng
 

From 94d699b2a983c8d5fc0d571324c4f0d42275df0d Mon Sep 17 00:00:00 2001
From: zihengL1 <ziheng.li@...>
Date: Fri, 26 Aug 2022 09:11:34 +0800
Subject: [PATCH v2] config-tools: generate config_summary.rst

This patch is modified and simplified on the iversion of V1 patch.
Changes are as follows:
1. V2 patch is modified and simplified on the version of V1 patch
2. Added Ivshmem information and vuart information in the PCI table.
3. At the basic information, extract and calculate the L3 cache size of
each VM from the XML file
4. Change the input / output file format to argparse format

Tracked-On: #8063
Signed-off-by: Ziheng Li <ziheng.li@...>
---
config_summary.py | 319 ++++++++++++++
.../scenario_config/config_summary.py | 392 ++++++++++++++++++
2 files changed, 711 insertions(+)
create mode 100644 config_summary.py
create mode 100644 misc/config_tools/scenario_config/config_summary.py

diff --git a/config_summary.py b/config_summary.py
new file mode 100644
index 000000000..f71cf2384
--- /dev/null
+++ b/config_summary.py
@@ -0,0 +1,319 @@
+from rstcloth import RstCloth
+from lxml import etree
+
+
+class GenerateRst:
+ io_port = {}
+ io_description = []
+
+ # Class initialization
+ def __init__(self, rst_file_name, file_access_mode, board_file_name, scenario_file_name) -> None:
+ self.file = open(rst_file_name, file_access_mode)
+ self.doc = RstCloth(self.file)
+ self.scenario_file_name = scenario_file_name
+ self.board_etree = etree.parse(board_file_name)
+ self.scenario_etree = etree.parse(scenario_file_name)
+
+ # The rst content is written in three parts according to the first level title
+ # 1. Hardware Resource Allocation 2. Inter-VM Connections 3. VM info
+ def write_configuration_rst(self):
+ self.doc.title(f'ACRN Scenario <{self.scenario_file_name}> - Datasheet')
+ self.doc.newline()
+ self.write_hardware_resource_allocation()
+ self.write_inter_vm_connections()
+ self.write_vms()
+ self.close_file()
+
+ # Write all info under the first level heading "Hardware Resource Allocation"
+ def write_hardware_resource_allocation(self):
+ self.doc.h1("Hardware Resource Allocation")
+ self.doc.newline()
+ self.write_pcpu()
+ self.write_shared_cache()
+
+ # Write physical CPUs info table
+ def write_pcpu(self):
+ self.doc.h2("Physical CPUs")
+ vm_name_list = self.get_vm_name()
+ vm_used_pcpu_info = self.get_vm_used_pcpu()
+ column_title, data_table = self.get_pcpu_table(vm_name_list, vm_used_pcpu_info)
+ self.doc.table(column_title, data_table)
+
+ # Get all VM names info from the scenario.xml
+ def get_vm_name(self):
+ vm_name_list = []
+ vm_node_list = self.scenario_etree.xpath("/acrn-config/vm")
+ for vm_node in vm_node_list:
+ vm_name_node = vm_node.xpath("name/text()")
+ vm_name_list.append("<VM " + f"{vm_node.attrib['id']} " + f"{vm_name_node[0]}>")
+ return vm_name_list
+
+ # Get the info about which PCUs are actually used by each VM from scenario.xml
+ def get_vm_used_pcpu(self):
+ vm_used_pcpu_info = {}
+ vm_node_list = self.scenario_etree.xpath("/acrn-config/vm")
+ for vm_node in vm_node_list:
+ vm_pcpu_id = []
+ vm_pcpu_id_node = vm_node.xpath("cpu_affinity/pcpu/pcpu_id")
+ if len(vm_pcpu_id_node) == 0:
+ vm_used_pcpu_info[int(vm_node.attrib['id'])] = []
+ continue
+ for pcpu_info in vm_pcpu_id_node:
+ vm_pcpu_id.append(int(pcpu_info.text))
+ vm_used_pcpu_info[int(vm_node.attrib['id'])] = vm_pcpu_id
+ return vm_used_pcpu_info
+
+ # get the column_title and datatable required by the physical CPUs table
+ @classmethod
+ def get_pcpu_table(cls, vm_name_list, vm_used_pcpu_info):
+ data_table = []
+ column_title = [" "]
+ actual_used_pcpu_list = []
+ for v in vm_used_pcpu_info.values():
+ for item in v:
+ if item not in actual_used_pcpu_list:
+ actual_used_pcpu_list.append(item)
+ actual_used_pcpu_list.sort()
+ for item in actual_used_pcpu_list:
+ column_title.append(str(item))
+ for vm_name in vm_name_list:
+ data_row = [vm_name]
+ for i in range(len(actual_used_pcpu_list)):
+ data_row.append(" ")
+ for item in vm_used_pcpu_info[vm_name_list.index(vm_name)]:
+ data_row[actual_used_pcpu_list.index(item) + 1] = " " + "*"
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Get all physical CPU information from board.xml
+ def get_pcpu(self):
+ pcpu_list = self.board_etree.xpath("/acrn-config/CPU_PROCESSOR_INFO/text()")[0].strip().split(',')
+ pcpu_list = list(map(int, pcpu_list))
+ return pcpu_list
+
+ def write_shared_cache(self):
+ self.doc.h2("Shared Cache")
+ self.doc.newline()
+ self.write_l2_shared_cache()
+ self.write_l3_shared_cache()
+
+ def write_l2_shared_cache(self):
+ column_title, data_table = self.get_vcpu_table(cache_level="2")
+ self.doc.h3("Level-2 Cache")
+ self.doc.table(column_title, data_table)
+ self.doc.content("Level-2 caches are local on all physical CPUs.")
+ self.doc.newline()
+
+ def write_l3_shared_cache(self):
+ column_title, data_table = self.get_vcpu_table(cache_level="3")
+ self.doc.h3("Level-3 Cache")
+ self.doc.table(column_title, data_table)
+ self.doc.note(name="note", content="Each cache chunk is 1MB.")
+
+ # Get used vcpu table
+ def get_vcpu_table(self, cache_level):
+ data_table = []
+ column_title = [" "]
+ pcpu_list = self.get_pcpu()
+ vm_vcpu_info = self.get_vm_used_vcpu(cache_level)
+ for pcpu_info in pcpu_list:
+ column_title.append(str(pcpu_info))
+ for vm_name, vcpu_id_list in vm_vcpu_info.items():
+ for vcpu_id in vcpu_id_list:
+ data_row = [vm_name + " vCPU " + str(vcpu_id)]
+ for i in range(len(pcpu_list)):
+ data_row.append(" ")
+ data_row[vcpu_id + 1] = " " + "*"
+ data_table.append(data_row)
+ return column_title, sorted(data_table, key=lambda x: x[-1])
+
+ # Get the vcpu info used by each VM from scenario.xml
+ def get_vm_used_vcpu(self, cache_level):
+ vm_vcpu_info = {}
+ cache_allocation_list = self.scenario_etree.xpath("/acrn-config/hv/CACHE_REGION/CACHE_ALLOCATION")
+ for cache_allocation_node in cache_allocation_list:
+ vm_name = []
+ vcpu_num = []
+ if cache_allocation_node.xpath("CACHE_LEVEL/text()")[0] == cache_level:
+ policy_node_list = cache_allocation_node.xpath("POLICY")
+ for policy_node in policy_node_list:
+ vm_name.append(policy_node.xpath("VM/text()")[0])
+ vcpu_num.append(policy_node.xpath("VCPU/text()")[0])
+
+ for i in range(len(vm_name)):
+ pre_vm_name = vm_name[i]
+ if pre_vm_name == vm_name[i]:
+ if vm_name[i] not in vm_vcpu_info:
+ vm_vcpu_info[vm_name[i]] = [int(vcpu_num[i])]
+ else:
+ vm_vcpu_info[vm_name[i]].append(int(vcpu_num[i]))
+ else:
+ vm_vcpu_info[pre_vm_name] = [int(vcpu_num[i])]
+ return vm_vcpu_info
+
+ # Write all info under the first level heading "Inter-VM Connections"
+ def write_inter_vm_connections(self):
+ self.doc.h1("Inter-VM Connections")
+ self.doc.newline()
+ self.write_virtual_uarts()
+ self.write_shared_memory()
+
+ # Write the virtual uarts info according to the virtual uarts table
+ def write_virtual_uarts(self):
+ self.doc.h2("Virtual UARTs")
+ self.doc.newline()
+ self.doc.content("The table below summarizes the virtual UART connections in the system.")
+ column_title, data_table = self.get_virtual_uarts_table()
+ self.doc.table(column_title, data_table)
+
+ # Get virtual uarts table
+ def get_virtual_uarts_table(self):
+ data_table = []
+ vm_name_list = self.get_vm_name()
+ column_title = ["vUART Connection"]
+ for vm_name in vm_name_list:
+ column_title.append(vm_name)
+ vuart_connections_list = self.scenario_etree.xpath("/acrn-config/hv/vuart_connections/vuart_connection")
+ for vuart_connection in vuart_connections_list:
+ data_row = [""] * (len(vm_name_list) + 1)
+ vc_name = vuart_connection.xpath("name/text()")[0]
+ vc_type = vuart_connection.xpath("type/text()")[0]
+ vc_endpoint_list = vuart_connection.xpath("endpoint")
+ data_row[0] = vc_name
+ for vc_endpoint_node in vc_endpoint_list:
+ vc_vm_name = vc_endpoint_node.xpath("vm_name/text()")[0]
+ vc_io_port = vc_endpoint_node.xpath("io_port/text()")[0]
+ vc_vbdf = vc_endpoint_node.xpath("vbdf/text()")[0]
+ if vc_vm_name not in self.io_port.keys():
+ self.io_port[vc_vm_name] = [vc_io_port]
+ else:
+ self.io_port[vc_vm_name].append(vc_io_port)
+ self.io_description.append(vc_name)
+ for vm_name in vm_name_list:
+ if vm_name.find(vc_vm_name) != -1:
+ data_row[vm_name_list.index(vm_name) + 1] = vc_type + " " + vc_io_port + " " + vc_vbdf
+
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Write the shared memory information according to the shared memory table
+ def write_shared_memory(self):
+ self.doc.h2("Shared Memory")
+ self.doc.newline()
+ self.doc.content("The table below summarizes topology of shared memory in the system.")
+ column_title, data_table = self.get_shared_memory_table()
+ self.doc.table(column_title, data_table)
+
+ # Get shared memory table
+ def get_shared_memory_table(self):
+ data_table = []
+ vm_name_list = self.get_vm_name()
+ column_title = ["Shared Memory Block"]
+ for vm_name in vm_name_list:
+ column_title.append(vm_name)
+ ivshmem_region_list = self.scenario_etree.xpath("/acrn-config/hv/FEATURES/IVSHMEM/IVSHMEM_REGION")
+ for ivshmem_region_node in ivshmem_region_list:
+ data_row = [""] * (len(vm_name_list) + 1)
+ ir_name = ivshmem_region_node.xpath("NAME/text()")[0]
+ data_row[0] = ir_name
+ for ivshmem_vm in ivshmem_region_node.xpath("IVSHMEM_VMS/IVSHMEM_VM"):
+ ivshmem_vm_name = ivshmem_vm.xpath("VM_NAME/text()")[0]
+ ivshmem_vm_vbdf = ivshmem_vm.xpath("VBDF/text()")[0]
+ for vm_name in vm_name_list:
+ if vm_name.find(ivshmem_vm_name) != -1:
+ data_row[vm_name_list.index(vm_name) + 1] = "pci " + ivshmem_vm_vbdf
+
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Write all info under the first level heading "VM X - <VM Name>"
+ def write_vms(self):
+ vm_node_list = self.scenario_etree.xpath("/acrn-config/vm")
+ for vm_node in vm_node_list:
+ vm_id = vm_node.attrib['id']
+ vm_name = vm_node.xpath("name/text()")[0]
+ self.doc.h1("VM " + f"{str(vm_id)} - " + vm_name)
+ self.doc.newline()
+ self.write_each_vm(vm_node)
+
+ # Write the information under all secondary headings for each VM in order
+ def write_each_vm(self, vm_node):
+ self.doc.h2("Basic Information")
+ column_title1, data_table1 = self.get_basic_information_table(vm_node)
+ self.doc.table(column_title1, data_table1)
+ self.doc.h2("PCI Devices")
+ column_title2, data_table2 = self.get_pci_devices_table(vm_node)
+ self.doc.table(column_title2, data_table2)
+ self.doc.h2("Fixed I/O Addresses")
+ column_title3, data_table3 = self.get_fixed_io_address_table(vm_node)
+ self.doc.table(column_title3, data_table3)
+
+ # Get basic information table for VM info
+ def get_basic_information_table(self, vm_node):
+ parameter_dict = {}
+ memory_size = 0
+ vcpu_num = set()
+ data_table = []
+ column_title = ["Parameter", "Configuration"]
+ load_order = vm_node.xpath("load_order")[0].text
+ vm_name = vm_node.xpath("name")[0].text
+ if len(vm_node.xpath("memory/hpa_region")) == 0 and len(vm_node.xpath("memory/size")) == 0:
+ memory_size = " "
+ else:
+ if len(vm_node.xpath("memory/hpa_region")) == 0:
+ memory_size = int(vm_node.xpath("memory/size/text()")[0])
+ else:
+ hpa_region_list = vm_node.xpath("memory/hpa_region")
+ for hpa_region in hpa_region_list:
+ memory_size = memory_size + int(hpa_region.xpath("size_hpa/text()")[0])
+ vm_vcpu_info_l2 = self.get_vm_used_vcpu("2")
+ vm_vcpu_info_l3 = self.get_vm_used_vcpu("3")
+ if vm_name in vm_vcpu_info_l2.keys():
+ for item in vm_vcpu_info_l2[vm_name]:
+ vcpu_num.add(item)
+ if vm_name in vm_vcpu_info_l3.keys():
+ for item in vm_vcpu_info_l3[vm_name]:
+ vcpu_num.add(item)
+ parameter_dict["Load Order"] = load_order
+ parameter_dict["Number of vCPUs"] = len(vcpu_num)
+ parameter_dict["Ammount of RAM"] = str(memory_size) + "MB"
+ parameter_dict["Amount of L3 Cache"] = "Amount of L3 Cache"
+ for k, v in parameter_dict.items():
+ data_row = [k, v]
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Get pci device table for VM info
+ @classmethod
+ def get_pci_devices_table(cls, vm_node):
+ data_table = []
+ column_title = ["Parameter", "Configuration"]
+ pci_devices_list = vm_node.xpath("pci_devs/pci_dev")
+ if len(pci_devices_list) != 0:
+ for pci_device in pci_devices_list:
+ data_row = pci_device.text.split(" ", 1)
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Get fixed io address table for VM info
+ def get_fixed_io_address_table(self, vm_node):
+ data_table = []
+ column_title = ["I/O Address", "Function Description"]
+ vm_name = vm_node.xpath("name")[0].text
+ for k, v in self.io_port.items():
+ if k in vm_name:
+ for item in v:
+ data_row = [item, "Virtual UART"]
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Close the Rst file after all information is written.
+ def close_file(self):
+ self.file.close()
+
+
+a = GenerateRst(rst_file_name="config_summary.rst",
+ file_access_mode='a+',
+ board_file_name="adl-s-crb.board.xml",
+ scenario_file_name="scenario.xml")
+a.write_configuration_rst()
diff --git a/misc/config_tools/scenario_config/config_summary.py b/misc/config_tools/scenario_config/config_summary.py
new file mode 100644
index 000000000..cff2450e3
--- /dev/null
+++ b/misc/config_tools/scenario_config/config_summary.py
@@ -0,0 +1,392 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 Intel Corporation.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+import argparse
+from rstcloth import RstCloth
+from lxml import etree
+
+
+class GenerateRst:
+ io_port = {}
+ io_description = []
+ pci_vuart = {}
+ pci_ivshmem = {}
+ amount_l3_cache = {}
+
+ # Class initialization
+ def __init__(self, board_file_name, scenario_file_name, rst_file_name, file_access_mode) -> None:
+ self.board_etree = etree.parse(board_file_name)
+ self.scenario_file_name = scenario_file_name
+ self.scenario_etree = etree.parse(scenario_file_name)
+ self.file = open(rst_file_name, file_access_mode)
+ self.doc = RstCloth(self.file)
+
+ # The rst content is written in three parts according to the first level title
+ # 1. Hardware Resource Allocation 2. Inter-VM Connections 3. VM info
+ def write_configuration_rst(self):
+ self.doc.title(f"ACRN Scenario <{self.scenario_file_name}> - Datasheet")
+ self.doc.newline()
+ self.write_hardware_resource_allocation()
+ self.write_inter_vm_connections()
+ self.write_vms()
+ self.close_file()
+
+ # Write all info under the first level heading "Hardware Resource Allocation"
+ def write_hardware_resource_allocation(self):
+ self.doc.h1("Hardware Resource Allocation")
+ self.doc.newline()
+ self.write_pcpu()
+ self.write_shared_cache()
+
+ # Write physical CPUs info table
+ def write_pcpu(self):
+ self.doc.h2("Physical CPUs")
+ column_title, data_table = self.get_pcpu_table()
+ self.doc.table(column_title, data_table)
+
+ # Get all VM names info from the scenario.xml
+ def get_vm_name(self):
+ return list(
+ map(lambda x: f"<VM {x.attrib['id']} {x.find('name').text}>", self.scenario_etree.xpath("/acrn-config/vm")))
+
+ # get the column_title and datatable required by the physical CPUs table
+ def get_pcpu_table(self):
+ data_table = []
+ column_title = [" "]
+ service_vm_used_pcpu_list = []
+ vm_node_list = self.scenario_etree.xpath("/acrn-config/vm")
+ total_pcpu_list = self.get_pcpu()
+ pre_launch_vm_node_list = self.scenario_etree.xpath("vm[load_order = 'PRE_LAUNCHED_VM']")
+
+ for item in total_pcpu_list:
+ column_title.append(str(item))
+
+ if pre_launch_vm_node_list is not None and len(pre_launch_vm_node_list) != 0:
+ for pre_launch_vm_node in pre_launch_vm_node_list:
+ pre_launch_vm_pcpu_list = pre_launch_vm_node.xpath("cpu_affinity/pcpu/pcpu_id/text()")
+ for pcpu in total_pcpu_list:
+ if pcpu not in pre_launch_vm_pcpu_list:
+ service_vm_used_pcpu_list.append(int(pcpu))
+ else:
+ for pcpu in total_pcpu_list:
+ service_vm_used_pcpu_list.append(int(pcpu))
+
+ for vm_node in vm_node_list:
+ vm_pcpu_id_list = []
+ data_row = [" "] * (len(total_pcpu_list) + 1)
+ data_row[0] = f"<VM {vm_node.attrib['id']} {vm_node.find('name').text}>"
+ vm_load_order = vm_node.find("load_order").text
+ vm_pcpu_id_node = vm_node.xpath("cpu_affinity/pcpu/pcpu_id")
+
+ for pcpu_info in vm_pcpu_id_node:
+ vm_pcpu_id_list.append(int(pcpu_info.text))
+
+ if len(vm_pcpu_id_node) == 0 and vm_load_order == "SERVICE_VM":
+ for pcpu in service_vm_used_pcpu_list:
+ data_row[pcpu + 1] = "*"
+ else:
+ for pcpu in vm_pcpu_id_list:
+ data_row[pcpu + 1] = "*"
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Get all physical CPU information from board.xml
+ def get_pcpu(self):
+ pcpu_list = self.board_etree.xpath("processors/die/core/thread/cpu_id/text()")
+ pcpu_list = list(map(int, pcpu_list))
+ return pcpu_list
+
+ def write_shared_cache(self):
+ self.doc.h2("Shared Cache")
+ self.doc.newline()
+ self.write_l2_shared_cache()
+ self.write_l3_shared_cache()
+
+ def write_l2_shared_cache(self):
+ column_title, data_table = self.get_vcpu_table(cache_level='2')
+ self.doc.h3("Level-2 Cache")
+ self.doc.table(column_title, data_table)
+ self.doc.content("Level-2 caches are local on all physical CPUs.")
+ self.doc.newline()
+
+ def write_l3_shared_cache(self):
+ column_title, data_table = self.get_vcpu_table(cache_level='3')
+ self.doc.h3("Level-3 Cache")
+ self.doc.table(column_title, data_table)
+ self.doc.note(name="note", content="Each cache chunk is 1MB.")
+
+ # Get used vcpu table
+ def get_vcpu_table(self, cache_level):
+ data_table = []
+ column_title = [" "]
+ pcpu_list = self.get_pcpu()
+ vm_vcpu_info = self.get_vm_used_vcpu(cache_level)
+ for pcpu_info in pcpu_list:
+ column_title.append(str(pcpu_info))
+ for vm_name, vcpu_id_list in vm_vcpu_info.items():
+ for vcpu_id in vcpu_id_list:
+ data_row = [vm_name + " vCPU " + str(vcpu_id)]
+ for i in range(len(pcpu_list)):
+ data_row.append(" ")
+ data_row[vcpu_id + 1] = "*"
+ data_table.append(data_row)
+ return column_title, sorted(data_table, key=lambda x: x[-1])
+
+ # Get the vcpu info used by each VM from scenario.xml
+ def get_vm_used_vcpu(self, cache_level):
+ vm_used_vcpu_info = {}
+ cache_allocation_list = self.scenario_etree.xpath("/acrn-config/hv/CACHE_REGION/CACHE_ALLOCATION")
+ for cache_allocation_node in cache_allocation_list:
+ vm_name = []
+ vcpu_num = []
+ if cache_allocation_node.find("CACHE_LEVEL").text == cache_level:
+ policy_node_list = cache_allocation_node.xpath("POLICY")
+ for policy_node in policy_node_list:
+ vm_name.append(policy_node.find("VM").text)
+ vcpu_num.append(policy_node.find("VCPU").text)
+
+ for index in range(len(vm_name)):
+ pre_vm_name = vm_name[index]
+ if pre_vm_name == vm_name[index]:
+ if vm_name[index] not in vm_used_vcpu_info:
+ vm_used_vcpu_info[vm_name[index]] = [int(vcpu_num[index])]
+ else:
+ vm_used_vcpu_info[vm_name[index]].append(int(vcpu_num[index]))
+ else:
+ vm_used_vcpu_info[pre_vm_name] = [int(vcpu_num[index])]
+ return vm_used_vcpu_info
+
+ # Write all info under the first level heading "Inter-VM Connections"
+ def write_inter_vm_connections(self):
+ self.doc.h1("Inter-VM Connections")
+ self.doc.newline()
+ self.write_virtual_uarts()
+ self.write_shared_memory()
+
+ # Write the virtual uarts info according to the virtual uarts table
+ def write_virtual_uarts(self):
+ self.doc.h2("Virtual UARTs")
+ self.doc.newline()
+ self.doc.content("The table below summarizes the virtual UART connections in the system.")
+ column_title, data_table = self.get_virtual_uarts_table()
+ self.doc.table(column_title, data_table)
+
+ # Get virtual uarts table
+ def get_virtual_uarts_table(self):
+ data_table = []
+ vm_name_list = self.get_vm_name()
+ column_title = ["vUART Connection"]
+ for vm_name in vm_name_list:
+ column_title.append(vm_name)
+ vuart_connections_list = self.scenario_etree.xpath("/acrn-config/hv/vuart_connections/vuart_connection")
+ for vuart_connection in vuart_connections_list:
+ data_row = [""] * (len(vm_name_list) + 1)
+ pci_row = [""] * 3
+ vc_name = vuart_connection.find("name").text
+ vc_type = vuart_connection.find("type").text
+ vc_endpoint_list = vuart_connection.xpath("endpoint")
+ data_row[0] = vc_name
+ for vc_endpoint_node in vc_endpoint_list:
+ vc_vm_name = vc_endpoint_node.find("vm_name").text
+ vc_io_port = vc_endpoint_node.find("io_port").text
+ if vc_endpoint_node.find("vbdf") is not None:
+ vc_vbdf = vc_endpoint_node.find("vbdf").text
+ pci_row[1] = vc_vbdf
+ else:
+ vc_vbdf = ""
+ if vc_vm_name not in self.io_port.keys():
+ self.io_port[vc_vm_name] = [vc_io_port]
+ else:
+ self.io_port[vc_vm_name].append(vc_io_port)
+ self.io_description.append(vc_name)
+ for vm_name in vm_name_list:
+ if vm_name.find(vc_vm_name) != -1:
+ data_row[vm_name_list.index(vm_name) + 1] = f"{vc_type} {vc_io_port} {vc_vbdf}"
+ pci_row[0] = vc_vm_name
+ pci_row[2] = f"{vc_name} {vc_type} {vc_io_port}"
+ if pci_row[0] not in self.pci_vuart:
+ self.pci_vuart[pci_row[0]] = [pci_row[-2:]]
+ else:
+ self.pci_vuart[pci_row[0]].append(pci_row[-2:])
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Write the shared memory information according to the shared memory table
+ def write_shared_memory(self):
+ self.doc.h2("Shared Memory")
+ self.doc.newline()
+ self.doc.content("The table below summarizes topology of shared memory in the system.")
+ column_title, data_table = self.get_shared_memory_table()
+ self.doc.table(column_title, data_table)
+
+ # Get shared memory table
+ def get_shared_memory_table(self):
+ data_table = []
+ vm_name_list = self.get_vm_name()
+ column_title = ["Shared Memory Block"]
+ for vm_name in vm_name_list:
+ column_title.append(vm_name)
+ ivshmem_region_list = self.scenario_etree.xpath("/acrn-config/hv/FEATURES/IVSHMEM/IVSHMEM_REGION")
+ for ivshmem_region_node in ivshmem_region_list:
+ data_row = [""] * (len(vm_name_list) + 1)
+ ivshmem_row = [""] * 3
+ ir_name = ivshmem_region_node.find("NAME").text
+ powered_by = ivshmem_region_node.find("PROVIDED_BY").text
+ data_row[0] = ir_name
+ for ivshmem_vm in ivshmem_region_node.xpath("IVSHMEM_VMS/IVSHMEM_VM"):
+ ivshmem_vm_name = ivshmem_vm.find("VM_NAME").text
+ ivshmem_vm_vbdf = ivshmem_vm.find("VBDF").text
+ for vm_name in vm_name_list:
+ if vm_name.find(ivshmem_vm_name) != -1:
+ ivshmem_row[0] = ivshmem_vm_name
+ ivshmem_row[1] = ivshmem_vm_vbdf
+ ivshmem_row[2] = f"pci, {ir_name}, powered by {powered_by}"
+ data_row[vm_name_list.index(vm_name) + 1] = f"pci {ivshmem_vm_vbdf}"
+
+ if ivshmem_row[0] not in self.pci_ivshmem:
+ self.pci_ivshmem[ivshmem_row[0]] = [ivshmem_row[-2:]]
+ else:
+ self.pci_ivshmem[ivshmem_row[0]].append(ivshmem_row[-2:])
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Write all info under the first level heading "VM X - <VM Name>"
+ def write_vms(self):
+ vm_node_list = self.scenario_etree.xpath("/acrn-config/vm")
+ for vm_node in vm_node_list:
+ vm_id = vm_node.attrib['id']
+ vm_name = vm_node.find("name").text
+ self.doc.h1(f"VM {str(vm_id)} - {vm_name}")
+ self.doc.newline()
+ self.write_each_vm(vm_node)
+
+ # Write the information under all secondary headings for each VM in order
+ def write_each_vm(self, vm_node):
+ self.doc.h2("Basic Information")
+ column_title1, data_table1 = self.get_basic_information_table(vm_node)
+ self.doc.table(column_title1, data_table1)
+ self.doc.h2("PCI Devices")
+ column_title2, data_table2 = self.get_pci_devices_table(vm_node)
+ self.doc.table(column_title2, data_table2)
+ self.doc.h2("Fixed I/O Addresses")
+ column_title3, data_table3 = self.get_fixed_io_address_table(vm_node)
+ self.doc.table(column_title3, data_table3)
+
+ # Get basic information table for VM info
+ def get_basic_information_table(self, vm_node):
+ parameter_dict = {}
+ memory_size = 0
+ vcpu_num = set()
+ data_table = []
+ column_title = ["Parameter", "Configuration"]
+ load_order = vm_node.find("load_order").text
+ vm_name = vm_node.find("name").text
+ if len(vm_node.xpath("memory/hpa_region")) == 0 and len(vm_node.xpath("memory/size")) == 0:
+ memory_size = " "
+ else:
+ if len(vm_node.xpath("memory/hpa_region")) == 0:
+ memory_size = int(vm_node.find("memory/size").text)
+ else:
+ hpa_region_list = vm_node.xpath("memory/hpa_region")
+ for hpa_region in hpa_region_list:
+ memory_size = memory_size + int(hpa_region.find("size_hpa").text)
+ vm_vcpu_info_l2 = self.get_vm_used_vcpu("2")
+ vm_vcpu_info_l3 = self.get_vm_used_vcpu("3")
+ if vm_name in vm_vcpu_info_l2.keys():
+ for item in vm_vcpu_info_l2[vm_name]:
+ vcpu_num.add(item)
+ if vm_name in vm_vcpu_info_l3.keys():
+ for item in vm_vcpu_info_l3[vm_name]:
+ vcpu_num.add(item)
+ amount_vm_l3_cache = self.get_amount_l3_cache(vm_node)
+ parameter_dict["Load Order"] = load_order
+ parameter_dict["Number of vCPUs"] = len(vcpu_num)
+ parameter_dict["Ammount of RAM"] = str(memory_size) + "MB"
+ parameter_dict["Amount of L3 Cache"] = amount_vm_l3_cache
+ data_table.extend(map(list, parameter_dict.items()))
+ return column_title, data_table
+
+ # get Amount of L3 Cache info
+ def get_amount_l3_cache(self, vm_node):
+ vm_bit_map = 0
+ total_cache_size = 0
+ total_cache_ways = 0
+ vm_name = vm_node.find("name").text
+ l3_cache_node_list = self.board_etree.xpath("caches/cache [@level = '3']")
+ cache_allocation_node_list = self.scenario_etree.xpath("/acrn-config/hv/CACHE_REGION/CACHE_ALLOCATION")
+
+ for l3_cache_node in l3_cache_node_list:
+ total_cache_size += int(l3_cache_node.find("cache_size").text)
+ total_cache_ways += int(l3_cache_node.find("ways").text)
+ each_cache_way_size = total_cache_size / 1024 / 1024 / total_cache_ways
+
+ for cache_allocation_node in cache_allocation_node_list:
+ if cache_allocation_node.find("CACHE_LEVEL").text == '3':
+ policy_node_list = cache_allocation_node.xpath("POLICY")
+ for policy_node in policy_node_list:
+ if vm_name == policy_node.find("VM").text:
+ vm_bit_map |= int(policy_node.find("CLOS_MASK").text, base=16)
+ vm_cache_way = bin(vm_bit_map).count('1')
+ amount_vm_l3_cache = f"{vm_cache_way * each_cache_way_size} MB"
+ return amount_vm_l3_cache
+
+ # Get pci device table for VM info
+ def get_pci_devices_table(self, vm_node):
+ data_table = []
+ vm_name = vm_node.find("name").text
+ column_title = ["Parameter", "Configuration"]
+ pci_devices_list = vm_node.xpath("pci_devs/pci_dev")
+ if len(pci_devices_list) != 0:
+ for pci_device in pci_devices_list:
+ data_row = pci_device.text.split(" ", 1)
+ data_table.append(data_row)
+ if vm_name in self.pci_vuart.keys():
+ for item in self.pci_vuart[vm_name]:
+ data_row = [item[0], item[1]]
+ data_table.append(data_row)
+ if vm_name in self.pci_ivshmem.keys():
+ for item in self.pci_ivshmem[vm_name]:
+ data_row = [item[0], item[1]]
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Get fixed io address table for VM info
+ def get_fixed_io_address_table(self, vm_node):
+ data_table = []
+ column_title = ["I/O Address", "Function Description"]
+ vm_name = vm_node.find("name").text
+ for k, v in self.io_port.items():
+ if k in vm_name:
+ for item in v:
+ data_row = [item, "Virtual UART"]
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Close the Rst file after all information is written.
+ def close_file(self):
+ self.file.close()
+
+
+def main(args):
+ GenerateRst(board_file_name=args.board_file_name, scenario_file_name=args.scenario_file_name,
+ rst_file_name=args.rst_file_name, file_access_mode=args.rst_file_access_mode).write_configuration_rst()
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument("board_file_name", default="adl-s-crb.board.xml",
+ help="Specifies the board config XML path and name used to generate the rst file")
+ parser.add_argument("scenario_file_name", default="scenario.xml",
+ help="Specifies the scenario XML path and name used to generate the rst file")
+ parser.add_argument("rst_file_name", default="config_summary.rst",
+ help="the path and name of the output rst file that "
+ "summaries the config from scenario.xml and board.xml")
+ parser.add_argument("rst_file_access_mode", default="w",
+ help="specifies the operating mode of rst file, e.g. 'a', 'w' ")
+
+ args = parser.parse_args()
+ main(args)
--
2.35.1.windows.2



-----邮件原件-----
发件人: Mao, Junjie <junjie.mao@...>
发送时间: Friday, August 26, 2022 7:52 PM
收件人: Li, Ziheng <ziheng.li@...>
抄送: acrn-dev@...
主题: Re: [PATCH] doc: generate config_summary.rst

"Li, Ziheng" <ziheng.li@...> writes:

From 12922e1b3e1a01be9efb3d827ac10f0de1656a10 Mon Sep 17 00:00:00 2001
From: zihengL1 <ziheng.li@...>
Date: Fri, 26 Aug 2022 09:11:34 +0800
Subject: [PATCH] doc: generate config_summary.rst

With the help of the functions provided in the "rstcloth" Python
package, the important information in scenario.xml and board.xml is summarized.
According to the format provided by Datasheet prototype.rst, finally,
it is output in a structured form and saved in the summary.rst file.

However, there are still some problems to be solved:
1. Where should the config_summary.py code be placed.
You can put this script under misc/config_tools/scenario_config/

2. Where should the output generated config_summary.rst file be placed.
I would recommend taking a parameter from the command line to allow users specify where the report should be generated. You can use the argparse package for this.

3. Where should we call the above code in Makefile or in other file.
The intended usage of this script is in the configurator, not the Makefile. We can integrate that in a separate patch.

4. It seems that the "Amount of L3 Cache" info under the "Basic Information"
heading could not be found in scenario.xml.
Do you mean the size of L3 cache? You can find it using XPATH `//caches/cache[@level='3']/cache_size` which reports the size of L3 cache in bytes.


Tracked-On: #8063
Signed-off-by: Ziheng Li <ziheng.li@...>
---
config_summary.py | 319
++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 319 insertions(+)
create mode 100644 config_summary.py

diff --git a/config_summary.py b/config_summary.py new file mode
100644 index 000000000..f71cf2384
--- /dev/null
+++ b/config_summary.py
@@ -0,0 +1,319 @@
+from rstcloth import RstCloth
+from lxml import etree
+
+
+class GenerateRst:
+ io_port = {}
+ io_description = []
+
+ # Class initialization
+ def __init__(self, rst_file_name, file_access_mode, board_file_name, scenario_file_name) -> None:
+ self.file = open(rst_file_name, file_access_mode)
+ self.doc = RstCloth(self.file)
+ self.scenario_file_name = scenario_file_name
+ self.board_etree = etree.parse(board_file_name)
+ self.scenario_etree = etree.parse(scenario_file_name)
+
+ # The rst content is written in three parts according to the first level title
+ # 1. Hardware Resource Allocation 2. Inter-VM Connections 3. VM info
+ def write_configuration_rst(self):
+ self.doc.title(f'ACRN Scenario <{self.scenario_file_name}> - Datasheet')
+ self.doc.newline()
+ self.write_hardware_resource_allocation()
+ self.write_inter_vm_connections()
+ self.write_vms()
+ self.close_file()
+
+ # Write all info under the first level heading "Hardware Resource Allocation"
+ def write_hardware_resource_allocation(self):
+ self.doc.h1("Hardware Resource Allocation")
+ self.doc.newline()
+ self.write_pcpu()
+ self.write_shared_cache()
+
+ # Write physical CPUs info table
+ def write_pcpu(self):
+ self.doc.h2("Physical CPUs")
+ vm_name_list = self.get_vm_name()
+ vm_used_pcpu_info = self.get_vm_used_pcpu()
+ column_title, data_table = self.get_pcpu_table(vm_name_list, vm_used_pcpu_info)
+ self.doc.table(column_title, data_table)
+
+ # Get all VM names info from the scenario.xml
+ def get_vm_name(self):
+ vm_name_list = []
+ vm_node_list = self.scenario_etree.xpath("/acrn-config/vm")
+ for vm_node in vm_node_list:
+ vm_name_node = vm_node.xpath("name/text()")
+ vm_name_list.append("<VM " + f"{vm_node.attrib['id']} " +
+ f"{vm_name_node[0]}>")
There is no need to concatenate multiple format strings; just combine them into a single format string.

+ return vm_name_list
This whole function can be written in a single line:

return map(lambda x: f"<VM {x['id']} {x.find('name').text}>", self.scenario_etree.xpath("/acrn-config/vm"))

That will generate a map object which can be iterated to get all VM names. If you still need it to be a list, apply the list() constructor before returning.

+
+ # Get the info about which PCUs are actually used by each VM from scenario.xml
+ def get_vm_used_pcpu(self):
+ vm_used_pcpu_info = {}
+ vm_node_list = self.scenario_etree.xpath("/acrn-config/vm")
+ for vm_node in vm_node_list:
+ vm_pcpu_id = []
+ vm_pcpu_id_node = vm_node.xpath("cpu_affinity/pcpu/pcpu_id")
+ if len(vm_pcpu_id_node) == 0:
+ vm_used_pcpu_info[int(vm_node.attrib['id'])] = []
Service VM needs to be considered separately. It uses all physical CPU except those assigned to any pre-launched VM.

+ continue
+ for pcpu_info in vm_pcpu_id_node:
+ vm_pcpu_id.append(int(pcpu_info.text))
+ vm_used_pcpu_info[int(vm_node.attrib['id'])] = vm_pcpu_id
+ return vm_used_pcpu_info
+
+ # get the column_title and datatable required by the physical CPUs table
+ @classmethod
+ def get_pcpu_table(cls, vm_name_list, vm_used_pcpu_info):
+ data_table = []
+ column_title = [" "]
+ actual_used_pcpu_list = []
+ for v in vm_used_pcpu_info.values():
+ for item in v:
+ if item not in actual_used_pcpu_list:
+ actual_used_pcpu_list.append(item)
+ actual_used_pcpu_list.sort()
+ for item in actual_used_pcpu_list:
+ column_title.append(str(item))
+ for vm_name in vm_name_list:
+ data_row = [vm_name]
+ for i in range(len(actual_used_pcpu_list)):
+ data_row.append(" ")
+ for item in vm_used_pcpu_info[vm_name_list.index(vm_name)]:
+ data_row[actual_used_pcpu_list.index(item) + 1] = " " + "*"
+ data_table.append(data_row)
+ return column_title, data_table
I think you can simplify the generation of the pCPU table as follows:

1. Get the list of all physical CPUs. This can be done by the XPATH
`//processors//thread/cpu_id/text()`. The column titles can be
derived solely from that.

2. Iterate over VM nodes in the scenario XML. For each of them, get
its name and used physical CPUs to format a row in the table.

When getting the pCPUs used by a VM, the service VM should be specially treated.

To format the row, contents in the cells can be calculated by a `map` operation, i.e. `list(map(lambda x: "*" if x in used_pcpus else " ", all_pcpus))`.

With those you don't need the get_vm_used_pcpu() helper anymore.

Note that it still helps list a pCPU that is not used by any VM so that users always have a complete view.

+
+ # Get all physical CPU information from board.xml
+ def get_pcpu(self):
+ pcpu_list =
+ self.board_etree.xpath("/acrn-config/CPU_PROCESSOR_INFO/text()")[0].
+ strip().split(',')
Using the text nodes generated by the legacy board inspector is not recommended. Simply use the XPATH `//processors//thread/cpu_id/text()`.

+ pcpu_list = list(map(int, pcpu_list))
+ return pcpu_list
+
+ def write_shared_cache(self):
+ self.doc.h2("Shared Cache")
+ self.doc.newline()
+ self.write_l2_shared_cache()
+ self.write_l3_shared_cache()
+
+ def write_l2_shared_cache(self):
+ column_title, data_table = self.get_vcpu_table(cache_level="2")
+ self.doc.h3("Level-2 Cache")
+ self.doc.table(column_title, data_table)
+ self.doc.content("Level-2 caches are local on all physical CPUs.")
+ self.doc.newline()
+
+ def write_l3_shared_cache(self):
+ column_title, data_table = self.get_vcpu_table(cache_level="3")
+ self.doc.h3("Level-3 Cache")
+ self.doc.table(column_title, data_table)
+ self.doc.note(name="note", content="Each cache chunk is
+ 1MB.")
+
+ # Get used vcpu table
+ def get_vcpu_table(self, cache_level):
+ data_table = []
+ column_title = [" "]
+ pcpu_list = self.get_pcpu()
+ vm_vcpu_info = self.get_vm_used_vcpu(cache_level)
+ for pcpu_info in pcpu_list:
+ column_title.append(str(pcpu_info))
+ for vm_name, vcpu_id_list in vm_vcpu_info.items():
+ for vcpu_id in vcpu_id_list:
+ data_row = [vm_name + " vCPU " + str(vcpu_id)]
+ for i in range(len(pcpu_list)):
+ data_row.append(" ")
+ data_row[vcpu_id + 1] = " " + "*"
+ data_table.append(data_row)
+ return column_title, sorted(data_table, key=lambda x: x[-1])
+
+ # Get the vcpu info used by each VM from scenario.xml
+ def get_vm_used_vcpu(self, cache_level):
+ vm_vcpu_info = {}
+ cache_allocation_list = self.scenario_etree.xpath("/acrn-config/hv/CACHE_REGION/CACHE_ALLOCATION")
+ for cache_allocation_node in cache_allocation_list:
+ vm_name = []
+ vcpu_num = []
+ if cache_allocation_node.xpath("CACHE_LEVEL/text()")[0] == cache_level:
+ policy_node_list = cache_allocation_node.xpath("POLICY")
+ for policy_node in policy_node_list:
+ vm_name.append(policy_node.xpath("VM/text()")[0])
+
+ vcpu_num.append(policy_node.xpath("VCPU/text()")[0])
+
+ for i in range(len(vm_name)):
+ pre_vm_name = vm_name[i]
+ if pre_vm_name == vm_name[i]:
+ if vm_name[i] not in vm_vcpu_info:
+ vm_vcpu_info[vm_name[i]] = [int(vcpu_num[i])]
+ else:
+ vm_vcpu_info[vm_name[i]].append(int(vcpu_num[i]))
+ else:
+ vm_vcpu_info[pre_vm_name] = [int(vcpu_num[i])]
+ return vm_vcpu_info
Similar to `get_pcpu_table()`, I think this method can be greatly simplified in the same way.

+
+ # Write all info under the first level heading "Inter-VM Connections"
+ def write_inter_vm_connections(self):
+ self.doc.h1("Inter-VM Connections")
+ self.doc.newline()
+ self.write_virtual_uarts()
+ self.write_shared_memory()
+
+ # Write the virtual uarts info according to the virtual uarts table
+ def write_virtual_uarts(self):
+ self.doc.h2("Virtual UARTs")
+ self.doc.newline()
+ self.doc.content("The table below summarizes the virtual UART connections in the system.")
+ column_title, data_table = self.get_virtual_uarts_table()
+ self.doc.table(column_title, data_table)
+
+ # Get virtual uarts table
+ def get_virtual_uarts_table(self):
+ data_table = []
+ vm_name_list = self.get_vm_name()
+ column_title = ["vUART Connection"]
+ for vm_name in vm_name_list:
+ column_title.append(vm_name)
+ vuart_connections_list = self.scenario_etree.xpath("/acrn-config/hv/vuart_connections/vuart_connection")
+ for vuart_connection in vuart_connections_list:
+ data_row = [""] * (len(vm_name_list) + 1)
+ vc_name = vuart_connection.xpath("name/text()")[0]
To get the text of a child, use `vuart_connection.find("name").text` is simplier.

+ vc_type = vuart_connection.xpath("type/text()")[0]
+ vc_endpoint_list = vuart_connection.xpath("endpoint")
+ data_row[0] = vc_name
+ for vc_endpoint_node in vc_endpoint_list:
+ vc_vm_name = vc_endpoint_node.xpath("vm_name/text()")[0]
+ vc_io_port = vc_endpoint_node.xpath("io_port/text()")[0]
+ vc_vbdf = vc_endpoint_node.xpath("vbdf/text()")[0]
+ if vc_vm_name not in self.io_port.keys():
+ self.io_port[vc_vm_name] = [vc_io_port]
+ else:
+ self.io_port[vc_vm_name].append(vc_io_port)
+ self.io_description.append(vc_name)
+ for vm_name in vm_name_list:
+ if vm_name.find(vc_vm_name) != -1:
+ data_row[vm_name_list.index(vm_name) + 1] =
+ vc_type + " " + vc_io_port + " " + vc_vbdf
+
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Write the shared memory information according to the shared memory table
+ def write_shared_memory(self):
+ self.doc.h2("Shared Memory")
+ self.doc.newline()
+ self.doc.content("The table below summarizes topology of shared memory in the system.")
+ column_title, data_table = self.get_shared_memory_table()
+ self.doc.table(column_title, data_table)
+
+ # Get shared memory table
+ def get_shared_memory_table(self):
+ data_table = []
+ vm_name_list = self.get_vm_name()
+ column_title = ["Shared Memory Block"]
+ for vm_name in vm_name_list:
+ column_title.append(vm_name)
+ ivshmem_region_list = self.scenario_etree.xpath("/acrn-config/hv/FEATURES/IVSHMEM/IVSHMEM_REGION")
+ for ivshmem_region_node in ivshmem_region_list:
+ data_row = [""] * (len(vm_name_list) + 1)
+ ir_name = ivshmem_region_node.xpath("NAME/text()")[0]
+ data_row[0] = ir_name
+ for ivshmem_vm in ivshmem_region_node.xpath("IVSHMEM_VMS/IVSHMEM_VM"):
+ ivshmem_vm_name = ivshmem_vm.xpath("VM_NAME/text()")[0]
+ ivshmem_vm_vbdf = ivshmem_vm.xpath("VBDF/text()")[0]
+ for vm_name in vm_name_list:
+ if vm_name.find(ivshmem_vm_name) != -1:
+ data_row[vm_name_list.index(vm_name) + 1] =
+ "pci " + ivshmem_vm_vbdf
+
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Write all info under the first level heading "VM X - <VM Name>"
+ def write_vms(self):
+ vm_node_list = self.scenario_etree.xpath("/acrn-config/vm")
+ for vm_node in vm_node_list:
+ vm_id = vm_node.attrib['id']
+ vm_name = vm_node.xpath("name/text()")[0]
+ self.doc.h1("VM " + f"{str(vm_id)} - " + vm_name)
Combine the substrings into one single format string.

+ self.doc.newline()
+ self.write_each_vm(vm_node)
+
+ # Write the information under all secondary headings for each VM in order
+ def write_each_vm(self, vm_node):
+ self.doc.h2("Basic Information")
+ column_title1, data_table1 = self.get_basic_information_table(vm_node)
+ self.doc.table(column_title1, data_table1)
+ self.doc.h2("PCI Devices")
+ column_title2, data_table2 = self.get_pci_devices_table(vm_node)
+ self.doc.table(column_title2, data_table2)
+ self.doc.h2("Fixed I/O Addresses")
+ column_title3, data_table3 = self.get_fixed_io_address_table(vm_node)
+ self.doc.table(column_title3, data_table3)
+
+ # Get basic information table for VM info
+ def get_basic_information_table(self, vm_node):
+ parameter_dict = {}
+ memory_size = 0
+ vcpu_num = set()
+ data_table = []
+ column_title = ["Parameter", "Configuration"]
+ load_order = vm_node.xpath("load_order")[0].text
+ vm_name = vm_node.xpath("name")[0].text
+ if len(vm_node.xpath("memory/hpa_region")) == 0 and len(vm_node.xpath("memory/size")) == 0:
+ memory_size = " "
+ else:
+ if len(vm_node.xpath("memory/hpa_region")) == 0:
+ memory_size = int(vm_node.xpath("memory/size/text()")[0])
+ else:
+ hpa_region_list = vm_node.xpath("memory/hpa_region")
+ for hpa_region in hpa_region_list:
+ memory_size = memory_size + int(hpa_region.xpath("size_hpa/text()")[0])
+ vm_vcpu_info_l2 = self.get_vm_used_vcpu("2")
+ vm_vcpu_info_l3 = self.get_vm_used_vcpu("3")
+ if vm_name in vm_vcpu_info_l2.keys():
+ for item in vm_vcpu_info_l2[vm_name]:
+ vcpu_num.add(item)
Why not using `vcpu_num.extend(vm_vcpu_info_l2[vm_name])`?

+ if vm_name in vm_vcpu_info_l3.keys():
+ for item in vm_vcpu_info_l3[vm_name]:
+ vcpu_num.add(item)
+ parameter_dict["Load Order"] = load_order
+ parameter_dict["Number of vCPUs"] = len(vcpu_num)
+ parameter_dict["Ammount of RAM"] = str(memory_size) + "MB"
+ parameter_dict["Amount of L3 Cache"] = "Amount of L3 Cache"
+ for k, v in parameter_dict.items():
+ data_row = [k, v]
+ data_table.append(data_row)
The above three lines can be combined into one line as follows.

data_table.extend(map(list, parameter_dict.items()))

+ return column_title, data_table
+
+ # Get pci device table for VM info
+ @classmethod
+ def get_pci_devices_table(cls, vm_node):
+ data_table = []
+ column_title = ["Parameter", "Configuration"]
+ pci_devices_list = vm_node.xpath("pci_devs/pci_dev")
+ if len(pci_devices_list) != 0:
+ for pci_device in pci_devices_list:
+ data_row = pci_device.text.split(" ", 1)
+ data_table.append(data_row)
Virtual PCI devices such as IVSHMEM or PCI vUART should be listed here as well.

+ return column_title, data_table
+
+ # Get fixed io address table for VM info
+ def get_fixed_io_address_table(self, vm_node):
+ data_table = []
+ column_title = ["I/O Address", "Function Description"]
+ vm_name = vm_node.xpath("name")[0].text
+ for k, v in self.io_port.items():
+ if k in vm_name:
+ for item in v:
+ data_row = [item, "Virtual UART"]
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Close the Rst file after all information is written.
+ def close_file(self):
+ self.file.close()
+
+
+a = GenerateRst(rst_file_name="config_summary.rst",
+ file_access_mode='a+',
+ board_file_name="adl-s-crb.board.xml",
+ scenario_file_name="scenario.xml")
Similar to the output path, the input files should be taken from command line parameters as well.

+a.write_configuration_rst()
Please wrap the above two statements in a main() function which is called only when `__name__` is `__main__`. The configurator may have its own wrapper script based on this, in which case this script will be imported there.

--
Best Regards
Junjie Mao

--
2.35.1.windows.2


Junjie Mao
 

-----Original Message-----
From: Li, Ziheng <ziheng.li@...>
Sent: Tuesday, August 30, 2022 4:07 PM
To: Mao, Junjie <junjie.mao@...>; acrn-dev@...
Subject: [PATCH v2] config-tools: generate config_summary.rst

From 94d699b2a983c8d5fc0d571324c4f0d42275df0d Mon Sep 17 00:00:00 2001
From: zihengL1 <ziheng.li@...>
Date: Fri, 26 Aug 2022 09:11:34 +0800
Subject: [PATCH v2] config-tools: generate config_summary.rst

This patch is modified and simplified on the iversion of V1 patch.
Changes are as follows:
1. V2 patch is modified and simplified on the version of V1 patch
2. Added Ivshmem information and vuart information in the PCI table.
3. At the basic information, extract and calculate the L3 cache size of
each VM from the XML file
4. Change the input / output file format to argparse format

Tracked-On: #8063
Signed-off-by: Ziheng Li <ziheng.li@...>
---
config_summary.py | 319 ++++++++++++++
.../scenario_config/config_summary.py | 392 ++++++++++++++++++
Why splitting the script?

I assume only the one under scenario_config is needed and will only review that.

2 files changed, 711 insertions(+)
create mode 100644 config_summary.py
create mode 100644 misc/config_tools/scenario_config/config_summary.py

diff --git a/config_summary.py b/config_summary.py
new file mode 100644
index 000000000..f71cf2384
--- /dev/null
+++ b/config_summary.py
@@ -0,0 +1,319 @@
+from rstcloth import RstCloth
+from lxml import etree
+
+
+class GenerateRst:
+ io_port = {}
+ io_description = []
+
+ # Class initialization
+ def __init__(self, rst_file_name, file_access_mode, board_file_name,
scenario_file_name) -> None:
+ self.file = open(rst_file_name, file_access_mode)
+ self.doc = RstCloth(self.file)
+ self.scenario_file_name = scenario_file_name
+ self.board_etree = etree.parse(board_file_name)
+ self.scenario_etree = etree.parse(scenario_file_name)
+
+ # The rst content is written in three parts according to the first level title
+ # 1. Hardware Resource Allocation 2. Inter-VM Connections 3. VM info
+ def write_configuration_rst(self):
+ self.doc.title(f'ACRN Scenario <{self.scenario_file_name}> - Datasheet')
+ self.doc.newline()
+ self.write_hardware_resource_allocation()
+ self.write_inter_vm_connections()
+ self.write_vms()
+ self.close_file()
+
+ # Write all info under the first level heading "Hardware Resource Allocation"
+ def write_hardware_resource_allocation(self):
+ self.doc.h1("Hardware Resource Allocation")
+ self.doc.newline()
+ self.write_pcpu()
+ self.write_shared_cache()
+
+ # Write physical CPUs info table
+ def write_pcpu(self):
+ self.doc.h2("Physical CPUs")
+ vm_name_list = self.get_vm_name()
+ vm_used_pcpu_info = self.get_vm_used_pcpu()
+ column_title, data_table = self.get_pcpu_table(vm_name_list, vm_used_pcpu_info)
+ self.doc.table(column_title, data_table)
+
+ # Get all VM names info from the scenario.xml
+ def get_vm_name(self):
+ vm_name_list = []
+ vm_node_list = self.scenario_etree.xpath("/acrn-config/vm")
+ for vm_node in vm_node_list:
+ vm_name_node = vm_node.xpath("name/text()")
+ vm_name_list.append("<VM " + f"{vm_node.attrib['id']} " +
f"{vm_name_node[0]}>")
+ return vm_name_list
+
+ # Get the info about which PCUs are actually used by each VM from scenario.xml
+ def get_vm_used_pcpu(self):
+ vm_used_pcpu_info = {}
+ vm_node_list = self.scenario_etree.xpath("/acrn-config/vm")
+ for vm_node in vm_node_list:
+ vm_pcpu_id = []
+ vm_pcpu_id_node = vm_node.xpath("cpu_affinity/pcpu/pcpu_id")
+ if len(vm_pcpu_id_node) == 0:
+ vm_used_pcpu_info[int(vm_node.attrib['id'])] = []
+ continue
+ for pcpu_info in vm_pcpu_id_node:
+ vm_pcpu_id.append(int(pcpu_info.text))
+ vm_used_pcpu_info[int(vm_node.attrib['id'])] = vm_pcpu_id
+ return vm_used_pcpu_info
+
+ # get the column_title and datatable required by the physical CPUs table
+ @classmethod
+ def get_pcpu_table(cls, vm_name_list, vm_used_pcpu_info):
+ data_table = []
+ column_title = [" "]
+ actual_used_pcpu_list = []
+ for v in vm_used_pcpu_info.values():
+ for item in v:
+ if item not in actual_used_pcpu_list:
+ actual_used_pcpu_list.append(item)
+ actual_used_pcpu_list.sort()
+ for item in actual_used_pcpu_list:
+ column_title.append(str(item))
+ for vm_name in vm_name_list:
+ data_row = [vm_name]
+ for i in range(len(actual_used_pcpu_list)):
+ data_row.append(" ")
+ for item in vm_used_pcpu_info[vm_name_list.index(vm_name)]:
+ data_row[actual_used_pcpu_list.index(item) + 1] = " " + "*"
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Get all physical CPU information from board.xml
+ def get_pcpu(self):
+ pcpu_list = self.board_etree.xpath("/acrn-
config/CPU_PROCESSOR_INFO/text()")[0].strip().split(',')
+ pcpu_list = list(map(int, pcpu_list))
+ return pcpu_list
+
+ def write_shared_cache(self):
+ self.doc.h2("Shared Cache")
+ self.doc.newline()
+ self.write_l2_shared_cache()
+ self.write_l3_shared_cache()
+
+ def write_l2_shared_cache(self):
+ column_title, data_table = self.get_vcpu_table(cache_level="2")
+ self.doc.h3("Level-2 Cache")
+ self.doc.table(column_title, data_table)
+ self.doc.content("Level-2 caches are local on all physical CPUs.")
+ self.doc.newline()
+
+ def write_l3_shared_cache(self):
+ column_title, data_table = self.get_vcpu_table(cache_level="3")
+ self.doc.h3("Level-3 Cache")
+ self.doc.table(column_title, data_table)
+ self.doc.note(name="note", content="Each cache chunk is 1MB.")
+
+ # Get used vcpu table
+ def get_vcpu_table(self, cache_level):
+ data_table = []
+ column_title = [" "]
+ pcpu_list = self.get_pcpu()
+ vm_vcpu_info = self.get_vm_used_vcpu(cache_level)
+ for pcpu_info in pcpu_list:
+ column_title.append(str(pcpu_info))
+ for vm_name, vcpu_id_list in vm_vcpu_info.items():
+ for vcpu_id in vcpu_id_list:
+ data_row = [vm_name + " vCPU " + str(vcpu_id)]
+ for i in range(len(pcpu_list)):
+ data_row.append(" ")
+ data_row[vcpu_id + 1] = " " + "*"
+ data_table.append(data_row)
+ return column_title, sorted(data_table, key=lambda x: x[-1])
+
+ # Get the vcpu info used by each VM from scenario.xml
+ def get_vm_used_vcpu(self, cache_level):
+ vm_vcpu_info = {}
+ cache_allocation_list = self.scenario_etree.xpath("/acrn-
config/hv/CACHE_REGION/CACHE_ALLOCATION")
+ for cache_allocation_node in cache_allocation_list:
+ vm_name = []
+ vcpu_num = []
+ if cache_allocation_node.xpath("CACHE_LEVEL/text()")[0] == cache_level:
+ policy_node_list = cache_allocation_node.xpath("POLICY")
+ for policy_node in policy_node_list:
+ vm_name.append(policy_node.xpath("VM/text()")[0])
+ vcpu_num.append(policy_node.xpath("VCPU/text()")[0])
+
+ for i in range(len(vm_name)):
+ pre_vm_name = vm_name[i]
+ if pre_vm_name == vm_name[i]:
+ if vm_name[i] not in vm_vcpu_info:
+ vm_vcpu_info[vm_name[i]] = [int(vcpu_num[i])]
+ else:
+ vm_vcpu_info[vm_name[i]].append(int(vcpu_num[i]))
+ else:
+ vm_vcpu_info[pre_vm_name] = [int(vcpu_num[i])]
+ return vm_vcpu_info
+
+ # Write all info under the first level heading "Inter-VM Connections"
+ def write_inter_vm_connections(self):
+ self.doc.h1("Inter-VM Connections")
+ self.doc.newline()
+ self.write_virtual_uarts()
+ self.write_shared_memory()
+
+ # Write the virtual uarts info according to the virtual uarts table
+ def write_virtual_uarts(self):
+ self.doc.h2("Virtual UARTs")
+ self.doc.newline()
+ self.doc.content("The table below summarizes the virtual UART connections in the
system.")
+ column_title, data_table = self.get_virtual_uarts_table()
+ self.doc.table(column_title, data_table)
+
+ # Get virtual uarts table
+ def get_virtual_uarts_table(self):
+ data_table = []
+ vm_name_list = self.get_vm_name()
+ column_title = ["vUART Connection"]
+ for vm_name in vm_name_list:
+ column_title.append(vm_name)
+ vuart_connections_list = self.scenario_etree.xpath("/acrn-
config/hv/vuart_connections/vuart_connection")
+ for vuart_connection in vuart_connections_list:
+ data_row = [""] * (len(vm_name_list) + 1)
+ vc_name = vuart_connection.xpath("name/text()")[0]
+ vc_type = vuart_connection.xpath("type/text()")[0]
+ vc_endpoint_list = vuart_connection.xpath("endpoint")
+ data_row[0] = vc_name
+ for vc_endpoint_node in vc_endpoint_list:
+ vc_vm_name = vc_endpoint_node.xpath("vm_name/text()")[0]
+ vc_io_port = vc_endpoint_node.xpath("io_port/text()")[0]
+ vc_vbdf = vc_endpoint_node.xpath("vbdf/text()")[0]
+ if vc_vm_name not in self.io_port.keys():
+ self.io_port[vc_vm_name] = [vc_io_port]
+ else:
+ self.io_port[vc_vm_name].append(vc_io_port)
+ self.io_description.append(vc_name)
+ for vm_name in vm_name_list:
+ if vm_name.find(vc_vm_name) != -1:
+ data_row[vm_name_list.index(vm_name) + 1] = vc_type + " " +
vc_io_port + " " + vc_vbdf
+
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Write the shared memory information according to the shared memory table
+ def write_shared_memory(self):
+ self.doc.h2("Shared Memory")
+ self.doc.newline()
+ self.doc.content("The table below summarizes topology of shared memory in the
system.")
+ column_title, data_table = self.get_shared_memory_table()
+ self.doc.table(column_title, data_table)
+
+ # Get shared memory table
+ def get_shared_memory_table(self):
+ data_table = []
+ vm_name_list = self.get_vm_name()
+ column_title = ["Shared Memory Block"]
+ for vm_name in vm_name_list:
+ column_title.append(vm_name)
+ ivshmem_region_list = self.scenario_etree.xpath("/acrn-
config/hv/FEATURES/IVSHMEM/IVSHMEM_REGION")
+ for ivshmem_region_node in ivshmem_region_list:
+ data_row = [""] * (len(vm_name_list) + 1)
+ ir_name = ivshmem_region_node.xpath("NAME/text()")[0]
+ data_row[0] = ir_name
+ for ivshmem_vm in ivshmem_region_node.xpath("IVSHMEM_VMS/IVSHMEM_VM"):
+ ivshmem_vm_name = ivshmem_vm.xpath("VM_NAME/text()")[0]
+ ivshmem_vm_vbdf = ivshmem_vm.xpath("VBDF/text()")[0]
+ for vm_name in vm_name_list:
+ if vm_name.find(ivshmem_vm_name) != -1:
+ data_row[vm_name_list.index(vm_name) + 1] = "pci " +
ivshmem_vm_vbdf
+
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Write all info under the first level heading "VM X - <VM Name>"
+ def write_vms(self):
+ vm_node_list = self.scenario_etree.xpath("/acrn-config/vm")
+ for vm_node in vm_node_list:
+ vm_id = vm_node.attrib['id']
+ vm_name = vm_node.xpath("name/text()")[0]
+ self.doc.h1("VM " + f"{str(vm_id)} - " + vm_name)
+ self.doc.newline()
+ self.write_each_vm(vm_node)
+
+ # Write the information under all secondary headings for each VM in order
+ def write_each_vm(self, vm_node):
+ self.doc.h2("Basic Information")
+ column_title1, data_table1 = self.get_basic_information_table(vm_node)
+ self.doc.table(column_title1, data_table1)
+ self.doc.h2("PCI Devices")
+ column_title2, data_table2 = self.get_pci_devices_table(vm_node)
+ self.doc.table(column_title2, data_table2)
+ self.doc.h2("Fixed I/O Addresses")
+ column_title3, data_table3 = self.get_fixed_io_address_table(vm_node)
+ self.doc.table(column_title3, data_table3)
+
+ # Get basic information table for VM info
+ def get_basic_information_table(self, vm_node):
+ parameter_dict = {}
+ memory_size = 0
+ vcpu_num = set()
+ data_table = []
+ column_title = ["Parameter", "Configuration"]
+ load_order = vm_node.xpath("load_order")[0].text
+ vm_name = vm_node.xpath("name")[0].text
+ if len(vm_node.xpath("memory/hpa_region")) == 0 and
len(vm_node.xpath("memory/size")) == 0:
+ memory_size = " "
+ else:
+ if len(vm_node.xpath("memory/hpa_region")) == 0:
+ memory_size = int(vm_node.xpath("memory/size/text()")[0])
+ else:
+ hpa_region_list = vm_node.xpath("memory/hpa_region")
+ for hpa_region in hpa_region_list:
+ memory_size = memory_size +
int(hpa_region.xpath("size_hpa/text()")[0])
+ vm_vcpu_info_l2 = self.get_vm_used_vcpu("2")
+ vm_vcpu_info_l3 = self.get_vm_used_vcpu("3")
+ if vm_name in vm_vcpu_info_l2.keys():
+ for item in vm_vcpu_info_l2[vm_name]:
+ vcpu_num.add(item)
+ if vm_name in vm_vcpu_info_l3.keys():
+ for item in vm_vcpu_info_l3[vm_name]:
+ vcpu_num.add(item)
+ parameter_dict["Load Order"] = load_order
+ parameter_dict["Number of vCPUs"] = len(vcpu_num)
+ parameter_dict["Ammount of RAM"] = str(memory_size) + "MB"
+ parameter_dict["Amount of L3 Cache"] = "Amount of L3 Cache"
+ for k, v in parameter_dict.items():
+ data_row = [k, v]
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Get pci device table for VM info
+ @classmethod
+ def get_pci_devices_table(cls, vm_node):
+ data_table = []
+ column_title = ["Parameter", "Configuration"]
+ pci_devices_list = vm_node.xpath("pci_devs/pci_dev")
+ if len(pci_devices_list) != 0:
+ for pci_device in pci_devices_list:
+ data_row = pci_device.text.split(" ", 1)
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Get fixed io address table for VM info
+ def get_fixed_io_address_table(self, vm_node):
+ data_table = []
+ column_title = ["I/O Address", "Function Description"]
+ vm_name = vm_node.xpath("name")[0].text
+ for k, v in self.io_port.items():
+ if k in vm_name:
+ for item in v:
+ data_row = [item, "Virtual UART"]
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Close the Rst file after all information is written.
+ def close_file(self):
+ self.file.close()
+
+
+a = GenerateRst(rst_file_name="config_summary.rst",
+ file_access_mode='a+',
+ board_file_name="adl-s-crb.board.xml",
+ scenario_file_name="scenario.xml")
+a.write_configuration_rst()
diff --git a/misc/config_tools/scenario_config/config_summary.py
b/misc/config_tools/scenario_config/config_summary.py
new file mode 100644
index 000000000..cff2450e3
--- /dev/null
+++ b/misc/config_tools/scenario_config/config_summary.py
@@ -0,0 +1,392 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 Intel Corporation.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+import argparse
+from rstcloth import RstCloth
+from lxml import etree
+
+
+class GenerateRst:
+ io_port = {}
+ io_description = []
+ pci_vuart = {}
+ pci_ivshmem = {}
+ amount_l3_cache = {}
+
+ # Class initialization
+ def __init__(self, board_file_name, scenario_file_name, rst_file_name,
file_access_mode) -> None:
+ self.board_etree = etree.parse(board_file_name)
+ self.scenario_file_name = scenario_file_name
+ self.scenario_etree = etree.parse(scenario_file_name)
+ self.file = open(rst_file_name, file_access_mode)
+ self.doc = RstCloth(self.file)
+
+ # The rst content is written in three parts according to the first level title
+ # 1. Hardware Resource Allocation 2. Inter-VM Connections 3. VM info
+ def write_configuration_rst(self):
+ self.doc.title(f"ACRN Scenario <{self.scenario_file_name}> - Datasheet")
+ self.doc.newline()
+ self.write_hardware_resource_allocation()
+ self.write_inter_vm_connections()
+ self.write_vms()
+ self.close_file()
+
+ # Write all info under the first level heading "Hardware Resource Allocation"
+ def write_hardware_resource_allocation(self):
+ self.doc.h1("Hardware Resource Allocation")
+ self.doc.newline()
+ self.write_pcpu()
+ self.write_shared_cache()
+
+ # Write physical CPUs info table
+ def write_pcpu(self):
+ self.doc.h2("Physical CPUs")
+ column_title, data_table = self.get_pcpu_table()
+ self.doc.table(column_title, data_table)
+
+ # Get all VM names info from the scenario.xml
+ def get_vm_name(self):
+ return list(
+ map(lambda x: f"<VM {x.attrib['id']} {x.find('name').text}>",
self.scenario_etree.xpath("/acrn-config/vm")))
+
+ # get the column_title and datatable required by the physical CPUs table
+ def get_pcpu_table(self):
+ data_table = []
+ column_title = [" "]
+ service_vm_used_pcpu_list = []
+ vm_node_list = self.scenario_etree.xpath("/acrn-config/vm")
+ total_pcpu_list = self.get_pcpu()
+ pre_launch_vm_node_list = self.scenario_etree.xpath("vm[load_order =
'PRE_LAUNCHED_VM']")
+
+ for item in total_pcpu_list:
+ column_title.append(str(item))
You can use the `extend` method of lists to avoid the for-loop. That applies to all code that has the following form:

for x in something:
y.append(fn(x))

which can be transformed to:

y.extend(map(fn, something))

+
+ if pre_launch_vm_node_list is not None and len(pre_launch_vm_node_list) != 0:
+ for pre_launch_vm_node in pre_launch_vm_node_list:
+ pre_launch_vm_pcpu_list =
pre_launch_vm_node.xpath("cpu_affinity/pcpu/pcpu_id/text()")
+ for pcpu in total_pcpu_list:
+ if pcpu not in pre_launch_vm_pcpu_list:
+ service_vm_used_pcpu_list.append(int(pcpu))
Just `extend` service_vm_used_pcpu_list using the difference of `total_pcpu_list` and `pre_launch_vm_pcpu_list` after converting them into sets. Also I think `total_pcpu_list` already has integers, and you do not need to convert it once more.


+ else:
+ for pcpu in total_pcpu_list:
+ service_vm_used_pcpu_list.append(int(pcpu))
Ditto.

+
+ for vm_node in vm_node_list:
+ vm_pcpu_id_list = []
+ data_row = [" "] * (len(total_pcpu_list) + 1)
+ data_row[0] = f"<VM {vm_node.attrib['id']} {vm_node.find('name').text}>"
It seems the VM name expression above appears multiple times in this script. Please abstract it as a private method to avoid code duplication.

Alternatively you can simply use VM name here. The `<VM 0 name>` in the template is a placeholder for the VM name only.

+ vm_load_order = vm_node.find("load_order").text
+ vm_pcpu_id_node = vm_node.xpath("cpu_affinity/pcpu/pcpu_id")
+
+ for pcpu_info in vm_pcpu_id_node:
+ vm_pcpu_id_list.append(int(pcpu_info.text))
+
+ if len(vm_pcpu_id_node) == 0 and vm_load_order == "SERVICE_VM":
+ for pcpu in service_vm_used_pcpu_list:
+ data_row[pcpu + 1] = "*"
+ else:
+ for pcpu in vm_pcpu_id_list:
+ data_row[pcpu + 1] = "*"
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Get all physical CPU information from board.xml
+ def get_pcpu(self):
+ pcpu_list = self.board_etree.xpath("processors/die/core/thread/cpu_id/text()")
+ pcpu_list = list(map(int, pcpu_list))
Just combine the two lines above into one.

+ return pcpu_list
+
+ def write_shared_cache(self):
+ self.doc.h2("Shared Cache")
+ self.doc.newline()
+ self.write_l2_shared_cache()
+ self.write_l3_shared_cache()
+
+ def write_l2_shared_cache(self):
+ column_title, data_table = self.get_vcpu_table(cache_level='2')
+ self.doc.h3("Level-2 Cache")
+ self.doc.table(column_title, data_table)
+ self.doc.content("Level-2 caches are local on all physical CPUs.")
L2 cache are not necessarily local. They are for P-cores, but not for E-cores which typically have 4 sharing the same L2 cache.

+ self.doc.newline()
+
+ def write_l3_shared_cache(self):
+ column_title, data_table = self.get_vcpu_table(cache_level='3')
+ self.doc.h3("Level-3 Cache")
+ self.doc.table(column_title, data_table)
+ self.doc.note(name="note", content="Each cache chunk is 1MB.")
The size of each cache chunk (or cache way in official terms) shall be calculated by the total size and number of ways of L3 instead of being hardcoded.

+
+ # Get used vcpu table
+ def get_vcpu_table(self, cache_level):
+ data_table = []
+ column_title = [" "]
+ pcpu_list = self.get_pcpu()
+ vm_vcpu_info = self.get_vm_used_vcpu(cache_level)
+ for pcpu_info in pcpu_list:
+ column_title.append(str(pcpu_info))
For L2 and L3 caches, what you need to show as tables is the allocation of cache chunks (or cache ways). The CPU affinity of vCPUs is already shown elsewhere.

+ for vm_name, vcpu_id_list in vm_vcpu_info.items():
+ for vcpu_id in vcpu_id_list:
+ data_row = [vm_name + " vCPU " + str(vcpu_id)]
+ for i in range(len(pcpu_list)):
+ data_row.append(" ")
Just define `data_row` as `[vm_name + " vCPU " + str(vcpu_id)] + ([" "] * len(pcpu_list))`.

+ data_row[vcpu_id + 1] = "*"
+ data_table.append(data_row)
+ return column_title, sorted(data_table, key=lambda x: x[-1])
+
+ # Get the vcpu info used by each VM from scenario.xml
+ def get_vm_used_vcpu(self, cache_level):
+ vm_used_vcpu_info = {}
+ cache_allocation_list = self.scenario_etree.xpath("/acrn-
config/hv/CACHE_REGION/CACHE_ALLOCATION")
+ for cache_allocation_node in cache_allocation_list:
+ vm_name = []
+ vcpu_num = []
+ if cache_allocation_node.find("CACHE_LEVEL").text == cache_level:
+ policy_node_list = cache_allocation_node.xpath("POLICY")
+ for policy_node in policy_node_list:
+ vm_name.append(policy_node.find("VM").text)
+ vcpu_num.append(policy_node.find("VCPU").text)
+
+ for index in range(len(vm_name)):
+ pre_vm_name = vm_name[index]
+ if pre_vm_name == vm_name[index]:
+ if vm_name[index] not in vm_used_vcpu_info:
+ vm_used_vcpu_info[vm_name[index]] = [int(vcpu_num[index])]
+ else:
+ vm_used_vcpu_info[vm_name[index]].append(int(vcpu_num[index]))
+ else:
+ vm_used_vcpu_info[pre_vm_name] = [int(vcpu_num[index])]
+ return vm_used_vcpu_info
+
+ # Write all info under the first level heading "Inter-VM Connections"
+ def write_inter_vm_connections(self):
+ self.doc.h1("Inter-VM Connections")
+ self.doc.newline()
+ self.write_virtual_uarts()
+ self.write_shared_memory()
+
+ # Write the virtual uarts info according to the virtual uarts table
+ def write_virtual_uarts(self):
+ self.doc.h2("Virtual UARTs")
+ self.doc.newline()
+ self.doc.content("The table below summarizes the virtual UART connections in the
system.")
+ column_title, data_table = self.get_virtual_uarts_table()
+ self.doc.table(column_title, data_table)
+
+ # Get virtual uarts table
+ def get_virtual_uarts_table(self):
+ data_table = []
+ vm_name_list = self.get_vm_name()
+ column_title = ["vUART Connection"]
+ for vm_name in vm_name_list:
+ column_title.append(vm_name)
+ vuart_connections_list = self.scenario_etree.xpath("/acrn-
config/hv/vuart_connections/vuart_connection")
+ for vuart_connection in vuart_connections_list:
+ data_row = [""] * (len(vm_name_list) + 1)
+ pci_row = [""] * 3
+ vc_name = vuart_connection.find("name").text
+ vc_type = vuart_connection.find("type").text
+ vc_endpoint_list = vuart_connection.xpath("endpoint")
+ data_row[0] = vc_name
+ for vc_endpoint_node in vc_endpoint_list:
+ vc_vm_name = vc_endpoint_node.find("vm_name").text
+ vc_io_port = vc_endpoint_node.find("io_port").text
+ if vc_endpoint_node.find("vbdf") is not None:
+ vc_vbdf = vc_endpoint_node.find("vbdf").text
+ pci_row[1] = vc_vbdf
+ else:
+ vc_vbdf = ""
+ if vc_vm_name not in self.io_port.keys():
+ self.io_port[vc_vm_name] = [vc_io_port]
+ else:
+ self.io_port[vc_vm_name].append(vc_io_port)
+ self.io_description.append(vc_name)
+ for vm_name in vm_name_list:
+ if vm_name.find(vc_vm_name) != -1:
+ data_row[vm_name_list.index(vm_name) + 1] = f"{vc_type}
{vc_io_port} {vc_vbdf}"
+ pci_row[0] = vc_vm_name
+ pci_row[2] = f"{vc_name} {vc_type} {vc_io_port}"
+ if pci_row[0] not in self.pci_vuart:
+ self.pci_vuart[pci_row[0]] = [pci_row[-2:]]
+ else:
+ self.pci_vuart[pci_row[0]].append(pci_row[-2:])
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Write the shared memory information according to the shared memory table
+ def write_shared_memory(self):
+ self.doc.h2("Shared Memory")
+ self.doc.newline()
+ self.doc.content("The table below summarizes topology of shared memory in the
system.")
+ column_title, data_table = self.get_shared_memory_table()
+ self.doc.table(column_title, data_table)
+
+ # Get shared memory table
+ def get_shared_memory_table(self):
+ data_table = []
+ vm_name_list = self.get_vm_name()
+ column_title = ["Shared Memory Block"]
+ for vm_name in vm_name_list:
+ column_title.append(vm_name)
+ ivshmem_region_list = self.scenario_etree.xpath("/acrn-
config/hv/FEATURES/IVSHMEM/IVSHMEM_REGION")
+ for ivshmem_region_node in ivshmem_region_list:
+ data_row = [""] * (len(vm_name_list) + 1)
+ ivshmem_row = [""] * 3
+ ir_name = ivshmem_region_node.find("NAME").text
+ powered_by = ivshmem_region_node.find("PROVIDED_BY").text
+ data_row[0] = ir_name
+ for ivshmem_vm in ivshmem_region_node.xpath("IVSHMEM_VMS/IVSHMEM_VM"):
+ ivshmem_vm_name = ivshmem_vm.find("VM_NAME").text
+ ivshmem_vm_vbdf = ivshmem_vm.find("VBDF").text
+ for vm_name in vm_name_list:
+ if vm_name.find(ivshmem_vm_name) != -1:
+ ivshmem_row[0] = ivshmem_vm_name
+ ivshmem_row[1] = ivshmem_vm_vbdf
+ ivshmem_row[2] = f"pci, {ir_name}, powered by {powered_by}"
+ data_row[vm_name_list.index(vm_name) + 1] = f"pci
{ivshmem_vm_vbdf}"
+
+ if ivshmem_row[0] not in self.pci_ivshmem:
+ self.pci_ivshmem[ivshmem_row[0]] = [ivshmem_row[-2:]]
+ else:
+ self.pci_ivshmem[ivshmem_row[0]].append(ivshmem_row[-2:])
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Write all info under the first level heading "VM X - <VM Name>"
+ def write_vms(self):
+ vm_node_list = self.scenario_etree.xpath("/acrn-config/vm")
+ for vm_node in vm_node_list:
+ vm_id = vm_node.attrib['id']
+ vm_name = vm_node.find("name").text
+ self.doc.h1(f"VM {str(vm_id)} - {vm_name}")
+ self.doc.newline()
+ self.write_each_vm(vm_node)
+
+ # Write the information under all secondary headings for each VM in order
+ def write_each_vm(self, vm_node):
+ self.doc.h2("Basic Information")
+ column_title1, data_table1 = self.get_basic_information_table(vm_node)
+ self.doc.table(column_title1, data_table1)
+ self.doc.h2("PCI Devices")
+ column_title2, data_table2 = self.get_pci_devices_table(vm_node)
+ self.doc.table(column_title2, data_table2)
+ self.doc.h2("Fixed I/O Addresses")
+ column_title3, data_table3 = self.get_fixed_io_address_table(vm_node)
+ self.doc.table(column_title3, data_table3)
+
+ # Get basic information table for VM info
+ def get_basic_information_table(self, vm_node):
+ parameter_dict = {}
+ memory_size = 0
+ vcpu_num = set()
+ data_table = []
+ column_title = ["Parameter", "Configuration"]
+ load_order = vm_node.find("load_order").text
+ vm_name = vm_node.find("name").text
+ if len(vm_node.xpath("memory/hpa_region")) == 0 and
len(vm_node.xpath("memory/size")) == 0:
+ memory_size = " "
+ else:
+ if len(vm_node.xpath("memory/hpa_region")) == 0:
+ memory_size = int(vm_node.find("memory/size").text)
+ else:
+ hpa_region_list = vm_node.xpath("memory/hpa_region")
+ for hpa_region in hpa_region_list:
+ memory_size = memory_size + int(hpa_region.find("size_hpa").text)
+ vm_vcpu_info_l2 = self.get_vm_used_vcpu("2")
+ vm_vcpu_info_l3 = self.get_vm_used_vcpu("3")
+ if vm_name in vm_vcpu_info_l2.keys():
+ for item in vm_vcpu_info_l2[vm_name]:
+ vcpu_num.add(item)
+ if vm_name in vm_vcpu_info_l3.keys():
+ for item in vm_vcpu_info_l3[vm_name]:
+ vcpu_num.add(item)
+ amount_vm_l3_cache = self.get_amount_l3_cache(vm_node)
+ parameter_dict["Load Order"] = load_order
+ parameter_dict["Number of vCPUs"] = len(vcpu_num)
+ parameter_dict["Ammount of RAM"] = str(memory_size) + "MB"
+ parameter_dict["Amount of L3 Cache"] = amount_vm_l3_cache
+ data_table.extend(map(list, parameter_dict.items()))
+ return column_title, data_table
+
+ # get Amount of L3 Cache info
+ def get_amount_l3_cache(self, vm_node):
+ vm_bit_map = 0
+ total_cache_size = 0
+ total_cache_ways = 0
+ vm_name = vm_node.find("name").text
+ l3_cache_node_list = self.board_etree.xpath("caches/cache [@level = '3']")
+ cache_allocation_node_list = self.scenario_etree.xpath("/acrn-
config/hv/CACHE_REGION/CACHE_ALLOCATION")
+
+ for l3_cache_node in l3_cache_node_list:
+ total_cache_size += int(l3_cache_node.find("cache_size").text)
+ total_cache_ways += int(l3_cache_node.find("ways").text)
+ each_cache_way_size = total_cache_size / 1024 / 1024 / total_cache_ways
+
+ for cache_allocation_node in cache_allocation_node_list:
+ if cache_allocation_node.find("CACHE_LEVEL").text == '3':
+ policy_node_list = cache_allocation_node.xpath("POLICY")
+ for policy_node in policy_node_list:
+ if vm_name == policy_node.find("VM").text:
+ vm_bit_map |= int(policy_node.find("CLOS_MASK").text, base=16)
+ vm_cache_way = bin(vm_bit_map).count('1')
+ amount_vm_l3_cache = f"{vm_cache_way * each_cache_way_size} MB"
+ return amount_vm_l3_cache
+
+ # Get pci device table for VM info
+ def get_pci_devices_table(self, vm_node):
+ data_table = []
+ vm_name = vm_node.find("name").text
+ column_title = ["Parameter", "Configuration"]
+ pci_devices_list = vm_node.xpath("pci_devs/pci_dev")
+ if len(pci_devices_list) != 0:
+ for pci_device in pci_devices_list:
+ data_row = pci_device.text.split(" ", 1)
+ data_table.append(data_row)
+ if vm_name in self.pci_vuart.keys():
+ for item in self.pci_vuart[vm_name]:
+ data_row = [item[0], item[1]]
+ data_table.append(data_row)
+ if vm_name in self.pci_ivshmem.keys():
+ for item in self.pci_ivshmem[vm_name]:
+ data_row = [item[0], item[1]]
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Get fixed io address table for VM info
+ def get_fixed_io_address_table(self, vm_node):
+ data_table = []
+ column_title = ["I/O Address", "Function Description"]
+ vm_name = vm_node.find("name").text
+ for k, v in self.io_port.items():
+ if k in vm_name:
+ for item in v:
+ data_row = [item, "Virtual UART"]
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Close the Rst file after all information is written.
+ def close_file(self):
+ self.file.close()
+
+
+def main(args):
+ GenerateRst(board_file_name=args.board_file_name,
scenario_file_name=args.scenario_file_name,
+ rst_file_name=args.rst_file_name,
file_access_mode=args.rst_file_access_mode).write_configuration_rst()
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument("board_file_name", default="adl-s-crb.board.xml",
+ help="Specifies the board config XML path and name used to
generate the rst file")
+ parser.add_argument("scenario_file_name", default="scenario.xml",
+ help="Specifies the scenario XML path and name used to generate
the rst file")
I don't think you need default values for board and scenario file names.

+ parser.add_argument("rst_file_name", default="config_summary.rst",
+ help="the path and name of the output rst file that "
+ "summaries the config from scenario.xml and board.xml")
+ parser.add_argument("rst_file_access_mode", default="w",
+ help="specifies the operating mode of rst file, e.g. 'a', 'w' ")
The access mode, if really necessary, can be defined as an option rather than a positional argument.

---
Best Regards
Junjie Mao

+
+ args = parser.parse_args()
+ main(args)
--
2.35.1.windows.2



-----邮件原件-----
发件人: Mao, Junjie <junjie.mao@...>
发送时间: Friday, August 26, 2022 7:52 PM
收件人: Li, Ziheng <ziheng.li@...>
抄送: acrn-dev@...
主题: Re: [PATCH] doc: generate config_summary.rst

"Li, Ziheng" <ziheng.li@...> writes:

From 12922e1b3e1a01be9efb3d827ac10f0de1656a10 Mon Sep 17 00:00:00 2001
From: zihengL1 <ziheng.li@...>
Date: Fri, 26 Aug 2022 09:11:34 +0800
Subject: [PATCH] doc: generate config_summary.rst

With the help of the functions provided in the "rstcloth" Python
package, the important information in scenario.xml and board.xml is summarized.
According to the format provided by Datasheet prototype.rst, finally,
it is output in a structured form and saved in the summary.rst file.

However, there are still some problems to be solved:
1. Where should the config_summary.py code be placed.
You can put this script under misc/config_tools/scenario_config/

2. Where should the output generated config_summary.rst file be placed.
I would recommend taking a parameter from the command line to allow users specify where
the report should be generated. You can use the argparse package for this.

3. Where should we call the above code in Makefile or in other file.
The intended usage of this script is in the configurator, not the Makefile. We can
integrate that in a separate patch.

4. It seems that the "Amount of L3 Cache" info under the "Basic Information"
heading could not be found in scenario.xml.
Do you mean the size of L3 cache? You can find it using XPATH
`//caches/cache[@level='3']/cache_size` which reports the size of L3 cache in bytes.


Tracked-On: #8063
Signed-off-by: Ziheng Li <ziheng.li@...>
---
config_summary.py | 319
++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 319 insertions(+)
create mode 100644 config_summary.py

diff --git a/config_summary.py b/config_summary.py new file mode
100644 index 000000000..f71cf2384
--- /dev/null
+++ b/config_summary.py
@@ -0,0 +1,319 @@
+from rstcloth import RstCloth
+from lxml import etree
+
+
+class GenerateRst:
+ io_port = {}
+ io_description = []
+
+ # Class initialization
+ def __init__(self, rst_file_name, file_access_mode, board_file_name,
scenario_file_name) -> None:
+ self.file = open(rst_file_name, file_access_mode)
+ self.doc = RstCloth(self.file)
+ self.scenario_file_name = scenario_file_name
+ self.board_etree = etree.parse(board_file_name)
+ self.scenario_etree = etree.parse(scenario_file_name)
+
+ # The rst content is written in three parts according to the first level title
+ # 1. Hardware Resource Allocation 2. Inter-VM Connections 3. VM info
+ def write_configuration_rst(self):
+ self.doc.title(f'ACRN Scenario <{self.scenario_file_name}> - Datasheet')
+ self.doc.newline()
+ self.write_hardware_resource_allocation()
+ self.write_inter_vm_connections()
+ self.write_vms()
+ self.close_file()
+
+ # Write all info under the first level heading "Hardware Resource Allocation"
+ def write_hardware_resource_allocation(self):
+ self.doc.h1("Hardware Resource Allocation")
+ self.doc.newline()
+ self.write_pcpu()
+ self.write_shared_cache()
+
+ # Write physical CPUs info table
+ def write_pcpu(self):
+ self.doc.h2("Physical CPUs")
+ vm_name_list = self.get_vm_name()
+ vm_used_pcpu_info = self.get_vm_used_pcpu()
+ column_title, data_table = self.get_pcpu_table(vm_name_list, vm_used_pcpu_info)
+ self.doc.table(column_title, data_table)
+
+ # Get all VM names info from the scenario.xml
+ def get_vm_name(self):
+ vm_name_list = []
+ vm_node_list = self.scenario_etree.xpath("/acrn-config/vm")
+ for vm_node in vm_node_list:
+ vm_name_node = vm_node.xpath("name/text()")
+ vm_name_list.append("<VM " + f"{vm_node.attrib['id']} " +
+ f"{vm_name_node[0]}>")
There is no need to concatenate multiple format strings; just combine them into a single
format string.

+ return vm_name_list
This whole function can be written in a single line:

return map(lambda x: f"<VM {x['id']} {x.find('name').text}>",
self.scenario_etree.xpath("/acrn-config/vm"))

That will generate a map object which can be iterated to get all VM names. If you still
need it to be a list, apply the list() constructor before returning.

+
+ # Get the info about which PCUs are actually used by each VM from scenario.xml
+ def get_vm_used_pcpu(self):
+ vm_used_pcpu_info = {}
+ vm_node_list = self.scenario_etree.xpath("/acrn-config/vm")
+ for vm_node in vm_node_list:
+ vm_pcpu_id = []
+ vm_pcpu_id_node = vm_node.xpath("cpu_affinity/pcpu/pcpu_id")
+ if len(vm_pcpu_id_node) == 0:
+ vm_used_pcpu_info[int(vm_node.attrib['id'])] = []
Service VM needs to be considered separately. It uses all physical CPU except those
assigned to any pre-launched VM.

+ continue
+ for pcpu_info in vm_pcpu_id_node:
+ vm_pcpu_id.append(int(pcpu_info.text))
+ vm_used_pcpu_info[int(vm_node.attrib['id'])] = vm_pcpu_id
+ return vm_used_pcpu_info
+
+ # get the column_title and datatable required by the physical CPUs table
+ @classmethod
+ def get_pcpu_table(cls, vm_name_list, vm_used_pcpu_info):
+ data_table = []
+ column_title = [" "]
+ actual_used_pcpu_list = []
+ for v in vm_used_pcpu_info.values():
+ for item in v:
+ if item not in actual_used_pcpu_list:
+ actual_used_pcpu_list.append(item)
+ actual_used_pcpu_list.sort()
+ for item in actual_used_pcpu_list:
+ column_title.append(str(item))
+ for vm_name in vm_name_list:
+ data_row = [vm_name]
+ for i in range(len(actual_used_pcpu_list)):
+ data_row.append(" ")
+ for item in vm_used_pcpu_info[vm_name_list.index(vm_name)]:
+ data_row[actual_used_pcpu_list.index(item) + 1] = " " + "*"
+ data_table.append(data_row)
+ return column_title, data_table
I think you can simplify the generation of the pCPU table as follows:

1. Get the list of all physical CPUs. This can be done by the XPATH
`//processors//thread/cpu_id/text()`. The column titles can be
derived solely from that.

2. Iterate over VM nodes in the scenario XML. For each of them, get
its name and used physical CPUs to format a row in the table.

When getting the pCPUs used by a VM, the service VM should be specially treated.

To format the row, contents in the cells can be calculated by a `map` operation, i.e.
`list(map(lambda x: "*" if x in used_pcpus else " ", all_pcpus))`.

With those you don't need the get_vm_used_pcpu() helper anymore.

Note that it still helps list a pCPU that is not used by any VM so that users always have
a complete view.

+
+ # Get all physical CPU information from board.xml
+ def get_pcpu(self):
+ pcpu_list =
+ self.board_etree.xpath("/acrn-config/CPU_PROCESSOR_INFO/text()")[0].
+ strip().split(',')
Using the text nodes generated by the legacy board inspector is not recommended. Simply
use the XPATH `//processors//thread/cpu_id/text()`.

+ pcpu_list = list(map(int, pcpu_list))
+ return pcpu_list
+
+ def write_shared_cache(self):
+ self.doc.h2("Shared Cache")
+ self.doc.newline()
+ self.write_l2_shared_cache()
+ self.write_l3_shared_cache()
+
+ def write_l2_shared_cache(self):
+ column_title, data_table = self.get_vcpu_table(cache_level="2")
+ self.doc.h3("Level-2 Cache")
+ self.doc.table(column_title, data_table)
+ self.doc.content("Level-2 caches are local on all physical CPUs.")
+ self.doc.newline()
+
+ def write_l3_shared_cache(self):
+ column_title, data_table = self.get_vcpu_table(cache_level="3")
+ self.doc.h3("Level-3 Cache")
+ self.doc.table(column_title, data_table)
+ self.doc.note(name="note", content="Each cache chunk is
+ 1MB.")
+
+ # Get used vcpu table
+ def get_vcpu_table(self, cache_level):
+ data_table = []
+ column_title = [" "]
+ pcpu_list = self.get_pcpu()
+ vm_vcpu_info = self.get_vm_used_vcpu(cache_level)
+ for pcpu_info in pcpu_list:
+ column_title.append(str(pcpu_info))
+ for vm_name, vcpu_id_list in vm_vcpu_info.items():
+ for vcpu_id in vcpu_id_list:
+ data_row = [vm_name + " vCPU " + str(vcpu_id)]
+ for i in range(len(pcpu_list)):
+ data_row.append(" ")
+ data_row[vcpu_id + 1] = " " + "*"
+ data_table.append(data_row)
+ return column_title, sorted(data_table, key=lambda x: x[-1])
+
+ # Get the vcpu info used by each VM from scenario.xml
+ def get_vm_used_vcpu(self, cache_level):
+ vm_vcpu_info = {}
+ cache_allocation_list = self.scenario_etree.xpath("/acrn-
config/hv/CACHE_REGION/CACHE_ALLOCATION")
+ for cache_allocation_node in cache_allocation_list:
+ vm_name = []
+ vcpu_num = []
+ if cache_allocation_node.xpath("CACHE_LEVEL/text()")[0] == cache_level:
+ policy_node_list = cache_allocation_node.xpath("POLICY")
+ for policy_node in policy_node_list:
+ vm_name.append(policy_node.xpath("VM/text()")[0])
+
+ vcpu_num.append(policy_node.xpath("VCPU/text()")[0])
+
+ for i in range(len(vm_name)):
+ pre_vm_name = vm_name[i]
+ if pre_vm_name == vm_name[i]:
+ if vm_name[i] not in vm_vcpu_info:
+ vm_vcpu_info[vm_name[i]] = [int(vcpu_num[i])]
+ else:
+ vm_vcpu_info[vm_name[i]].append(int(vcpu_num[i]))
+ else:
+ vm_vcpu_info[pre_vm_name] = [int(vcpu_num[i])]
+ return vm_vcpu_info
Similar to `get_pcpu_table()`, I think this method can be greatly simplified in the same
way.

+
+ # Write all info under the first level heading "Inter-VM Connections"
+ def write_inter_vm_connections(self):
+ self.doc.h1("Inter-VM Connections")
+ self.doc.newline()
+ self.write_virtual_uarts()
+ self.write_shared_memory()
+
+ # Write the virtual uarts info according to the virtual uarts table
+ def write_virtual_uarts(self):
+ self.doc.h2("Virtual UARTs")
+ self.doc.newline()
+ self.doc.content("The table below summarizes the virtual UART connections in
the system.")
+ column_title, data_table = self.get_virtual_uarts_table()
+ self.doc.table(column_title, data_table)
+
+ # Get virtual uarts table
+ def get_virtual_uarts_table(self):
+ data_table = []
+ vm_name_list = self.get_vm_name()
+ column_title = ["vUART Connection"]
+ for vm_name in vm_name_list:
+ column_title.append(vm_name)
+ vuart_connections_list = self.scenario_etree.xpath("/acrn-
config/hv/vuart_connections/vuart_connection")
+ for vuart_connection in vuart_connections_list:
+ data_row = [""] * (len(vm_name_list) + 1)
+ vc_name = vuart_connection.xpath("name/text()")[0]
To get the text of a child, use `vuart_connection.find("name").text` is simplier.

+ vc_type = vuart_connection.xpath("type/text()")[0]
+ vc_endpoint_list = vuart_connection.xpath("endpoint")
+ data_row[0] = vc_name
+ for vc_endpoint_node in vc_endpoint_list:
+ vc_vm_name = vc_endpoint_node.xpath("vm_name/text()")[0]
+ vc_io_port = vc_endpoint_node.xpath("io_port/text()")[0]
+ vc_vbdf = vc_endpoint_node.xpath("vbdf/text()")[0]
+ if vc_vm_name not in self.io_port.keys():
+ self.io_port[vc_vm_name] = [vc_io_port]
+ else:
+ self.io_port[vc_vm_name].append(vc_io_port)
+ self.io_description.append(vc_name)
+ for vm_name in vm_name_list:
+ if vm_name.find(vc_vm_name) != -1:
+ data_row[vm_name_list.index(vm_name) + 1] =
+ vc_type + " " + vc_io_port + " " + vc_vbdf
+
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Write the shared memory information according to the shared memory table
+ def write_shared_memory(self):
+ self.doc.h2("Shared Memory")
+ self.doc.newline()
+ self.doc.content("The table below summarizes topology of shared memory in the
system.")
+ column_title, data_table = self.get_shared_memory_table()
+ self.doc.table(column_title, data_table)
+
+ # Get shared memory table
+ def get_shared_memory_table(self):
+ data_table = []
+ vm_name_list = self.get_vm_name()
+ column_title = ["Shared Memory Block"]
+ for vm_name in vm_name_list:
+ column_title.append(vm_name)
+ ivshmem_region_list = self.scenario_etree.xpath("/acrn-
config/hv/FEATURES/IVSHMEM/IVSHMEM_REGION")
+ for ivshmem_region_node in ivshmem_region_list:
+ data_row = [""] * (len(vm_name_list) + 1)
+ ir_name = ivshmem_region_node.xpath("NAME/text()")[0]
+ data_row[0] = ir_name
+ for ivshmem_vm in ivshmem_region_node.xpath("IVSHMEM_VMS/IVSHMEM_VM"):
+ ivshmem_vm_name = ivshmem_vm.xpath("VM_NAME/text()")[0]
+ ivshmem_vm_vbdf = ivshmem_vm.xpath("VBDF/text()")[0]
+ for vm_name in vm_name_list:
+ if vm_name.find(ivshmem_vm_name) != -1:
+ data_row[vm_name_list.index(vm_name) + 1] =
+ "pci " + ivshmem_vm_vbdf
+
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Write all info under the first level heading "VM X - <VM Name>"
+ def write_vms(self):
+ vm_node_list = self.scenario_etree.xpath("/acrn-config/vm")
+ for vm_node in vm_node_list:
+ vm_id = vm_node.attrib['id']
+ vm_name = vm_node.xpath("name/text()")[0]
+ self.doc.h1("VM " + f"{str(vm_id)} - " + vm_name)
Combine the substrings into one single format string.

+ self.doc.newline()
+ self.write_each_vm(vm_node)
+
+ # Write the information under all secondary headings for each VM in order
+ def write_each_vm(self, vm_node):
+ self.doc.h2("Basic Information")
+ column_title1, data_table1 = self.get_basic_information_table(vm_node)
+ self.doc.table(column_title1, data_table1)
+ self.doc.h2("PCI Devices")
+ column_title2, data_table2 = self.get_pci_devices_table(vm_node)
+ self.doc.table(column_title2, data_table2)
+ self.doc.h2("Fixed I/O Addresses")
+ column_title3, data_table3 = self.get_fixed_io_address_table(vm_node)
+ self.doc.table(column_title3, data_table3)
+
+ # Get basic information table for VM info
+ def get_basic_information_table(self, vm_node):
+ parameter_dict = {}
+ memory_size = 0
+ vcpu_num = set()
+ data_table = []
+ column_title = ["Parameter", "Configuration"]
+ load_order = vm_node.xpath("load_order")[0].text
+ vm_name = vm_node.xpath("name")[0].text
+ if len(vm_node.xpath("memory/hpa_region")) == 0 and
len(vm_node.xpath("memory/size")) == 0:
+ memory_size = " "
+ else:
+ if len(vm_node.xpath("memory/hpa_region")) == 0:
+ memory_size = int(vm_node.xpath("memory/size/text()")[0])
+ else:
+ hpa_region_list = vm_node.xpath("memory/hpa_region")
+ for hpa_region in hpa_region_list:
+ memory_size = memory_size +
int(hpa_region.xpath("size_hpa/text()")[0])
+ vm_vcpu_info_l2 = self.get_vm_used_vcpu("2")
+ vm_vcpu_info_l3 = self.get_vm_used_vcpu("3")
+ if vm_name in vm_vcpu_info_l2.keys():
+ for item in vm_vcpu_info_l2[vm_name]:
+ vcpu_num.add(item)
Why not using `vcpu_num.extend(vm_vcpu_info_l2[vm_name])`?

+ if vm_name in vm_vcpu_info_l3.keys():
+ for item in vm_vcpu_info_l3[vm_name]:
+ vcpu_num.add(item)
+ parameter_dict["Load Order"] = load_order
+ parameter_dict["Number of vCPUs"] = len(vcpu_num)
+ parameter_dict["Ammount of RAM"] = str(memory_size) + "MB"
+ parameter_dict["Amount of L3 Cache"] = "Amount of L3 Cache"
+ for k, v in parameter_dict.items():
+ data_row = [k, v]
+ data_table.append(data_row)
The above three lines can be combined into one line as follows.

data_table.extend(map(list, parameter_dict.items()))

+ return column_title, data_table
+
+ # Get pci device table for VM info
+ @classmethod
+ def get_pci_devices_table(cls, vm_node):
+ data_table = []
+ column_title = ["Parameter", "Configuration"]
+ pci_devices_list = vm_node.xpath("pci_devs/pci_dev")
+ if len(pci_devices_list) != 0:
+ for pci_device in pci_devices_list:
+ data_row = pci_device.text.split(" ", 1)
+ data_table.append(data_row)
Virtual PCI devices such as IVSHMEM or PCI vUART should be listed here as well.

+ return column_title, data_table
+
+ # Get fixed io address table for VM info
+ def get_fixed_io_address_table(self, vm_node):
+ data_table = []
+ column_title = ["I/O Address", "Function Description"]
+ vm_name = vm_node.xpath("name")[0].text
+ for k, v in self.io_port.items():
+ if k in vm_name:
+ for item in v:
+ data_row = [item, "Virtual UART"]
+ data_table.append(data_row)
+ return column_title, data_table
+
+ # Close the Rst file after all information is written.
+ def close_file(self):
+ self.file.close()
+
+
+a = GenerateRst(rst_file_name="config_summary.rst",
+ file_access_mode='a+',
+ board_file_name="adl-s-crb.board.xml",
+ scenario_file_name="scenario.xml")
Similar to the output path, the input files should be taken from command line parameters
as well.

+a.write_configuration_rst()
Please wrap the above two statements in a main() function which is called only when
`__name__` is `__main__`. The configurator may have its own wrapper script based on this,
in which case this script will be imported there.

--
Best Regards
Junjie Mao

--
2.35.1.windows.2