blob: 8270655bd9e2d43fcb976c3a6cfa1911646b476f [file]
############################################################################
# SPDX-License-Identifier: Apache-2.0
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership. The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
############################################################################
"""Process performance data."""
import csv
import json
import os
import re
import time
from typing import TYPE_CHECKING, Any, List, Tuple
from ntfc.lib.performance.sqllite_lib import DBLib
from ntfc.log.logger import logger
if TYPE_CHECKING:
from pathlib import Path
class ProcessPerfData:
"""Process performance data."""
def read_json_file(self, jsonfilepath: str) -> Any:
"""Read json file."""
with open(jsonfilepath, "r", encoding="utf-8") as file:
data = file.read()
json_data = json.loads(data)
return json_data
def _wait_for_file(
self, file_path: str, max_retries: int = 10, retry_interval: int = 5
) -> bool:
"""Check if the file exists.
If not, it will retry for a maximum of max_retries times with a retry
interval of retry_interval seconds.
If the file is not found after max_retries, it will return False.
"""
retries = 0
while retries < max_retries:
if os.path.exists(file_path):
logger.info(f"File: <{file_path}> already exists")
return True
logger.info(
f"Search for files <{file_path}>..."
f" (retry {retries + 1}/{max_retries})"
)
time.sleep(retry_interval)
retries += 1
logger.info(
f"Reached maximum retry count {max_retries},"
f"file <{file_path}> still not found"
)
return False
def __create_performance_folder(self, resultdir: str) -> Tuple[bool, str]:
"""Create performance folder."""
try:
report_path = resultdir
folder_path = os.path.join(report_path, "performance")
if not os.path.exists(folder_path):
os.makedirs(folder_path, exist_ok=True)
logger.info(f"Create performance-folder: {folder_path}")
return (True, folder_path)
except Exception as e:
logger.error(f"Failed to create performance-folder: {e}")
return (False, "")
def __get_perf_data_from_log_file(
self,
output: List[str],
board: str = "",
core: str = "",
branch: str = "",
) -> Tuple[List[str], List[List[str]]]:
"""Get performance data from log file."""
raw_data_list = [line for line in output if not line.startswith("ap>")]
head = ["board", "core", "branch"]
processed_data = []
flag = True
for line in raw_data_list:
j = line.split()
if len(j) == 4 and flag:
head.extend(j)
flag = False
elif len(j) == 4 and not flag:
data_row = [board, core, branch] + j
processed_data.append(data_row)
return head, processed_data
def generate_csv_in_the_specified_dir(
self,
resultdir: "Path",
domain: str,
metricname: str,
csvheadlist: List[str],
csvdatalist: List[List[str]],
) -> bool:
"""Generate CSV file."""
create_res = self.__create_performance_folder(str(resultdir))
if create_res[0]:
try:
head = [
x.lower() if isinstance(x, str) else x for x in csvheadlist
]
required_items = {"board", "core", "branch"}
contains_all = all(item in head for item in required_items)
if contains_all:
csvfp = os.path.join(
create_res[1], domain + "-" + metricname + ".csv"
)
with open(csvfp, "w", encoding="UTF8", newline="") as f:
writer = csv.writer(f)
writer.writerow(head)
writer.writerows(csvdatalist)
logger.info(
f"Write performance data to csv file Successfully."
f" Csv fp: {csvfp}"
)
return True
logger.error(
"The csvheadlist must contain three fields: board, "
"core, and branch.Please check your csvheadlist field."
)
return False
except Exception as e:
logger.error(f"Writing CSV file failed: {str(e)}")
return False
return False
def generate_csv_of_simple_scene(
self,
output: List[str],
board: str,
core: str,
branch: str,
reportdir: "Path",
domain: str,
metricname: str,
) -> None:
"""Generate CSV file."""
perf_data = self.__get_perf_data_from_log_file(
output, board, core, branch
)
self.generate_csv_in_the_specified_dir(
reportdir,
domain,
metricname,
csvheadlist=perf_data[0],
csvdatalist=perf_data[1],
)
class DataProcess(DBLib):
"""Process data."""
def __init__(self, dbpath: str) -> None:
"""Initialize data process handler."""
super().__init__(dbpath)
self.db_path = dbpath
def __clean_sql(self, sql: str) -> str:
"""Clean sql command."""
sql = re.sub(r"--.*", "", sql)
sql = re.sub(r"/\*.*?\*/", "", sql, flags=re.DOTALL)
sql = re.sub(r"\s+", " ", sql)
return sql.strip()
def __split_columns(self, body: str) -> List[str]:
"""Split columds."""
columns: List[str] = []
current: List[str] = []
depth = 0
for char in body:
if char == "(":
depth += 1
elif char == ")":
depth -= 1
if char == "," and depth == 0:
columns.append("".join(current).strip())
current = []
else:
current.append(char)
if current:
columns.append("".join(current).strip())
return columns
def step_1_mysql_to_sqlitesql(self, mysqlfp: str) -> Any: # noqa: C901
"""Translate MySQL statements into SQLite statements."""
logger.info(
"step-1: translate MySQL statements into SQLite statements"
)
time.sleep(3)
with open(mysqlfp, "r", encoding="utf-8") as file:
sql_commands = file.read()
cleaned_sql = self.__clean_sql(sql_commands)
table_match = re.search(r"CREATE TABLE `([^`]+)`", cleaned_sql)
if not table_match:
raise ValueError("Unable to extract table name")
table_name = table_match.group(1)
body_match = re.search(r"\((.*)\)", cleaned_sql, re.DOTALL)
if not body_match:
raise ValueError("Unable to extract table definition subject")
table_body = body_match.group(1)
column_defs = self.__split_columns(table_body)
processed_columns = []
indexes = []
for col_def in column_defs:
if col_def.startswith("`"):
col_name_match = re.match(r"`([^`]+)`", col_def)
if not col_name_match:
continue
col_name = col_name_match.group(1)
remaining = col_def[len(col_name_match.group(0)) :].strip()
type_match = re.match(
r"([a-z]+)(?:\([^)]+\))?", remaining, re.IGNORECASE
)
if not type_match:
continue
mysql_type = type_match.group(1).lower()
type_map = {
"bigint": "INTEGER",
"int": "INTEGER",
"varchar": "TEXT",
"char": "TEXT",
"datetime": "TEXT",
}
sqlite_type = type_map.get(mysql_type, mysql_type.upper())
constraints = []
if col_name == "id" and "NOT NULL" in remaining:
remaining = remaining.replace("NOT NULL", "").strip()
if "NOT NULL" in remaining and col_name != "id":
constraints.append("NOT NULL")
default_match = re.search(
r"DEFAULT\s+([^\s,]+)", remaining, re.IGNORECASE
)
if default_match:
default_value = default_match.group(1)
if default_value.startswith(
"'"
) and default_value.endswith("'"):
default_value = f"'{default_value[1:-1]}'"
constraints.append(f"DEFAULT {default_value}")
if "AUTO_INCREMENT" in remaining:
constraints.append("PRIMARY KEY AUTOINCREMENT")
if (
col_name == "update_time"
and "ON UPDATE CURRENT_TIMESTAMP" in remaining
):
remaining = remaining.replace(
"ON UPDATE CURRENT_TIMESTAMP", ""
).strip()
new_col_def = f"{col_name} {sqlite_type}"
if constraints:
new_col_def += " " + " ".join(constraints)
processed_columns.append(new_col_def)
elif "PRIMARY KEY" in col_def and "`id`" in col_def:
continue
elif "KEY" in col_def:
key_match = re.search(
r"KEY\s+`([^`]+)`\s*\(`([^`]+)`\)", col_def
)
if key_match:
indexes.append((key_match.group(1), key_match.group(2)))
create_table_sql = f"CREATE TABLE {table_name} (\n "
create_table_sql += ",\n ".join(processed_columns)
create_table_sql += "\n);\n"
for index_name, column in indexes:
create_table_sql += (
f"CREATE INDEX {index_name} ON {table_name} ({column});\n"
)
return create_table_sql.strip(), table_name
def step_2_create_new_table(self, sqlcmd: str) -> None:
"""Create new table for sqlite."""
logger.info("step-2: create new table")
time.sleep(1)
super()._create_table(sqlcmd)
def setp_3_insert_csv_data_from_csv(
self, csvfp: str, tabname: Any
) -> None:
"""Read data from CSV file and insert it into database."""
logger.info("step-3: insert performance data from csv")
time.sleep(1)
try:
with open(csvfp, "r", encoding="utf-8") as file:
reader = csv.reader(file)
headers = next(reader)
lowercase_headers = [
header[0].lower() + header[1:] for header in headers
]
data_to_insert = []
for row in reader:
board = row[0]
core = row[1].strip("[]'")
description = row[2]
max_val = int(row[3])
min_val = int(row[4])
avg_val = int(row[5])
data_to_insert.append(
tuple(
[
board,
core,
description,
max_val,
min_val,
avg_val,
]
)
)
super()._insert_data(
tablename=tabname,
lowercaseheaders=lowercase_headers,
data=data_to_insert,
)
except Exception as e:
logger.info(f"Error: fail to insert performance data: {e}")
def performance_indicator_data_storage_verification(
self, mysqlfp: str, csvfp: str
) -> None:
"""Verify the data storage of performance indicators."""
data_tup = self.step_1_mysql_to_sqlitesql(mysqlfp)
self.step_2_create_new_table(data_tup[0])
self.setp_3_insert_csv_data_from_csv(csvfp, data_tup[1])