########################################################################
#
# Copyright (c) 2023 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
########################################################################
#
# This file contains the Render function.
# The Rendering tool visualizes the collect bundle and generates
# an index.html file
#
########################################################################
from datetime import datetime
import os
from pathlib import Path
import re
def exclude_path():
"""Generate a set for files to be excluded
"""
exclude_file = Path(__file__).parent / 'render.exclude'
exclude_paths = set()
if exclude_file.exists():
with exclude_file.open('r') as file:
exclude_paths = set(line.strip() for line in file)
return exclude_paths
def can_open_file(file_path):
"""Test if the file can be opened or not empty
Parameters:
file_path(Path): path of the file
"""
try:
with open(file_path, 'r'):
return os.path.getsize(file_path) != 0
except IOError:
return False
def extract_section(log_contents, start_phrase):
"""Extract the correlated or plugin content of the summary
Parameters:
log_contents (string): content of the log
start_phrase (string): the name of the section extracted
"""
start = log_contents.find(start_phrase)
if start == -1:
return ""
end = log_contents.find("\n\n", start)
if end == -1:
end = len(log_contents)
return log_contents[start:end].strip()
def remove_timestamp(text):
"""Remove timestamp of summary message
Parameters:
text (string): the summary message
"""
lines = text.split('\n')
temp = []
for line in lines:
split_string = line.split(' ', 1)
# check if the first part is time format, then remove if it is
if split_string[0] and datetime.fromisoformat(split_string[0]):
temp.append(split_string[1])
else:
temp.append(line)
final_text = '\n'.join(temp)
return final_text
def remove_emptyinfo(text):
"""Remove 'INFO' text of summary message
Parameters:
text (string): the summary message
"""
lines = text.split('\n')
temp = []
for line in lines:
if line.strip() != 'INFO:':
temp.append(line)
final_text = '\n'.join(temp)
return final_text
def process_section(section, title):
"""Return text with timestamp and INFO: removed
Parameters:
section (string): the message of the correlated/plugins section
title (string): correlated/plugin results
"""
section = section[len(title):]
section = remove_timestamp(section)
section = remove_emptyinfo(section)
return section
def classify_node(data):
"""Classify node type in system_info summary
Parameters:
data (string): the summary of system_info
"""
node_type = ''
for item in data:
if 'Node Type' in item:
node_type = item.split(':')[-1].strip().lower()
return node_type
def controller_sort(x):
"""Sort the controller, place the controller-0 first
Parameters:
x (list): list of controller info
"""
return x[0] != 'controller-0'
def html_css():
"""Static css code of the rendering tool
iframe, textarea: the content panel showing information
#show-worker: the show more worker button
.container-menu: the overall layout of the page
.menu: the sidebar menu of the page
#correlated-results-toggle, #plugin-results-toggle: +/- button for results menu
"""
html_content_css = """
Report Analysis
"""
return html_content_css
def html_script():
"""Static script code
Functions:
toggleContent: show content in System Info section
toggleSub: show/hide submenus in correlated/plugin results
toggleMenu: show the correlated/plugin summary
showContentStorage: display content of selected storage item
showContentWorker: display content of selected worker item
showContentTwo: display content of result section
toggleTree: show the collect bundle
"""
html_content_script = """
"""
return html_content_script
def html_info(sys_section):
"""System info part generation
reads from plugin/system_info and show by different types
order: controller, storage(if there exists), worker(if there exists)
Parameters:
sys_section (string): the summary of system_info
"""
controller_section = []
storage_section = []
worker_section = []
for i in sys_section:
section_lines = i.strip().split("\n")
section_type = classify_node(section_lines)
if "controller" == section_type:
controller_section.append(section_lines)
if "storage" == section_type:
storage_section.append(section_lines)
if "worker" == section_type:
worker_section.append(section_lines)
controller_section = sorted(controller_section, key=controller_sort)
controller_zero = controller_section.pop(0)
sections = {
"controller": controller_section,
"storage": storage_section,
"worker": worker_section
}
html_content_one = ""
html_content_one += """
"""
return html_content_one
def html_result(log_contents, output_dir):
"""Result part generation in the menu-content style
generates correlated results, plugin results, and the items under them
subitems for plugins and correlated results under separate menus
Parameters:
log_contents (string): content of the summary
output_dir (string): the location of output
"""
# Extract sections from the log
plugin_section = extract_section(log_contents, 'Plugin Results:')
correlated_section = extract_section(log_contents, 'Correlated Results:')
# Process the extracted sections
plugin_section = process_section(plugin_section, 'Plugin Results:')
correlated_section = process_section(correlated_section, 'Correlated Results:')
# HTML part
correlated_directory = os.path.join(os.getcwd(), output_dir)
os.chdir(correlated_directory)
correlated_items = []
for file in os.listdir(correlated_directory):
if os.path.isfile(file) and '.' not in file:
correlated_items.append({'name': file, 'id': f'content-item-{file}'})
plugin_directory = os.path.join(correlated_directory, 'plugins')
os.chdir(plugin_directory)
plugin_items = []
for file in os.listdir(plugin_directory):
if os.path.isfile(file) and file != "system_info":
plugin_items.append({'name': file, 'id': f'content-item-{file}'})
html_content_two = ""
html_content_two += """
"""
return html_content_two
def generate_collect():
os.chdir('../../')
current_directory = Path('.')
finalstr = """"
return finalstr
def html_collect():
"""Collect bundle code generation
Calls a helper function to to generate the collect bundle
"""
current_directory = Path('.')
tree_html = ""
content_html = ""
target_dir = current_directory.resolve()
newtree_html, newcontent_html = generate_directory_tree(current_directory, exclude_path(), target_dir, 0)
tree_html += newtree_html
content_html += newcontent_html
content_html += "
"
html_content_three = """"
return html_content_three
def generate_directory_tree(directory_path, exclude_path, target_dir, is_top_level):
"""Helper function for Collect bundle generation
Parameters:
directory_path(Path): the path of the directory in each call
target_dir(string): the path of the file/folder
is_top_level(bool): if the level is the top level of the collect bundle
"""
directory_name = directory_path.name
tree_html = ""
content_html = ""
approved_list = ['.log', '.conf', '.info', '.json', '.alarm', '.pid', '.list', '.lock', '.txt']
if is_top_level == 1:
temp_name = re.sub(r'[^a-zA-Z0-9]', '', directory_name)
tree_html = f''
if is_top_level > 1:
tree_html = f'- {directory_name}
'
for item in directory_path.iterdir():
# write to a file called 'exclude', all the files including the full path
# if item in exclude, do not add to html
# else add it to another
item_path = str(item)
if not any(exclude_item in item_path for exclude_item in exclude_path):
try:
if item.is_dir() and item.name != "report_analysis":
nested_tree_html, nested_content_html = generate_directory_tree(item, exclude_path, target_dir, is_top_level + 1)
tree_html += nested_tree_html
content_html += nested_content_html
elif item.is_file():
if not can_open_file(item):
tree_html += f'- {item}
'
else:
if item.name.endswith(tuple(approved_list)):
tree_html += f'- {item.name}
'
content_html += f'{item.name}
'
else:
if not item.name.endswith(".tgz") and not item.name.endswith(".gz"):
tree_html += f'- {item}
'
# if it's permission error, just skip reading the file or folder
except PermissionError as e:
continue
if is_top_level:
tree_html += '
'
return tree_html, content_html
# main
def main(input_dir, output_dir):
reportlog_path = os.path.join(output_dir, 'report.log')
with open(reportlog_path, 'r') as file:
log_contents = file.read()
sysinfo_path = os.path.join(output_dir, 'plugins/system_info')
with open(sysinfo_path, 'r') as file:
sysinfo_contents = file.read()
# pre-set html file path
html_file = os.path.abspath(os.path.join(output_dir, 'index.html'))
sys_section = sysinfo_contents.strip().split("\n\n")
html_content = html_css() + html_info(sys_section) + html_result(log_contents, output_dir)
html_content = html_content.format()
html_content += html_collect() + html_script()
# write the HTML content to file
with open(html_file, "w") as file:
file.write(html_content)