From f5c1d79424592b6f0e928e0f4f7c28866768b4bb Mon Sep 17 00:00:00 2001 From: Boaz Haim Date: Sun, 10 Nov 2024 21:40:52 +0200 Subject: [PATCH 1/9] Analyzing telemtry - for now --- .../src/loganalyze/log_analyzer.py | 16 +++- .../ibdiagnet2_port_counters_analyzer.py | 93 +++++++++++++++++++ .../ibdiagnet2_port_counters_log_regex.py | 20 ++-- 3 files changed, 120 insertions(+), 9 deletions(-) create mode 100644 plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py index add4a7be..b6da60b1 100755 --- a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py @@ -44,6 +44,7 @@ from loganalyze.log_analyzers.console_log_analyzer import ConsoleLogAnalyzer from loganalyze.log_analyzers.rest_api_log_analyzer import RestApiAnalyzer from loganalyze.log_analyzers.link_flapping_analyzer import LinkFlappingAnalyzer +from loganalyze.log_analyzers.ibdiagnet2_port_counters_analyzer import Ibdiagnet2PortCountersAnalyzer from loganalyze.pdf_creator import PDFCreator from loganalyze.utils.common import delete_files_by_types @@ -252,7 +253,7 @@ def create_analyzer(parsed_args, full_extracted_logs_list, in the full report. Returns the created analyzer """ - if log_name in full_extracted_logs_list: + if any(os.path.basename(log) == log_name for log in full_extracted_logs_list): log_csvs = get_files_in_dest_by_type(parsed_args.destination, log_name, parsed_args.extract_level) @@ -305,7 +306,7 @@ def create_analyzer(parsed_args, full_extracted_logs_list, log.LOGGER.debug("Starting analyzing the data") partial_create_analyzer = partial(create_analyzer, parsed_args=args, - full_extracted_logs_list=full_logs_list, + full_extracted_logs_list=logs_to_work_with, ufm_top_analyzer_obj=ufm_top_analyzer) # Creating the analyzer for each log @@ -328,6 +329,12 @@ def create_analyzer(parsed_args, full_extracted_logs_list, rest_api_log_analyzer = partial_create_analyzer(log_name="rest_api.log", analyzer_clc=RestApiAnalyzer) + + ibdianget_2_ports_primary_analyzer = partial_create_analyzer(log_name="ufm_logs_ibdiagnet2_port_counters.log", + analyzer_clc=Ibdiagnet2PortCountersAnalyzer) + + ibdianget_2_ports_secondary_analyzer = partial_create_analyzer(log_name="ufm_logs_ibdiagnet2_port_counters.log", + analyzer_clc=Ibdiagnet2PortCountersAnalyzer) second_telemetry_samples = get_files_in_dest_by_type(args.destination, "secondary_", 1000, @@ -388,6 +395,11 @@ def create_analyzer(parsed_args, full_extracted_logs_list, for image, title in images_and_title_to_present: log.LOGGER.info(f"{title}: {image}") log.LOGGER.info(f"Summary PDF was created! you can open here at {pdf_path}") + + if args.interactive: + import IPython + IPython.embed() + # Clean some unended files created during run files_types_to_delete = set() files_types_to_delete.add("png") #png images created for PDF report diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py new file mode 100644 index 00000000..8947057a --- /dev/null +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py @@ -0,0 +1,93 @@ +# +# Copyright © 2013-2024 NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED. +# +# This software product is a proprietary product of Nvidia Corporation and its affiliates +# (the "Company") and all right, title, and interest in and to the software +# product, including all associated intellectual property rights, are and +# shall remain exclusively with the Company. +# +# This software product is governed by the End User License Agreement +# provided with the software product. +# + +from typing import List + +import pandas as pd +from loganalyze.log_analyzers.base_analyzer import BaseAnalyzer +import loganalyze.logger as log + +class Ibdiagnet2PortCountersAnalyzer(BaseAnalyzer): + def __init__(self, logs_csvs: List[str], hours: int, dest_image_path: str, sort_timestamp=False): + super().__init__(logs_csvs, hours, dest_image_path, sort_timestamp) + + # This will make all the extra colum are int + # Convert the 'extra' columns to integers if possible + extra_columns = ['extra1', 'extra2', 'extra3', 'extra4', 'extra5'] + + for col in extra_columns: + self._log_data_sorted[col] = pd.to_numeric( + self._log_data_sorted[col], + errors='coerce' + ).astype('Int64') + + # self._log_data_sorted['extra'] = ( + # self._log_data_sorted['extra'] + # .fillna(0) # Replace NaN with 0 + # .astype(int) # Convert to integer + # ) + + def get_collectx_versions(self): + unique_collectx_versions = self._log_data_sorted[self._log_data_sorted['type'] == 'collectx_version']['data'].unique() + return unique_collectx_versions + + + def get_number_of_switches_and_ports(self): + """ + Generate summary statistics for 'total_devices_ports' data. + This function calculates the average, maximum, minimum, and non-zero counts + for switches, CAs, routers, and ports. + """ + # Step 1: Filter data for 'total_devices_ports' + filtered_data = self._log_data_sorted[self._log_data_sorted['type'] == 'total_devices_ports'] + + # Step 2: Create a combined column for 'extra1', 'extra3', and 'extra5' + combined_columns = ['extra1', 'extra3', 'extra5'] + filtered_data['extra135'] = pd.to_numeric( + filtered_data[combined_columns].stack(), errors='coerce' + ).groupby(level=0).sum(min_count=1) + + # Define columns of interest and their mapping to meaningful names + columns_of_interest = ['data', 'extra2', 'extra4', 'extra135'] + column_mapping = { + 'data': 'Number of Switches', + 'extra2': 'CAs', + 'extra4': 'Routers', + 'extra135': 'Ports' + } + + # Step 3: Initialize a list to store the summary statistics + summary_stats = [] + + # Step 4: Calculate statistics for each column + for col in columns_of_interest: + numeric_col = pd.to_numeric(filtered_data[col], errors='coerce') + non_zero_col = numeric_col[numeric_col != 0] + + # Determine stats, defaulting to 0 if the column has no non-zero values + avg = int(round(non_zero_col.mean())) if not non_zero_col.empty else 0 + max_val = int(non_zero_col.max()) if not non_zero_col.empty else 0 + min_val = int(non_zero_col.min()) if not non_zero_col.empty else 0 + count = int(non_zero_col.count()) + + summary_stats.append({ + 'Category': column_mapping.get(col, col), + 'Average': avg, + 'Maximum': max_val, + 'Minimum': min_val, + 'Total Rows (Non-Zero)': count + }) + + # Step 5: Convert the summary stats list into a DataFrame + summary_df = pd.DataFrame(summary_stats) + + return summary_df diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_parsing/ibdiagnet2_port_counters_log_regex.py b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_parsing/ibdiagnet2_port_counters_log_regex.py index 5f994f2f..12d4cd09 100644 --- a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_parsing/ibdiagnet2_port_counters_log_regex.py +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_parsing/ibdiagnet2_port_counters_log_regex.py @@ -20,28 +20,34 @@ TIMEOUT_DUMP_CORE_REGEX = re.compile(r"^timeout: the monitored command dumped core$") -TOTAL_SWITCH_PORTS_REGEX = re.compile(r"^.*Total switches\/ports \[(\d+)\/(\d+)\]\,.*$") +TOTAL_SWITCH_PORTS_REGEX = re.compile(r"^.*Total switches\/ports \[(\d+)\/(\d+)\]\, CAs\/ports \[(\d+)\/(\d+)\]\, Routers\/ports \[(\d+)\/(\d+)\]\s*$") COLLECTX_VERSION_REGEX = re.compile(r"^\[ExportAPI\] Collectx version ([\d\.]+)$") def iteration_time(match: Match): iteration_time_sec = match.group(1) timestamp = match.group(2) - return ("iteration_time", timestamp, iteration_time_sec, None) + return ("iteration_time", timestamp, iteration_time_sec, None, None, None, None, None) def timeout_dump_core(_: Match): - return ("timeout_dump_core", None, None, None) + return ("timeout_dump_core", None, None, None, None, None, None, None) def total_switch_ports(match: Match): total_switches = match.group(1) - total_ports = match.group(2) - return ("total_switch_ports", None, total_switches, total_ports) + total_switch_ports = match.group(2) + total_cas = match.group(3) + total_cas_ports = match.group(4) + total_routers = match.group(5) + total_routers_ports = match.group(6) + return ("total_devices_ports", None, total_switches, total_switch_ports,\ + total_cas, total_cas_ports,\ + total_routers, total_routers_ports) def collectx_version(match:Match): collectx_version_str = match.group(1) - return ("collectx_version", None, collectx_version_str, None) + return ("collectx_version", None, collectx_version_str, None, None, None, None, None) -ibdiagnet2_headers = ("type", "timestamp", "data", "extra") +ibdiagnet2_headers = ("type", "timestamp", "data", "extra1", "extra2", "extra3", "extra4", "extra5") ibdiagnet2_primary_log_regex_cls = \ RegexAndHandlers("ufm_logs_ibdiagnet2_port_counters.log", ibdiagnet2_headers) From 2bd0a85e32f95872870bc30f16ee9c4cfd6834d3 Mon Sep 17 00:00:00 2001 From: Boaz Haim Date: Mon, 11 Nov 2024 17:13:22 +0200 Subject: [PATCH 2/9] Working, needs refactor + doc string --- .../src/loganalyze/log_analyzer.py | 23 +++- .../loganalyze/log_analyzers/base_analyzer.py | 7 +- .../ibdiagnet2_port_counters_analyzer.py | 109 ++++++++++++++---- 3 files changed, 111 insertions(+), 28 deletions(-) diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py index b6da60b1..920de666 100755 --- a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py @@ -333,7 +333,7 @@ def create_analyzer(parsed_args, full_extracted_logs_list, ibdianget_2_ports_primary_analyzer = partial_create_analyzer(log_name="ufm_logs_ibdiagnet2_port_counters.log", analyzer_clc=Ibdiagnet2PortCountersAnalyzer) - ibdianget_2_ports_secondary_analyzer = partial_create_analyzer(log_name="ufm_logs_ibdiagnet2_port_counters.log", + ibdianget_2_ports_secondary_analyzer = partial_create_analyzer(log_name="secondary_telemetry_ibdiagnet2_port_counters.log", analyzer_clc=Ibdiagnet2PortCountersAnalyzer) second_telemetry_samples = get_files_in_dest_by_type(args.destination, "secondary_", @@ -387,6 +387,27 @@ def create_analyzer(parsed_args, full_extracted_logs_list, text_to_show_in_pdf += os.linesep + os.linesep + "More than 5 events burst over a minute:" \ + os.linesep + critical_events_text + # Adding telemetry stats to the PDF + for cur_telemetry in [ibdianget_2_ports_primary_analyzer, ibdianget_2_ports_secondary_analyzer]: + txt = f"{cur_telemetry.telemetry_type} info: {os.linesep}" + txt += f"Found the following collectx version(s):{os.linesep}" + for collectx_version in cur_telemetry.get_collectx_versions(): + txt += f"{collectx_version}, " + txt += os.linesep + txt += f"Found {cur_telemetry.get_number_of_core_dumps()} core dumps{os.linesep}" + txt += str(cur_telemetry.get_number_of_switches_and_ports()) + iteration_stats = cur_telemetry.get_last_iterations_time_stats() + if iteration_stats is None: + cur_telemetry.analyze_iteration_time() + iteration_stats = cur_telemetry.get_last_iterations_time_stats() + txt += f"Iteration time stats:{os.linesep}" + txt += str(iteration_stats) + text_to_show_in_pdf += os.linesep + os.linesep + txt + + #Also print the stats to the screen + print(f"stats for {cur_telemetry.telemetry_type}:") + print(cur_telemetry.get_last_iterations_time_stats()) + print(cur_telemetry.get_number_of_switches_and_ports()) # PDF creator gets all the images and to add to the report pdf = PDFCreator(pdf_path, pdf_header, png_images, text_to_show_in_pdf) pdf.created_pdf() diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/base_analyzer.py b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/base_analyzer.py index 1d6db24f..fde5ed7b 100644 --- a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/base_analyzer.py +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/base_analyzer.py @@ -47,7 +47,7 @@ def __init__(self, dest_image_path): self._funcs_for_analysis = set() def _save_data_based_on_timestamp( - self, data_to_plot, x_label, y_label, title + self, data_to_plot, x_label, y_label, title, large_sample=False ): with plt.ion(): log.LOGGER.debug(f"saving {title}") @@ -61,7 +61,10 @@ def _save_data_based_on_timestamp( # Set the locator to show ticks every hour and the formatter to # include both date and time ax = plt.gca() - ax.xaxis.set_major_locator(mdates.HourLocator()) + if large_sample: + ax.xaxis.set_major_locator(mdates.HourLocator(interval=24)) # Show labels every hour + else: + ax.xaxis.set_major_locator(mdates.HourLocator()) ax.xaxis.set_minor_locator( mdates.MinuteLocator(interval=15) ) # Add minor ticks every 15 minutes diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py index 8947057a..c41fe807 100644 --- a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py @@ -11,52 +11,50 @@ # from typing import List - +import warnings import pandas as pd from loganalyze.log_analyzers.base_analyzer import BaseAnalyzer -import loganalyze.logger as log +from datetime import timedelta + class Ibdiagnet2PortCountersAnalyzer(BaseAnalyzer): def __init__(self, logs_csvs: List[str], hours: int, dest_image_path: str, sort_timestamp=False): super().__init__(logs_csvs, hours, dest_image_path, sort_timestamp) - - # This will make all the extra colum are int - # Convert the 'extra' columns to integers if possible + self._iteration_time_data = None + self._iteration_time_stats = None + # This will make sure all the extra columns are int extra_columns = ['extra1', 'extra2', 'extra3', 'extra4', 'extra5'] - for col in extra_columns: self._log_data_sorted[col] = pd.to_numeric( self._log_data_sorted[col], errors='coerce' - ).astype('Int64') - - # self._log_data_sorted['extra'] = ( - # self._log_data_sorted['extra'] - # .fillna(0) # Replace NaN with 0 - # .astype(int) # Convert to integer - # ) + ).astype('Int64') + self._funcs_for_analysis = {self.plot_iteration_time_over_time} + # Based on the log path, decided if this is primary or secondary + if "ufm_logs" in logs_csvs[0]: + self.telemetry_type = "primary" + elif "secondary_telemetry" in logs_csvs[0]: + self.telemetry_type = "secondary" + else: + self.telemetry_type = "Unknown_telemetry_type" def get_collectx_versions(self): unique_collectx_versions = self._log_data_sorted[self._log_data_sorted['type'] == 'collectx_version']['data'].unique() return unique_collectx_versions - def get_number_of_switches_and_ports(self): """ Generate summary statistics for 'total_devices_ports' data. - This function calculates the average, maximum, minimum, and non-zero counts + This function calculates the average, maximum, minimum for switches, CAs, routers, and ports. """ - # Step 1: Filter data for 'total_devices_ports' filtered_data = self._log_data_sorted[self._log_data_sorted['type'] == 'total_devices_ports'] - # Step 2: Create a combined column for 'extra1', 'extra3', and 'extra5' - combined_columns = ['extra1', 'extra3', 'extra5'] + ports_numbers_columns = ['extra1', 'extra3', 'extra5'] filtered_data['extra135'] = pd.to_numeric( - filtered_data[combined_columns].stack(), errors='coerce' + filtered_data[ports_numbers_columns].stack(), errors='coerce' ).groupby(level=0).sum(min_count=1) - # Define columns of interest and their mapping to meaningful names columns_of_interest = ['data', 'extra2', 'extra4', 'extra135'] column_mapping = { 'data': 'Number of Switches', @@ -65,16 +63,13 @@ def get_number_of_switches_and_ports(self): 'extra135': 'Ports' } - # Step 3: Initialize a list to store the summary statistics summary_stats = [] - # Step 4: Calculate statistics for each column for col in columns_of_interest: numeric_col = pd.to_numeric(filtered_data[col], errors='coerce') non_zero_col = numeric_col[numeric_col != 0] - # Determine stats, defaulting to 0 if the column has no non-zero values - avg = int(round(non_zero_col.mean())) if not non_zero_col.empty else 0 + avg = round(non_zero_col.mean()) if not non_zero_col.empty else 0 max_val = int(non_zero_col.max()) if not non_zero_col.empty else 0 min_val = int(non_zero_col.min()) if not non_zero_col.empty else 0 count = int(non_zero_col.count()) @@ -87,7 +82,71 @@ def get_number_of_switches_and_ports(self): 'Total Rows (Non-Zero)': count }) - # Step 5: Convert the summary stats list into a DataFrame summary_df = pd.DataFrame(summary_stats) return summary_df + + def analyze_iteration_time(self, threshold=0.15): + """ + Analyze rows where 'type' is 'iteration_time'. + Keep only 'type', 'timestamp', and 'data' columns. + Calculate statistics for the 'data' column, including timestamps for max and min. + Also, find gaps of at least 2 minutes with no data and allow filtering by a threshold. + + Parameters: + - threshold (float): Minimum value to consider for analysis. Default is 0.5 seconds. + """ + filtered_data = self._log_data_sorted[self._log_data_sorted['type'] == 'iteration_time'] + filtered_data = filtered_data[['type', 'timestamp', 'data']] + filtered_data['data'] = pd.to_numeric(filtered_data['data'], errors='coerce') + + filtered_data = filtered_data[filtered_data['data'] >= threshold] + filtered_data['timestamp'] = pd.to_datetime(filtered_data['timestamp'], errors='coerce') + filtered_data = filtered_data.dropna(subset=['timestamp']) + + if not filtered_data['data'].empty: + average = filtered_data['data'].mean() + max_value = filtered_data['data'].max() + min_value = filtered_data['data'].min() + + max_timestamp = filtered_data.loc[filtered_data['data'] == max_value, 'timestamp'].iloc[0] + min_timestamp = filtered_data.loc[filtered_data['data'] == min_value, 'timestamp'].iloc[0] + else: + average = max_value = min_value = 0.0 + max_timestamp = min_timestamp = None + + stats = { + 'Average': average, + 'Maximum': max_value, + 'Max Timestamp': max_timestamp, + 'Minimum': min_value, + 'Min Timestamp': min_timestamp, + 'Total Rows': filtered_data['data'].count() + } + stats_df = pd.DataFrame([stats]) + self._iteration_time_data = filtered_data + self._iteration_time_stats = stats_df + return stats_df + + def get_last_iterations_time_stats(self): + return self._iteration_time_stats + + def plot_iteration_time_over_time(self): + if not self._iteration_time_data: + self.analyze_iteration_time() + + self._iteration_time_data.set_index('timestamp', inplace=True) + + # Plot the data using the existing method + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", ".*Locator attempting to generate.*") + self._save_data_based_on_timestamp( + data_to_plot=self._iteration_time_data['data'], + x_label='Timestamp', + y_label='Iteration Time (s)', + title=f'{self.telemetry_type} Iteration Time', + large_sample=True) + + def get_number_of_core_dumps(self): + core_dumps = self._log_data_sorted[self._log_data_sorted['type'] == 'timeout_dump_core'] + return len(core_dumps) From deecc02010898d0eee1a6381a0276e2b142c945f Mon Sep 17 00:00:00 2001 From: Boaz Haim Date: Mon, 11 Nov 2024 20:24:27 +0200 Subject: [PATCH 3/9] Working for telemetry --- .../src/loganalyze/log_analyzer.py | 20 +------- .../loganalyze/log_analyzers/base_analyzer.py | 5 +- .../ibdiagnet2_port_counters_analyzer.py | 46 ++++++++++++++----- 3 files changed, 40 insertions(+), 31 deletions(-) diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py index 920de666..1332d8cb 100755 --- a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py @@ -389,25 +389,7 @@ def create_analyzer(parsed_args, full_extracted_logs_list, # Adding telemetry stats to the PDF for cur_telemetry in [ibdianget_2_ports_primary_analyzer, ibdianget_2_ports_secondary_analyzer]: - txt = f"{cur_telemetry.telemetry_type} info: {os.linesep}" - txt += f"Found the following collectx version(s):{os.linesep}" - for collectx_version in cur_telemetry.get_collectx_versions(): - txt += f"{collectx_version}, " - txt += os.linesep - txt += f"Found {cur_telemetry.get_number_of_core_dumps()} core dumps{os.linesep}" - txt += str(cur_telemetry.get_number_of_switches_and_ports()) - iteration_stats = cur_telemetry.get_last_iterations_time_stats() - if iteration_stats is None: - cur_telemetry.analyze_iteration_time() - iteration_stats = cur_telemetry.get_last_iterations_time_stats() - txt += f"Iteration time stats:{os.linesep}" - txt += str(iteration_stats) - text_to_show_in_pdf += os.linesep + os.linesep + txt - - #Also print the stats to the screen - print(f"stats for {cur_telemetry.telemetry_type}:") - print(cur_telemetry.get_last_iterations_time_stats()) - print(cur_telemetry.get_number_of_switches_and_ports()) + text_to_show_in_pdf += cur_telemetry.text_to_show_in_pdf # PDF creator gets all the images and to add to the report pdf = PDFCreator(pdf_path, pdf_header, png_images, text_to_show_in_pdf) pdf.created_pdf() diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/base_analyzer.py b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/base_analyzer.py index fde5ed7b..3710223c 100644 --- a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/base_analyzer.py +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/base_analyzer.py @@ -12,6 +12,7 @@ # pylint: disable=missing-function-docstring # pylint: disable=missing-module-docstring +import logging import os import csv import shutil @@ -21,15 +22,17 @@ import pandas as pd import numpy as np import matplotlib.pyplot as plt +import matplotlib import matplotlib.dates as mdates from loganalyze.log_analyzers.constants import DataConstants import loganalyze.logger as log +logging.getLogger('matplotlib').setLevel(logging.ERROR) # This makes sure the user does not see the warning from plotting +matplotlib.use('Agg') # This allows to run the tool on servers without graphic card/headless pd.set_option("display.max_colwidth", None) warnings.filterwarnings("ignore") - class BaseImageCreator: # Setting the graph time interval to 1 hour # This is out side of the constructor since diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py index c41fe807..2155a953 100644 --- a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py @@ -10,11 +10,11 @@ # provided with the software product. # +import os from typing import List import warnings import pandas as pd from loganalyze.log_analyzers.base_analyzer import BaseAnalyzer -from datetime import timedelta class Ibdiagnet2PortCountersAnalyzer(BaseAnalyzer): @@ -22,6 +22,7 @@ def __init__(self, logs_csvs: List[str], hours: int, dest_image_path: str, sort_ super().__init__(logs_csvs, hours, dest_image_path, sort_timestamp) self._iteration_time_data = None self._iteration_time_stats = None + self.text_to_show_in_pdf = "" # This will make sure all the extra columns are int extra_columns = ['extra1', 'extra2', 'extra3', 'extra4', 'extra5'] for col in extra_columns: @@ -132,21 +133,44 @@ def get_last_iterations_time_stats(self): return self._iteration_time_stats def plot_iteration_time_over_time(self): - if not self._iteration_time_data: + if self._iteration_time_data is None: self.analyze_iteration_time() - self._iteration_time_data.set_index('timestamp', inplace=True) + self._iteration_time_data.set_index('timestamp', inplace=True) # Plot the data using the existing method - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", ".*Locator attempting to generate.*") - self._save_data_based_on_timestamp( - data_to_plot=self._iteration_time_data['data'], - x_label='Timestamp', - y_label='Iteration Time (s)', - title=f'{self.telemetry_type} Iteration Time', - large_sample=True) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", ".*Locator attempting to generate.*") + self._save_data_based_on_timestamp( + data_to_plot=self._iteration_time_data['data'], + x_label='Timestamp', + y_label='Iteration Time (s)', + title=f'{self.telemetry_type} Iteration Time', + large_sample=True) def get_number_of_core_dumps(self): core_dumps = self._log_data_sorted[self._log_data_sorted['type'] == 'timeout_dump_core'] return len(core_dumps) + + def full_analysis(self): + txt_for_pdf = os.linesep + os.linesep + txt_for_pdf += f"{self.telemetry_type} info: {os.linesep}" + txt_for_pdf += f"Found the following collectx version(s):{os.linesep}" + for collectx_version in self.get_collectx_versions(): + txt_for_pdf += f"{collectx_version}, " + txt_for_pdf += os.linesep + txt_for_pdf += f"Found {self.get_number_of_core_dumps()} core dumps{os.linesep}" + txt_for_pdf += str(self.get_number_of_switches_and_ports()) + iteration_stats = self.get_last_iterations_time_stats() + if iteration_stats is None: + self.analyze_iteration_time() + iteration_stats = self.get_last_iterations_time_stats() + txt_for_pdf += f"Iteration time stats:{os.linesep}" + txt_for_pdf += str(iteration_stats) + self.text_to_show_in_pdf = txt_for_pdf + print(f"stats for {self.telemetry_type}:") + print(self.get_last_iterations_time_stats()) + print(self.get_number_of_switches_and_ports()) + print(f"Collectx versions {self.get_collectx_versions()}") + + return super().full_analysis() \ No newline at end of file From e90afe69b8dd5e92b914ee5f303beba6b547ff4d Mon Sep 17 00:00:00 2001 From: Boaz Haim Date: Mon, 11 Nov 2024 21:14:21 +0200 Subject: [PATCH 4/9] pylint --- .../src/loganalyze/.pylintrc | 5 +++- .../src/loganalyze/log_analyzer.py | 25 +++++++++------- .../loganalyze/log_analyzers/base_analyzer.py | 7 +++-- .../ibdiagnet2_port_counters_analyzer.py | 30 ++++++++++++------- .../ibdiagnet2_port_counters_log_regex.py | 8 +++-- 5 files changed, 47 insertions(+), 28 deletions(-) diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/.pylintrc b/plugins/ufm_log_analyzer_plugin/src/loganalyze/.pylintrc index 79193d66..48acd16e 100644 --- a/plugins/ufm_log_analyzer_plugin/src/loganalyze/.pylintrc +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/.pylintrc @@ -4,6 +4,9 @@ disable=missing-function-docstring, missing-module-docstring, too-few-public-methods, logging-fstring-interpolation, + too-many-positional-arguments # Too many false postivies + [DESIGN] -max-locals=20 \ No newline at end of file +max-locals=20 +max-args=8 \ No newline at end of file diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py index 1332d8cb..2d3bd4b2 100755 --- a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py @@ -44,7 +44,8 @@ from loganalyze.log_analyzers.console_log_analyzer import ConsoleLogAnalyzer from loganalyze.log_analyzers.rest_api_log_analyzer import RestApiAnalyzer from loganalyze.log_analyzers.link_flapping_analyzer import LinkFlappingAnalyzer -from loganalyze.log_analyzers.ibdiagnet2_port_counters_analyzer import Ibdiagnet2PortCountersAnalyzer +from loganalyze.log_analyzers.ibdiagnet2_port_counters_analyzer \ + import Ibdiagnet2PortCountersAnalyzer from loganalyze.pdf_creator import PDFCreator from loganalyze.utils.common import delete_files_by_types @@ -329,12 +330,15 @@ def create_analyzer(parsed_args, full_extracted_logs_list, rest_api_log_analyzer = partial_create_analyzer(log_name="rest_api.log", analyzer_clc=RestApiAnalyzer) - - ibdianget_2_ports_primary_analyzer = partial_create_analyzer(log_name="ufm_logs_ibdiagnet2_port_counters.log", - analyzer_clc=Ibdiagnet2PortCountersAnalyzer) - - ibdianget_2_ports_secondary_analyzer = partial_create_analyzer(log_name="secondary_telemetry_ibdiagnet2_port_counters.log", - analyzer_clc=Ibdiagnet2PortCountersAnalyzer) + + ibdianget_2_ports_primary_analyzer = partial_create_analyzer( + log_name="ufm_logs_ibdiagnet2_port_counters.log", + analyzer_clc=Ibdiagnet2PortCountersAnalyzer) + + ibdianget_2_ports_secondary_analyzer = partial_create_analyzer( + log_name="secondary_telemetry_ibdiagnet2_port_counters.log", + analyzer_clc=Ibdiagnet2PortCountersAnalyzer) + second_telemetry_samples = get_files_in_dest_by_type(args.destination, "secondary_", 1000, @@ -388,7 +392,8 @@ def create_analyzer(parsed_args, full_extracted_logs_list, + os.linesep + critical_events_text # Adding telemetry stats to the PDF - for cur_telemetry in [ibdianget_2_ports_primary_analyzer, ibdianget_2_ports_secondary_analyzer]: + for cur_telemetry in \ + [ibdianget_2_ports_primary_analyzer, ibdianget_2_ports_secondary_analyzer]: text_to_show_in_pdf += cur_telemetry.text_to_show_in_pdf # PDF creator gets all the images and to add to the report pdf = PDFCreator(pdf_path, pdf_header, png_images, text_to_show_in_pdf) @@ -398,11 +403,11 @@ def create_analyzer(parsed_args, full_extracted_logs_list, for image, title in images_and_title_to_present: log.LOGGER.info(f"{title}: {image}") log.LOGGER.info(f"Summary PDF was created! you can open here at {pdf_path}") - + if args.interactive: import IPython IPython.embed() - + # Clean some unended files created during run files_types_to_delete = set() files_types_to_delete.add("png") #png images created for PDF report diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/base_analyzer.py b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/base_analyzer.py index 3710223c..e5b2da63 100644 --- a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/base_analyzer.py +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/base_analyzer.py @@ -27,7 +27,8 @@ from loganalyze.log_analyzers.constants import DataConstants import loganalyze.logger as log -logging.getLogger('matplotlib').setLevel(logging.ERROR) # This makes sure the user does not see the warning from plotting +# This makes sure the user does not see the warning from plotting +logging.getLogger('matplotlib').setLevel(logging.ERROR) matplotlib.use('Agg') # This allows to run the tool on servers without graphic card/headless pd.set_option("display.max_colwidth", None) @@ -65,7 +66,7 @@ def _save_data_based_on_timestamp( # include both date and time ax = plt.gca() if large_sample: - ax.xaxis.set_major_locator(mdates.HourLocator(interval=24)) # Show labels every hour + ax.xaxis.set_major_locator(mdates.HourLocator(interval=24)) else: ax.xaxis.set_major_locator(mdates.HourLocator()) ax.xaxis.set_minor_locator( @@ -100,7 +101,7 @@ def _save_data_based_on_timestamp( self._images_created.extend(images_list_with_title) plt.close() - def _save_pivot_data_in_bars( # pylint: disable=too-many-arguments + def _save_pivot_data_in_bars( self, pivoted_data, x_label, y_label, title, legend_title ): if pivoted_data.empty: diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py index 2155a953..be22b2b9 100644 --- a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py @@ -18,7 +18,11 @@ class Ibdiagnet2PortCountersAnalyzer(BaseAnalyzer): - def __init__(self, logs_csvs: List[str], hours: int, dest_image_path: str, sort_timestamp=False): + def __init__(self, + logs_csvs: List[str], + hours: int, + dest_image_path: str, + sort_timestamp=False): super().__init__(logs_csvs, hours, dest_image_path, sort_timestamp) self._iteration_time_data = None self._iteration_time_stats = None @@ -40,8 +44,9 @@ def __init__(self, logs_csvs: List[str], hours: int, dest_image_path: str, sort_ self.telemetry_type = "Unknown_telemetry_type" def get_collectx_versions(self): - unique_collectx_versions = self._log_data_sorted[self._log_data_sorted['type'] == 'collectx_version']['data'].unique() - return unique_collectx_versions + unique_collectx_versions = self._log_data_sorted[\ + self._log_data_sorted['type'] == 'collectx_version']['data'].unique() + return unique_collectx_versions def get_number_of_switches_and_ports(self): """ @@ -49,7 +54,8 @@ def get_number_of_switches_and_ports(self): This function calculates the average, maximum, minimum for switches, CAs, routers, and ports. """ - filtered_data = self._log_data_sorted[self._log_data_sorted['type'] == 'total_devices_ports'] + filtered_data = self._log_data_sorted[\ + self._log_data_sorted['type'] == 'total_devices_ports'] ports_numbers_columns = ['extra1', 'extra3', 'extra5'] filtered_data['extra135'] = pd.to_numeric( @@ -84,9 +90,9 @@ def get_number_of_switches_and_ports(self): }) summary_df = pd.DataFrame(summary_stats) - + return summary_df - + def analyze_iteration_time(self, threshold=0.15): """ Analyze rows where 'type' is 'iteration_time'. @@ -100,7 +106,7 @@ def analyze_iteration_time(self, threshold=0.15): filtered_data = self._log_data_sorted[self._log_data_sorted['type'] == 'iteration_time'] filtered_data = filtered_data[['type', 'timestamp', 'data']] filtered_data['data'] = pd.to_numeric(filtered_data['data'], errors='coerce') - + filtered_data = filtered_data[filtered_data['data'] >= threshold] filtered_data['timestamp'] = pd.to_datetime(filtered_data['timestamp'], errors='coerce') filtered_data = filtered_data.dropna(subset=['timestamp']) @@ -110,8 +116,10 @@ def analyze_iteration_time(self, threshold=0.15): max_value = filtered_data['data'].max() min_value = filtered_data['data'].min() - max_timestamp = filtered_data.loc[filtered_data['data'] == max_value, 'timestamp'].iloc[0] - min_timestamp = filtered_data.loc[filtered_data['data'] == min_value, 'timestamp'].iloc[0] + max_timestamp = filtered_data.loc[filtered_data['data'] \ + == max_value, 'timestamp'].iloc[0] + min_timestamp = filtered_data.loc[filtered_data['data'] \ + == min_value, 'timestamp'].iloc[0] else: average = max_value = min_value = 0.0 max_timestamp = min_timestamp = None @@ -127,7 +135,7 @@ def analyze_iteration_time(self, threshold=0.15): stats_df = pd.DataFrame([stats]) self._iteration_time_data = filtered_data self._iteration_time_stats = stats_df - return stats_df + return stats_df def get_last_iterations_time_stats(self): return self._iteration_time_stats @@ -173,4 +181,4 @@ def full_analysis(self): print(self.get_number_of_switches_and_ports()) print(f"Collectx versions {self.get_collectx_versions()}") - return super().full_analysis() \ No newline at end of file + return super().full_analysis() diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_parsing/ibdiagnet2_port_counters_log_regex.py b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_parsing/ibdiagnet2_port_counters_log_regex.py index 12d4cd09..97f30d5e 100644 --- a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_parsing/ibdiagnet2_port_counters_log_regex.py +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_parsing/ibdiagnet2_port_counters_log_regex.py @@ -20,7 +20,9 @@ TIMEOUT_DUMP_CORE_REGEX = re.compile(r"^timeout: the monitored command dumped core$") -TOTAL_SWITCH_PORTS_REGEX = re.compile(r"^.*Total switches\/ports \[(\d+)\/(\d+)\]\, CAs\/ports \[(\d+)\/(\d+)\]\, Routers\/ports \[(\d+)\/(\d+)\]\s*$") +TOTAL_SWITCH_PORTS_REGEX = re.compile(r"^.*Total switches\/ports \[(\d+)\/(\d+)\]\, " + r"CAs\/ports \[(\d+)\/(\d+)\]\, Routers\/ports " + r"\[(\d+)\/(\d+)\]\s*$") COLLECTX_VERSION_REGEX = re.compile(r"^\[ExportAPI\] Collectx version ([\d\.]+)$") @@ -34,12 +36,12 @@ def timeout_dump_core(_: Match): def total_switch_ports(match: Match): total_switches = match.group(1) - total_switch_ports = match.group(2) + total_switch_ports_count = match.group(2) total_cas = match.group(3) total_cas_ports = match.group(4) total_routers = match.group(5) total_routers_ports = match.group(6) - return ("total_devices_ports", None, total_switches, total_switch_ports,\ + return ("total_devices_ports", None, total_switches, total_switch_ports_count,\ total_cas, total_cas_ports,\ total_routers, total_routers_ports) From 95db1bb3cc2f1577ac6890ac3e87822246a4464c Mon Sep 17 00:00:00 2001 From: Boaz Haim Date: Mon, 11 Nov 2024 21:18:09 +0200 Subject: [PATCH 5/9] pylint2 --- plugins/ufm_log_analyzer_plugin/src/loganalyze/.pylintrc | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/.pylintrc b/plugins/ufm_log_analyzer_plugin/src/loganalyze/.pylintrc index 48acd16e..1f8ebad5 100644 --- a/plugins/ufm_log_analyzer_plugin/src/loganalyze/.pylintrc +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/.pylintrc @@ -4,7 +4,6 @@ disable=missing-function-docstring, missing-module-docstring, too-few-public-methods, logging-fstring-interpolation, - too-many-positional-arguments # Too many false postivies [DESIGN] From dd43c977fce3feec4a97781de5665030f04910c6 Mon Sep 17 00:00:00 2001 From: Boaz Haim Date: Sun, 17 Nov 2024 13:59:39 +0200 Subject: [PATCH 6/9] fixed the output to pdf --- .../src/loganalyze/log_analyzer.py | 42 +++---- .../ibdiagnet2_port_counters_analyzer.py | 47 ++++---- .../src/loganalyze/pdf_creator.py | 104 +++++++++++++----- .../src/loganalyze/requirements.txt | 3 +- 4 files changed, 121 insertions(+), 75 deletions(-) diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py index 2d3bd4b2..2abd9269 100755 --- a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py @@ -369,35 +369,29 @@ def create_analyzer(parsed_args, full_extracted_logs_list, ) used_ufm_version = console_log_analyzer.ufm_versions - text_to_show_in_pdf = f"Used ufm version in console log {used_ufm_version}" - fabric_info = "fabric info:" + os.linesep + str(ibdiagnet_analyzer.get_fabric_size()) \ - if ibdiagnet_analyzer else "No Fabric Info found" # pylint: disable=invalid-name - if links_flapping_analyzer: - link_flapping = links_flapping_analyzer.get_link_flapping_last_week() \ - if links_flapping_analyzer else "No link flapping info" - text_to_show_in_pdf += os.linesep + str(fabric_info) + os.linesep + \ - "Link Flapping:" + os.linesep + str(link_flapping) - - critical_events_burst = event_log_analyzer.get_critical_event_bursts() - critical_events_text = "The minute event_type event count" # pylint: disable=invalid-name - for critical_event in critical_events_burst: - timestamp = critical_event['timestamp'] - event_type = critical_event['event_type'] - event = critical_event['event'] - counter = critical_event['count'] - event_text = f"{timestamp} {event_type} {event} {counter}" - critical_events_text = critical_events_text + os.linesep + event_text - - text_to_show_in_pdf += os.linesep + os.linesep + "More than 5 events burst over a minute:" \ - + os.linesep + critical_events_text + text_to_show_in_pdf = f"Used ufm version in console log {used_ufm_version}{os.linesep}" + pdf = PDFCreator(pdf_path, pdf_header, png_images, text_to_show_in_pdf) # Adding telemetry stats to the PDF + dataframes_for_pdf = [] + fabric_info = ibdiagnet_analyzer.get_fabric_size() if ibdiagnet_analyzer else "No Fabric Info found" + dataframes_for_pdf.append(("Fabric info", fabric_info)) + if links_flapping_analyzer: + dataframes_for_pdf.append(("Link Flapping past week", links_flapping_analyzer.get_link_flapping_last_week())) + lists_to_add = [] + critical_events_headers = ["timestamp", "event_type", "event", "count"] + lists_to_add.append((event_log_analyzer.get_critical_event_bursts(), "More than 5 events burst over a minute", critical_events_headers)) + for cur_telemetry in \ [ibdianget_2_ports_primary_analyzer, ibdianget_2_ports_secondary_analyzer]: - text_to_show_in_pdf += cur_telemetry.text_to_show_in_pdf + dataframes_for_pdf.append((f"{cur_telemetry.telemetry_type} Telemetry iteration time", cur_telemetry.get_last_iterations_time_stats())) + dataframes_for_pdf.append((f"{cur_telemetry.telemetry_type} Telemetry iteration first and last timestamps", cur_telemetry.get_first_last_iteration_timestamp())) + dataframes_for_pdf.append((f"{cur_telemetry.telemetry_type} Telemetry fabric size", cur_telemetry.get_number_of_switches_and_ports())) + lists_to_add.append(([cur_telemetry.get_number_of_core_dumps()], f"{cur_telemetry.telemetry_type} number of core dumps found in the logs",["Amount"])) + + # PDF creator gets all the images and to add to the report - pdf = PDFCreator(pdf_path, pdf_header, png_images, text_to_show_in_pdf) - pdf.created_pdf() + pdf.create_pdf(dataframes_for_pdf, lists_to_add) # Generated a report that can be located in the destination log.LOGGER.info("Analysis is done, please see the following outputs:") for image, title in images_and_title_to_present: diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py index be22b2b9..5ee0de4d 100644 --- a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py @@ -43,6 +43,9 @@ def __init__(self, else: self.telemetry_type = "Unknown_telemetry_type" + self._first_timestamp_of_logs = None + self._last_timestamp_of_logs = None + def get_collectx_versions(self): unique_collectx_versions = self._log_data_sorted[\ self._log_data_sorted['type'] == 'collectx_version']['data'].unique() @@ -64,7 +67,7 @@ def get_number_of_switches_and_ports(self): columns_of_interest = ['data', 'extra2', 'extra4', 'extra135'] column_mapping = { - 'data': 'Number of Switches', + 'data': '# of Switches', 'extra2': 'CAs', 'extra4': 'Routers', 'extra135': 'Ports' @@ -110,6 +113,8 @@ def analyze_iteration_time(self, threshold=0.15): filtered_data = filtered_data[filtered_data['data'] >= threshold] filtered_data['timestamp'] = pd.to_datetime(filtered_data['timestamp'], errors='coerce') filtered_data = filtered_data.dropna(subset=['timestamp']) + + filtered_data = filtered_data.sort_values(by='timestamp').reset_index(drop=True) if not filtered_data['data'].empty: average = filtered_data['data'].mean() @@ -120,9 +125,13 @@ def analyze_iteration_time(self, threshold=0.15): == max_value, 'timestamp'].iloc[0] min_timestamp = filtered_data.loc[filtered_data['data'] \ == min_value, 'timestamp'].iloc[0] + first_timestamp = filtered_data['timestamp'].iloc[0] + last_timestamp = filtered_data['timestamp'].iloc[-1] + else: average = max_value = min_value = 0.0 max_timestamp = min_timestamp = None + first_timestamp = last_timestamp = None stats = { 'Average': average, @@ -135,8 +144,19 @@ def analyze_iteration_time(self, threshold=0.15): stats_df = pd.DataFrame([stats]) self._iteration_time_data = filtered_data self._iteration_time_stats = stats_df + self._first_timestamp_of_logs = first_timestamp + self._last_timestamp_of_logs = last_timestamp return stats_df + def get_first_last_iteration_timestamp(self): + if not self._first_timestamp_of_logs or not self._last_timestamp_of_logs: + self.analyze_iteration_time() + times ={ + 'first': str(self._first_timestamp_of_logs), + 'last': str(self._last_timestamp_of_logs) + } + return pd.DataFrame([times]) + def get_last_iterations_time_stats(self): return self._iteration_time_stats @@ -158,27 +178,4 @@ def plot_iteration_time_over_time(self): def get_number_of_core_dumps(self): core_dumps = self._log_data_sorted[self._log_data_sorted['type'] == 'timeout_dump_core'] - return len(core_dumps) - - def full_analysis(self): - txt_for_pdf = os.linesep + os.linesep - txt_for_pdf += f"{self.telemetry_type} info: {os.linesep}" - txt_for_pdf += f"Found the following collectx version(s):{os.linesep}" - for collectx_version in self.get_collectx_versions(): - txt_for_pdf += f"{collectx_version}, " - txt_for_pdf += os.linesep - txt_for_pdf += f"Found {self.get_number_of_core_dumps()} core dumps{os.linesep}" - txt_for_pdf += str(self.get_number_of_switches_and_ports()) - iteration_stats = self.get_last_iterations_time_stats() - if iteration_stats is None: - self.analyze_iteration_time() - iteration_stats = self.get_last_iterations_time_stats() - txt_for_pdf += f"Iteration time stats:{os.linesep}" - txt_for_pdf += str(iteration_stats) - self.text_to_show_in_pdf = txt_for_pdf - print(f"stats for {self.telemetry_type}:") - print(self.get_last_iterations_time_stats()) - print(self.get_number_of_switches_and_ports()) - print(f"Collectx versions {self.get_collectx_versions()}") - - return super().full_analysis() + return {"Amount":len(core_dumps)} diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/pdf_creator.py b/plugins/ufm_log_analyzer_plugin/src/loganalyze/pdf_creator.py index c96e1738..087bbcf9 100644 --- a/plugins/ufm_log_analyzer_plugin/src/loganalyze/pdf_creator.py +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/pdf_creator.py @@ -16,7 +16,8 @@ import os from io import StringIO -from fpdf import FPDF # Import FPDF from fpdf module +from fpdf import FPDF +from tabulate import tabulate # Import FPDF from fpdf module class PDFCreator(FPDF): @@ -36,45 +37,98 @@ def footer(self): self.set_font("Arial", "I", 8) self.cell(0, 10, f"Page {self.page_no()}", 0, 0, "C") - def created_pdf(self): - self.set_display_mode("fullpage") - self.add_page() - - # Initial coordinates for images + def add_images(self): + """Adds images to the PDF.""" x_start = 10 y_start = 20 image_width = 180 image_height = 100 spacing = 10 - # Add each image - x = x_start - y = y_start + x, y = x_start, y_start for image_path in self._images_path: if os.path.exists(image_path): - self.image( - image_path, x=x, y=y, w=image_width, h=image_height, type="PNG" - ) - - # Update coordinates for the next image + self.image(image_path, x=x, y=y, w=image_width, h=image_height, type="PNG") y += image_height + spacing - - # Check if next image exceeds page height if y > self.h - image_height - 20: - self.add_page() # Add a new page if needed + self.add_page() y = y_start - # Add text on a new page - self.add_page() # Add a new page for the text + def add_text(self): self.set_font("Arial", "", 12) - output = StringIO() print(self._fabric_stats_list, file=output) - text = ( - output.getvalue().strip() - ) - + text = output.getvalue().strip() self.multi_cell(0, 10, text) - # Output PDF + def add_list_of_dicts_as_text(self, data_list, title=None, headers=None): + """Adds a list of dictionaries to the PDF as aligned text.""" + if not data_list or not isinstance(data_list, list): + return + + # Add title if provided + if title: + self.set_font("Arial", "B", 12) + self.cell(0, 10, title, 0, 1, 'C') + self.ln(5) + + # Set font size for the content + self.set_font("Arial", "", 10) + + # Prepare data for tabulate + table_data = [[str(item.get(header, '')) for header in headers] for item in data_list] + + # Convert the list of dictionaries to a formatted string using `tabulate` + table_str = tabulate(table_data, headers=headers, tablefmt='plain') + + # Print the formatted text as plain text + self.multi_cell(0, 10, table_str) + self.ln(10) + + def add_dataframe_as_text(self, data_frame, title=None): + """Adds a DataFrame to the PDF as aligned text without row numbers.""" + if data_frame is None or data_frame.empty: + return + + # Add title if provided + if title: + self.set_font("Arial", "B", 12) + self.cell(0, 10, title, 0, 1, 'C') + self.ln(5) + + # Adjust font size based on the number of columns + num_columns = len(data_frame.columns) + if num_columns > 10: + self.set_font("Arial", "", 8) # Smaller font for many columns + elif num_columns > 5: + self.set_font("Arial", "", 10) + else: + self.set_font("Arial", "", 12) + + # Convert DataFrame to a formatted string using `tabulate` without row numbers + table_str = tabulate(data_frame.values, headers=data_frame.columns, tablefmt='plain') + + # Print the formatted table as plain text + self.multi_cell(0, 10, table_str) + self.ln(10) + + def create_pdf(self, data_frames_with_titles, lists_to_add): + """Generates the PDF with images, text, and multiple tables.""" + self.set_display_mode("fullpage") + self.add_page() + + # Add images section + self.add_images() + + # Add multiple DataFrames with titles + for title, df in data_frames_with_titles: + self.add_dataframe_as_text(data_frame=df, title=title) + + for data_list, title, headers in lists_to_add: + self.add_list_of_dicts_as_text(data_list, title, headers) + + # Output the final PDF + # Add text section + self.add_text() + self.output(self._pdf_path) diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/requirements.txt b/plugins/ufm_log_analyzer_plugin/src/loganalyze/requirements.txt index 916868a5..de5ba89b 100644 --- a/plugins/ufm_log_analyzer_plugin/src/loganalyze/requirements.txt +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/requirements.txt @@ -2,4 +2,5 @@ pandas numpy matplotlib IPython -fpdf2 \ No newline at end of file +fpdf2 +tabulate \ No newline at end of file From 35cca1caffcd868b686c6eea2bb403c8d6ed54eb Mon Sep 17 00:00:00 2001 From: Boaz Haim Date: Sun, 17 Nov 2024 14:06:37 +0200 Subject: [PATCH 7/9] pylint --- .../src/loganalyze/log_analyzer.py | 25 +++++++++++++------ .../ibdiagnet2_port_counters_analyzer.py | 3 +-- .../src/loganalyze/pdf_creator.py | 2 +- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py index 2abd9269..920e0205 100755 --- a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py @@ -374,20 +374,31 @@ def create_analyzer(parsed_args, full_extracted_logs_list, pdf = PDFCreator(pdf_path, pdf_header, png_images, text_to_show_in_pdf) # Adding telemetry stats to the PDF dataframes_for_pdf = [] - fabric_info = ibdiagnet_analyzer.get_fabric_size() if ibdiagnet_analyzer else "No Fabric Info found" + fabric_info = ibdiagnet_analyzer.get_fabric_size() \ + if ibdiagnet_analyzer else "No Fabric Info found" dataframes_for_pdf.append(("Fabric info", fabric_info)) if links_flapping_analyzer: - dataframes_for_pdf.append(("Link Flapping past week", links_flapping_analyzer.get_link_flapping_last_week())) + dataframes_for_pdf.append(("Link Flapping past week", + links_flapping_analyzer.get_link_flapping_last_week())) lists_to_add = [] critical_events_headers = ["timestamp", "event_type", "event", "count"] - lists_to_add.append((event_log_analyzer.get_critical_event_bursts(), "More than 5 events burst over a minute", critical_events_headers)) + lists_to_add.append((event_log_analyzer.get_critical_event_bursts(), + "More than 5 events burst over a minute", + critical_events_headers)) for cur_telemetry in \ [ibdianget_2_ports_primary_analyzer, ibdianget_2_ports_secondary_analyzer]: - dataframes_for_pdf.append((f"{cur_telemetry.telemetry_type} Telemetry iteration time", cur_telemetry.get_last_iterations_time_stats())) - dataframes_for_pdf.append((f"{cur_telemetry.telemetry_type} Telemetry iteration first and last timestamps", cur_telemetry.get_first_last_iteration_timestamp())) - dataframes_for_pdf.append((f"{cur_telemetry.telemetry_type} Telemetry fabric size", cur_telemetry.get_number_of_switches_and_ports())) - lists_to_add.append(([cur_telemetry.get_number_of_core_dumps()], f"{cur_telemetry.telemetry_type} number of core dumps found in the logs",["Amount"])) + dataframes_for_pdf.append((f"{cur_telemetry.telemetry_type} Telemetry iteration time", + cur_telemetry.get_last_iterations_time_stats())) + dataframes_for_pdf.append((f"{cur_telemetry.telemetry_type} " + "Telemetry iteration first and last timestamps", + cur_telemetry.get_first_last_iteration_timestamp())) + dataframes_for_pdf.append((f"{cur_telemetry.telemetry_type} Telemetry fabric size", + cur_telemetry.get_number_of_switches_and_ports())) + lists_to_add.append(([cur_telemetry.get_number_of_core_dumps()], + f"{cur_telemetry.telemetry_type} " + "number of core dumps found in the logs", + ["Amount"])) # PDF creator gets all the images and to add to the report diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py index 5ee0de4d..a59713de 100644 --- a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py @@ -10,7 +10,6 @@ # provided with the software product. # -import os from typing import List import warnings import pandas as pd @@ -113,7 +112,7 @@ def analyze_iteration_time(self, threshold=0.15): filtered_data = filtered_data[filtered_data['data'] >= threshold] filtered_data['timestamp'] = pd.to_datetime(filtered_data['timestamp'], errors='coerce') filtered_data = filtered_data.dropna(subset=['timestamp']) - + filtered_data = filtered_data.sort_values(by='timestamp').reset_index(drop=True) if not filtered_data['data'].empty: diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/pdf_creator.py b/plugins/ufm_log_analyzer_plugin/src/loganalyze/pdf_creator.py index 087bbcf9..71b9025e 100644 --- a/plugins/ufm_log_analyzer_plugin/src/loganalyze/pdf_creator.py +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/pdf_creator.py @@ -130,5 +130,5 @@ def create_pdf(self, data_frames_with_titles, lists_to_add): # Output the final PDF # Add text section self.add_text() - + self.output(self._pdf_path) From 97b0dbb36d381b46268a004d2aeeb3776688ec9f Mon Sep 17 00:00:00 2001 From: Boaz Haim Date: Sun, 17 Nov 2024 14:19:29 +0200 Subject: [PATCH 8/9] monior changes --- .../src/loganalyze/log_analyzer.py | 2 +- .../ibdiagnet2_port_counters_analyzer.py | 1 - .../src/loganalyze/pdf_creator.py | 18 ++---------------- 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py index 920e0205..6f96e24f 100755 --- a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzer.py @@ -254,6 +254,7 @@ def create_analyzer(parsed_args, full_extracted_logs_list, in the full report. Returns the created analyzer """ + # Checking the base name since some logs in the list are with a directory name if any(os.path.basename(log) == log_name for log in full_extracted_logs_list): log_csvs = get_files_in_dest_by_type(parsed_args.destination, log_name, @@ -372,7 +373,6 @@ def create_analyzer(parsed_args, full_extracted_logs_list, text_to_show_in_pdf = f"Used ufm version in console log {used_ufm_version}{os.linesep}" pdf = PDFCreator(pdf_path, pdf_header, png_images, text_to_show_in_pdf) - # Adding telemetry stats to the PDF dataframes_for_pdf = [] fabric_info = ibdiagnet_analyzer.get_fabric_size() \ if ibdiagnet_analyzer else "No Fabric Info found" diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py index a59713de..40872c0a 100644 --- a/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/log_analyzers/ibdiagnet2_port_counters_analyzer.py @@ -165,7 +165,6 @@ def plot_iteration_time_over_time(self): self._iteration_time_data.set_index('timestamp', inplace=True) - # Plot the data using the existing method with warnings.catch_warnings(): warnings.filterwarnings("ignore", ".*Locator attempting to generate.*") self._save_data_based_on_timestamp( diff --git a/plugins/ufm_log_analyzer_plugin/src/loganalyze/pdf_creator.py b/plugins/ufm_log_analyzer_plugin/src/loganalyze/pdf_creator.py index 71b9025e..146aa200 100644 --- a/plugins/ufm_log_analyzer_plugin/src/loganalyze/pdf_creator.py +++ b/plugins/ufm_log_analyzer_plugin/src/loganalyze/pdf_creator.py @@ -17,7 +17,7 @@ import os from io import StringIO from fpdf import FPDF -from tabulate import tabulate # Import FPDF from fpdf module +from tabulate import tabulate class PDFCreator(FPDF): @@ -66,22 +66,16 @@ def add_list_of_dicts_as_text(self, data_list, title=None, headers=None): if not data_list or not isinstance(data_list, list): return - # Add title if provided if title: self.set_font("Arial", "B", 12) self.cell(0, 10, title, 0, 1, 'C') self.ln(5) - # Set font size for the content self.set_font("Arial", "", 10) - # Prepare data for tabulate table_data = [[str(item.get(header, '')) for header in headers] for item in data_list] - - # Convert the list of dictionaries to a formatted string using `tabulate` table_str = tabulate(table_data, headers=headers, tablefmt='plain') - # Print the formatted text as plain text self.multi_cell(0, 10, table_str) self.ln(10) @@ -90,13 +84,11 @@ def add_dataframe_as_text(self, data_frame, title=None): if data_frame is None or data_frame.empty: return - # Add title if provided if title: self.set_font("Arial", "B", 12) self.cell(0, 10, title, 0, 1, 'C') self.ln(5) - # Adjust font size based on the number of columns num_columns = len(data_frame.columns) if num_columns > 10: self.set_font("Arial", "", 8) # Smaller font for many columns @@ -105,10 +97,9 @@ def add_dataframe_as_text(self, data_frame, title=None): else: self.set_font("Arial", "", 12) - # Convert DataFrame to a formatted string using `tabulate` without row numbers + # Converting and removing the row number as it is not needed table_str = tabulate(data_frame.values, headers=data_frame.columns, tablefmt='plain') - # Print the formatted table as plain text self.multi_cell(0, 10, table_str) self.ln(10) @@ -116,19 +107,14 @@ def create_pdf(self, data_frames_with_titles, lists_to_add): """Generates the PDF with images, text, and multiple tables.""" self.set_display_mode("fullpage") self.add_page() - - # Add images section self.add_images() - # Add multiple DataFrames with titles for title, df in data_frames_with_titles: self.add_dataframe_as_text(data_frame=df, title=title) for data_list, title, headers in lists_to_add: self.add_list_of_dicts_as_text(data_list, title, headers) - # Output the final PDF - # Add text section self.add_text() self.output(self._pdf_path) From bf28e48a941657f4cff8797372dea3ddb1adffa4 Mon Sep 17 00:00:00 2001 From: Boaz Haim Date: Sun, 17 Nov 2024 14:20:52 +0200 Subject: [PATCH 9/9] Removing old text from readme --- plugins/ufm_log_analyzer_plugin/README.md | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/plugins/ufm_log_analyzer_plugin/README.md b/plugins/ufm_log_analyzer_plugin/README.md index 30dd81b3..05694bae 100644 --- a/plugins/ufm_log_analyzer_plugin/README.md +++ b/plugins/ufm_log_analyzer_plugin/README.md @@ -26,22 +26,6 @@ sudo yum install -y libjpeg-devel zlib-devel ``` Know your UFM sysdump location. -#### Running on a remote server -Since the tool generates graphs, you will need to setup an X11 forwarding: - -1. Mac - Install and run [Quartz](https://www.xquartz.org/). Windows - Install and run [Xming](http://www.straightrunning.com) -2. On your remote server (Ubuntu/RedHat), make sure the x11 forwarding is enabled: -``` -vim /etc/ssh/sshd_config -#Enalbe x11 -X11Forwarding yes -``` -3. Restart the ssh service `systemctl restart ssh` or `systemctl restart sshd` depends on the OS. -4. Install `python3-tk` using `sudo yum install python3-tkinter` or `sudo apt-get install python3-tk` depends on the OS. -5. When you SSH to the server, use the flag `-X`, for example `ssh -X root@my-vm` - -If you would like to make sure it is working, once connection is done, do `xclock &`. This should start a clock on your machine. - ### How to run ``` ./log_analzer.sh [options] -l @@ -89,7 +73,6 @@ This logic will show links that: 2. Thermal shut down. 3. If one side went down and the other side was not rebooted. - ![Tool flow](img/loganalzer.png)