diff --git a/src/backends/dlb_talp_tree/post_process/nesmik_talp_tree_viewer.py b/src/backends/dlb_talp_tree/post_process/nesmik_talp_tree_viewer.py new file mode 100644 index 0000000000000000000000000000000000000000..a70eb511cb15a2ff8ebd05a07b15f1420090be19 --- /dev/null +++ b/src/backends/dlb_talp_tree/post_process/nesmik_talp_tree_viewer.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 + +import os +import sys +import json +import math + +try: + from PySide6.QtWidgets import QApplication, QTreeView, QMainWindow, QVBoxLayout, QWidget, QPushButton + from PySide6.QtGui import QStandardItemModel, QStandardItem, QColor, QBrush, QPixmap + from PySide6 import QtCore + from PySide6.QtCore import QRect + import matplotlib.pyplot as plt + import matplotlib.colors as mcolors +except ModuleNotFoundError: + print("Error loading MODULES") + print("") + print("Please run `pip install -r requirements.txt`") + print("") + exit(1) + + +# Header Format Operation Width Alignment Gradient +headings= { + "regionName": ("Region", "{}{}", (lambda x,y: x), 400, QtCore.Qt.AlignLeft, False), + "elapsedTime": ("Time (%)", "{:11.2f}", (lambda x,y: 100 * x / y), 70, QtCore.Qt.AlignRight, True), + "parallelEfficiency": ("PE", "{:11.2f}", (lambda x,y: x * 100), 70, QtCore.Qt.AlignRight, True), + "mpiParallelEfficiency": ("MPI-PE", "{:11.2f}", (lambda x,y: x * 100), 70, QtCore.Qt.AlignRight, True), + "mpiCommunicationEfficiency": ("MPI-COM", "{:11.2f}", (lambda x,y: x * 100), 70, QtCore.Qt.AlignRight, True), + "mpiLoadBalance": ("MPI-LB", "{:11.2f}", (lambda x,y: x * 100), 70, QtCore.Qt.AlignRight, True), + "mpiLoadBalanceIn": ("MPI-LBI", "{:11.2f}", (lambda x,y: x * 100), 70, QtCore.Qt.AlignRight, True), + "mpiLoadBalanceOut": ("MPI-LBO", "{:11.2f}", (lambda x,y: x * 100), 70, QtCore.Qt.AlignRight, True), + "ompParallelEfficiency": ("OMP-PE", "{:11.2f}", (lambda x,y: x * 100), 70, QtCore.Qt.AlignRight, True), + "ompLoadBalance": ("OMP-LB", "{:11.2f}", (lambda x,y: x * 100), 70, QtCore.Qt.AlignRight, True), + "ompSchedulingEfficiency": ("OMP-SCH", "{:11.2f}", (lambda x,y: x * 100), 70, QtCore.Qt.AlignRight, True), + "ompSerializationEfficiency": ("OMP-SER", "{:11.2f}", (lambda x,y: x * 100), 70, QtCore.Qt.AlignRight, True), +} +# "elapsedTime": ("Time (s)", "{:11.2f}", (lambda x,y: x / 1000000000), 100, QtCore.Qt.AlignRight, False), + +def construct_header_row(headings): + return [ heading[0] for heading in headings.values() ] + + +class TreeTable(QMainWindow): + def __init__(self, filename): + super().__init__() + + self.filename = filename + + with open(filename, 'r') as file: + json_data = json.load(file) + + self.json_data = json_data; + self.root_elapsed_time = None + + # Set up the main window + self.setWindowTitle("neSmiK TALP::Tree") + window_width=sum([ heading[3] for heading in headings.values() ]) + 20 + self.setGeometry(100, 100, window_width, 800) + + # Create the tree view and model + self.tree_view = QTreeView() + self.tree_view.setAlternatingRowColors(True) + + self.model = QStandardItemModel() + self.model.setHorizontalHeaderLabels(construct_header_row(headings)) + self.tree_view.setModel(self.model) + + # Set fixed column widths + for (column, width) in enumerate([ heading[3] for heading in headings.values() ]): + self.tree_view.setColumnWidth(column, width) + + # Create the tree structure with color gradients + self.add_tree_data() + + self.export_button = QPushButton("Export as Image") + self.export_button.clicked.connect(self.export_as_image) + + # Set the layout + container = QWidget() + layout = QVBoxLayout() + layout.addWidget(self.tree_view) + layout.addWidget(self.export_button) + container.setLayout(layout) + self.setCentralWidget(container) + + def populate_tree_view(self, json_data, parent, parent_index): + row_items = self.construct_row(json_data) + parent.appendRow(row_items) + + percent_of_time = json_data["metrics"]["elapsedTime"] / self.root_elapsed_time + row_count = self.model.rowCount(parent_index) + my_index = self.model.index(row_count-1, 0, parent_index) + if percent_of_time >= 0.01: + self.tree_view.expand(my_index) + + for subregion in sorted(json_data["subregions"], key=lambda region: region["metrics"]["elapsedTime"], reverse=True): + self.populate_tree_view(subregion, row_items[0], my_index) + + def add_tree_data(self): + # Root item + root_item = self.model.invisibleRootItem() + + # Add data with gradients + self.populate_tree_view(self.json_data, root_item, self.model.index(0,0)) + + def format_value(self, key, data): + value = headings[key][1].format(headings[key][2](data[key], self.root_elapsed_time)) + item = QStandardItem(value) + item.setTextAlignment(headings[key][4]) + if headings[key][5]: + self.apply_gradient_value(item, value) + #else: + # self.apply_gradient_weight(item) + + return item + + def format_region_name(self, region_name): + item = QStandardItem(region_name) + item.setTextAlignment(headings["regionName"][4]) + #self.apply_gradient_weight(item) + return item + + def construct_row(self, json_data): + if self.root_elapsed_time == None: + self.root_elapsed_time = json_data["metrics"]["elapsedTime"] + self.current_opacity = (json_data["metrics"]["elapsedTime"] / self.root_elapsed_time) * 100 + self.current_opacity = (math.log(self.current_opacity, 10) / 2) * 155 + 100 + self.current_percent = (json_data["metrics"]["elapsedTime"] / self.root_elapsed_time) * 100 + + row = [self.format_value(key, json_data["metrics"]) for key in headings.keys() if key != "regionName" ] + row.insert(0, self.format_region_name(json_data["regionName"])) + return row + + def add_row(self, values, parent_item): + row_items = [] + for value in values: + item = QStandardItem(str(value)) + item.setTextAlignment(QtCore.Qt.AlignRight) + self.apply_gradient(item,value) + row_items.append(item) + parent_item.appendRow(row_items) + return row_items[0] + + def apply_gradient_value(self, item, value): + + # Set color theme + color_map='RdYlGn' + lower_bound=70 + upper_bound=100 + + norm = mcolors.Normalize(vmin=lower_bound, vmax=upper_bound) + cmap = plt.get_cmap(color_map) + + try: + # Try to convert to float + float_value=float(value) + color = cmap(norm(float_value)) + + red = color[0] * 256 + green = color[1] * 256 + blue = color[2] * 256 + + # Apply the background color + color = QColor(red, green, blue, self.current_opacity) + #color = QColor(red, green, blue) + item.setBackground(QBrush(color)) + except ValueError: + return + + def apply_gradient_weight(self, item): + # Set color theme + color_map='RdYlGn' + lower_bound=0 + upper_bound=100 + + norm = mcolors.Normalize(vmin=lower_bound, vmax=upper_bound) + cmap = plt.get_cmap(color_map) + + color = cmap(norm(self.current_percent)) + # Color gradient from red (low) to green (high) + red = color[0] * 256 + green = color[1] * 256 + blue = color[2] * 256 + + # Apply the background color + color = QColor(red, green, blue, self.current_opacity) + item.setBackground(QBrush(color)) + + def export_as_image(self): + rect = self.tree_view.viewport().rect() + + pixmap = QPixmap(rect.size()) + self.tree_view.render(pixmap) + + pixmap.save(os.path.splitext(self.filename)[0]+'.png') + + +if __name__ == "__main__": + app = QApplication(sys.argv) + window = TreeTable(sys.argv[1]) + window.show() + sys.exit(app.exec()) diff --git a/src/backends/dlb_talp_tree/post_process/requirements.txt b/src/backends/dlb_talp_tree/post_process/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..5d0142ed1f73e9a803c01761bf2fbf28eda12247 --- /dev/null +++ b/src/backends/dlb_talp_tree/post_process/requirements.txt @@ -0,0 +1 @@ +PySide6==6.7.2