init repo
This commit is contained in:
commit
31a6a0b255
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
tmp/
|
||||
src/__pycache__/
|
||||
log.tar.gz
|
||||
184
resource/MainWindow.ui
Normal file
184
resource/MainWindow.ui
Normal file
@ -0,0 +1,184 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1212</width>
|
||||
<height>693</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>OnekeyLogDiagnose</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string/>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab_console">
|
||||
<attribute name="title">
|
||||
<string>控制台</string>
|
||||
</attribute>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QTextBrowser" name="textBrowser_console">
|
||||
<property name="html">
|
||||
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">通过文件上传一键日志压缩包来开始</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_baseinfo">
|
||||
<attribute name="title">
|
||||
<string>基本信息</string>
|
||||
</attribute>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QTextBrowser" name="textBrowser_info"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_alert">
|
||||
<attribute name="title">
|
||||
<string>关键告警</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_temp_all">
|
||||
<attribute name="title">
|
||||
<string>温度信息</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboBox">
|
||||
<property name="editable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>ALL_Temp</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>CPU_Temp</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>FPGA_Temp</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>DIMM_Temp</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Inlet_CPU_Temp</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Inlet_FPGA_Temp</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>M2_Temp</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Outlet_CPU_Temp</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Outlet_FPGA_Temp</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>VR_CPU_Temp</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>VR_FPGA_Temp</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGraphicsView" name="graphicsView"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menubar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1212</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menu">
|
||||
<property name="title">
|
||||
<string>文件</string>
|
||||
</property>
|
||||
<addaction name="actionUpload_log"/>
|
||||
</widget>
|
||||
<addaction name="menu"/>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar"/>
|
||||
<action name="actionUpload_log">
|
||||
<property name="text">
|
||||
<string>打开</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>上传日志文件到程序中</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+O</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
<designerdata>
|
||||
<property name="gridDeltaX">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="gridDeltaY">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="gridSnapX">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="gridSnapY">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="gridVisible">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</designerdata>
|
||||
</ui>
|
||||
113
src/MainWindow_ui.py
Normal file
113
src/MainWindow_ui.py
Normal file
@ -0,0 +1,113 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'resource\Mainwindow.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.9
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_MainWindow(object):
|
||||
def setupUi(self, MainWindow):
|
||||
MainWindow.setObjectName("MainWindow")
|
||||
MainWindow.resize(1212, 693)
|
||||
MainWindow.setStatusTip("")
|
||||
self.centralwidget = QtWidgets.QWidget(MainWindow)
|
||||
self.centralwidget.setObjectName("centralwidget")
|
||||
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.centralwidget)
|
||||
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
|
||||
self.tabWidget = QtWidgets.QTabWidget(self.centralwidget)
|
||||
self.tabWidget.setObjectName("tabWidget")
|
||||
self.tab_console = QtWidgets.QWidget()
|
||||
self.tab_console.setObjectName("tab_console")
|
||||
self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.tab_console)
|
||||
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
|
||||
self.textBrowser_console = QtWidgets.QTextBrowser(self.tab_console)
|
||||
self.textBrowser_console.setObjectName("textBrowser_console")
|
||||
self.horizontalLayout_4.addWidget(self.textBrowser_console)
|
||||
self.tabWidget.addTab(self.tab_console, "")
|
||||
self.tab_baseinfo = QtWidgets.QWidget()
|
||||
self.tab_baseinfo.setObjectName("tab_baseinfo")
|
||||
self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.tab_baseinfo)
|
||||
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
|
||||
self.textBrowser_info = QtWidgets.QTextBrowser(self.tab_baseinfo)
|
||||
self.textBrowser_info.setObjectName("textBrowser_info")
|
||||
self.horizontalLayout_3.addWidget(self.textBrowser_info)
|
||||
self.tabWidget.addTab(self.tab_baseinfo, "")
|
||||
self.tab_alert = QtWidgets.QWidget()
|
||||
self.tab_alert.setObjectName("tab_alert")
|
||||
self.tabWidget.addTab(self.tab_alert, "")
|
||||
self.tab_temp_all = QtWidgets.QWidget()
|
||||
self.tab_temp_all.setObjectName("tab_temp_all")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(self.tab_temp_all)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.comboBox = QtWidgets.QComboBox(self.tab_temp_all)
|
||||
self.comboBox.setEditable(False)
|
||||
self.comboBox.setObjectName("comboBox")
|
||||
self.comboBox.addItem("")
|
||||
self.comboBox.addItem("")
|
||||
self.comboBox.addItem("")
|
||||
self.comboBox.addItem("")
|
||||
self.comboBox.addItem("")
|
||||
self.comboBox.addItem("")
|
||||
self.comboBox.addItem("")
|
||||
self.comboBox.addItem("")
|
||||
self.comboBox.addItem("")
|
||||
self.comboBox.addItem("")
|
||||
self.comboBox.addItem("")
|
||||
self.verticalLayout.addWidget(self.comboBox)
|
||||
self.graphicsView = QtWidgets.QGraphicsView(self.tab_temp_all)
|
||||
self.graphicsView.setObjectName("graphicsView")
|
||||
self.verticalLayout.addWidget(self.graphicsView)
|
||||
self.tabWidget.addTab(self.tab_temp_all, "")
|
||||
self.horizontalLayout_2.addWidget(self.tabWidget)
|
||||
MainWindow.setCentralWidget(self.centralwidget)
|
||||
self.menubar = QtWidgets.QMenuBar(MainWindow)
|
||||
self.menubar.setGeometry(QtCore.QRect(0, 0, 1212, 23))
|
||||
self.menubar.setObjectName("menubar")
|
||||
self.menu = QtWidgets.QMenu(self.menubar)
|
||||
self.menu.setObjectName("menu")
|
||||
MainWindow.setMenuBar(self.menubar)
|
||||
self.statusbar = QtWidgets.QStatusBar(MainWindow)
|
||||
self.statusbar.setObjectName("statusbar")
|
||||
MainWindow.setStatusBar(self.statusbar)
|
||||
self.actionUpload_log = QtWidgets.QAction(MainWindow)
|
||||
self.actionUpload_log.setObjectName("actionUpload_log")
|
||||
self.menu.addAction(self.actionUpload_log)
|
||||
self.menubar.addAction(self.menu.menuAction())
|
||||
|
||||
self.retranslateUi(MainWindow)
|
||||
self.tabWidget.setCurrentIndex(0)
|
||||
QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
||||
|
||||
def retranslateUi(self, MainWindow):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
MainWindow.setWindowTitle(_translate("MainWindow", "OnekeyLogDiagnose"))
|
||||
self.textBrowser_console.setHtml(_translate("MainWindow", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
|
||||
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
|
||||
"p, li { white-space: pre-wrap; }\n"
|
||||
"</style></head><body style=\" font-family:\'SimSun\'; font-size:9pt; font-weight:400; font-style:normal;\">\n"
|
||||
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">通过文件上传一键日志压缩包来开始</p></body></html>"))
|
||||
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_console), _translate("MainWindow", "控制台"))
|
||||
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_baseinfo), _translate("MainWindow", "基本信息"))
|
||||
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_alert), _translate("MainWindow", "关键告警"))
|
||||
self.comboBox.setItemText(0, _translate("MainWindow", "ALL_Temp"))
|
||||
self.comboBox.setItemText(1, _translate("MainWindow", "CPU_Temp"))
|
||||
self.comboBox.setItemText(2, _translate("MainWindow", "FPGA_Temp"))
|
||||
self.comboBox.setItemText(3, _translate("MainWindow", "DIMM_Temp"))
|
||||
self.comboBox.setItemText(4, _translate("MainWindow", "Inlet_CPU_Temp"))
|
||||
self.comboBox.setItemText(5, _translate("MainWindow", "Inlet_FPGA_Temp"))
|
||||
self.comboBox.setItemText(6, _translate("MainWindow", "M2_Temp"))
|
||||
self.comboBox.setItemText(7, _translate("MainWindow", "Outlet_CPU_Temp"))
|
||||
self.comboBox.setItemText(8, _translate("MainWindow", "Outlet_FPGA_Temp"))
|
||||
self.comboBox.setItemText(9, _translate("MainWindow", "VR_CPU_Temp"))
|
||||
self.comboBox.setItemText(10, _translate("MainWindow", "VR_FPGA_Temp"))
|
||||
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_temp_all), _translate("MainWindow", "温度信息"))
|
||||
self.menu.setTitle(_translate("MainWindow", "文件"))
|
||||
self.actionUpload_log.setText(_translate("MainWindow", "打开"))
|
||||
self.actionUpload_log.setStatusTip(_translate("MainWindow", "上传日志文件到程序中"))
|
||||
self.actionUpload_log.setShortcut(_translate("MainWindow", "Ctrl+O"))
|
||||
79
src/ZiJin_parse_baseinfo.py
Normal file
79
src/ZiJin_parse_baseinfo.py
Normal file
@ -0,0 +1,79 @@
|
||||
import os
|
||||
import utils
|
||||
import json
|
||||
|
||||
project_root = utils.get_project_root()
|
||||
cache_dir = os.path.join(project_root, "tmp")
|
||||
log_dir = os.path.join(cache_dir, "onekeylog")
|
||||
|
||||
component_dir = os.path.join(log_dir, "component")
|
||||
component_file = os.path.join(component_dir, "component.log")
|
||||
baseinfo_file = os.path.join(cache_dir, "baseinfo.txt")
|
||||
|
||||
def get_fw_version_info():
|
||||
fw_version_line = 3
|
||||
if not os.path.exists(component_file):
|
||||
print("no component.log found")
|
||||
return
|
||||
|
||||
fw_version_info = utils.read_specific_line(component_file, fw_version_line)
|
||||
json_fw_info = json.loads(fw_version_info)
|
||||
|
||||
version_str = "\nDPU Firmware Version Info:\n"
|
||||
version_str += f"BMC Version : {json_fw_info[0]['dev_version']}\n"
|
||||
version_str += f"Bios Version : {json_fw_info[2]['dev_version']}\n"
|
||||
version_str += f"ME Version : {json_fw_info[3]['dev_version']}\n"
|
||||
version_str += f"Soc_CPLD Version : {json_fw_info[4]['dev_version']}\n"
|
||||
version_str += f"Fpga_CPLD Version : {json_fw_info[5]['dev_version']}\n"
|
||||
version_str += f"FPGA Version : {json_fw_info[6]['dev_version']}\n"
|
||||
|
||||
print(version_str)
|
||||
utils.append_to_file(baseinfo_file, version_str)
|
||||
|
||||
def get_fru_info():
|
||||
fru_line = 19
|
||||
if not os.path.exists(component_file):
|
||||
print("no component.log found")
|
||||
return
|
||||
fru_info = utils.read_specific_line(component_file, fru_line)
|
||||
json_fru_info = json.loads(fru_info)
|
||||
|
||||
fru_str = "\nDPU FRU Infomation:\n"
|
||||
fru_str += f"Board Product Name : {json_fru_info[0]['board']['product_name']}\n"
|
||||
fru_str += f"Board SN : {json_fru_info[0]['board']['serial_number']}\n"
|
||||
fru_str += f"Board PartNumber : {json_fru_info[0]['board']['part_number']}\n"
|
||||
fru_str += f"Server Product Name : {json_fru_info[0]['product']['product_name']}\n"
|
||||
fru_str += f"Server Product Ver : {json_fru_info[0]['product']['product_version']}\n"
|
||||
fru_str += f"Server Product SN : {json_fru_info[0]['product']['serial_number']}\n"
|
||||
fru_str += f"Server Custom Field : {json_fru_info[0]['product']['custom_fields-1']}\n"
|
||||
|
||||
print(fru_str)
|
||||
utils.append_to_file(baseinfo_file, fru_str)
|
||||
|
||||
# def get_hardware_info():
|
||||
# hw_cpu_info_line = 5
|
||||
# if not os.path.exists(component_file):
|
||||
# print("no component.log found")
|
||||
# return
|
||||
# cpu_info = read_specific_line(component_file, hw_cpu_info_line)
|
||||
# json_cpu_info = json.loads(cpu_info)
|
||||
|
||||
# hw_info_str = "\nDPU Hardware Infomation:\n"
|
||||
|
||||
|
||||
def program_main():
|
||||
get_fw_version_info()
|
||||
get_fru_info()
|
||||
# get_hardware_info()
|
||||
|
||||
return True
|
||||
|
||||
def get_all_infostring():
|
||||
return utils.read_file_to_string(baseinfo_file)
|
||||
|
||||
if __name__ == "__main__":
|
||||
get_fw_version_info()
|
||||
|
||||
|
||||
|
||||
|
||||
1
src/ZiJin_parse_idl.py
Normal file
1
src/ZiJin_parse_idl.py
Normal file
@ -0,0 +1 @@
|
||||
import os
|
||||
293
src/ZiJin_parse_sensorhistory.py
Normal file
293
src/ZiJin_parse_sensorhistory.py
Normal file
@ -0,0 +1,293 @@
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
import re
|
||||
from datetime import datetime
|
||||
import os
|
||||
import argparse
|
||||
import utils
|
||||
|
||||
project_root = utils.get_project_root()
|
||||
cache_dir = os.path.join(project_root, "tmp")
|
||||
default_chart_dir = os.path.join(cache_dir, "chart")
|
||||
|
||||
def parse_temperature_data(file_path):
|
||||
"""
|
||||
解析温度数据文件,返回一个包含时间戳和各温度点数据的DataFrame
|
||||
处理温度值为'disable'或255的情况,将其视为无效值
|
||||
|
||||
参数:
|
||||
file_path: 数据文件路径
|
||||
|
||||
返回:
|
||||
DataFrame: 包含时间戳和各温度点数据的DataFrame
|
||||
"""
|
||||
# 初始化数据列表
|
||||
timestamps = []
|
||||
temperature_data = {}
|
||||
|
||||
try:
|
||||
with open(file_path, 'r') as file:
|
||||
for line_num, line in enumerate(file, 1):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
|
||||
# 分割时间戳和温度数据
|
||||
parts = line.split('|')
|
||||
if len(parts) != 2:
|
||||
print(f"警告: 第 {line_num} 行格式不正确,已跳过")
|
||||
continue
|
||||
|
||||
timestamp_str, temps_str = parts
|
||||
|
||||
# 解析时间戳
|
||||
try:
|
||||
# 尝试多种常见时间格式
|
||||
timestamp_formats = [
|
||||
'%Y-%m-%dT%H:%M:%S',
|
||||
]
|
||||
|
||||
timestamp = None
|
||||
for fmt in timestamp_formats:
|
||||
try:
|
||||
timestamp = datetime.strptime(timestamp_str, fmt)
|
||||
break
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
if timestamp is None:
|
||||
print(f"警告: 第 {line_num} 行时间戳格式无法识别,已跳过")
|
||||
continue
|
||||
|
||||
timestamps.append(timestamp)
|
||||
|
||||
except Exception as e:
|
||||
print(f"警告: 第 {line_num} 行时间戳解析错误 - {str(e)},已跳过")
|
||||
continue
|
||||
|
||||
# 解析温度数据
|
||||
temp_entries = temps_str.split(',')
|
||||
for entry in temp_entries:
|
||||
entry = entry.strip()
|
||||
if not entry:
|
||||
continue
|
||||
|
||||
try:
|
||||
name, temp_value = entry.split(':')
|
||||
name = name.strip()
|
||||
temp_value = temp_value.strip()
|
||||
|
||||
# 检查是否为无效值
|
||||
if temp_value.lower() == 'disable' or temp_value == '255':
|
||||
temp = None # 用None表示无效值
|
||||
else:
|
||||
temp = float(temp_value)
|
||||
|
||||
# 初始化温度点列表(如果不存在)
|
||||
if name not in temperature_data:
|
||||
temperature_data[name] = []
|
||||
|
||||
# 填充数据(确保所有列表长度相同)
|
||||
while len(temperature_data[name]) < len(timestamps) - 1:
|
||||
temperature_data[name].append(None)
|
||||
|
||||
temperature_data[name].append(temp)
|
||||
|
||||
except Exception as e:
|
||||
print(f"警告: 第 {line_num} 行温度数据 '{entry}' 解析错误 - {str(e)},已跳过")
|
||||
continue
|
||||
|
||||
# 确保所有温度点列表长度相同
|
||||
for name in temperature_data:
|
||||
while len(temperature_data[name]) < len(timestamps):
|
||||
temperature_data[name].append(None)
|
||||
|
||||
# 创建DataFrame
|
||||
data = {'timestamp': timestamps}
|
||||
data.update(temperature_data)
|
||||
df = pd.DataFrame(data)
|
||||
df.set_index('timestamp', inplace=True)
|
||||
|
||||
# 统计每个温度点的无效值数量
|
||||
for column in df.columns:
|
||||
invalid_count = df[column].isna().sum()
|
||||
if invalid_count > 0:
|
||||
print(f"注意: 温度点 '{column}' 包含 {invalid_count} 个无效值,已排除在分析之外")
|
||||
|
||||
return df
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"错误: 文件 '{file_path}' 不存在")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"解析文件时发生错误: {str(e)}")
|
||||
return None
|
||||
|
||||
def generate_temperature_charts(df, output_dir=default_chart_dir):
|
||||
"""
|
||||
根据温度数据生成图表,自动忽略无效值
|
||||
|
||||
参数:
|
||||
df: 包含温度数据的DataFrame
|
||||
output_dir: 图表输出目录
|
||||
"""
|
||||
if df is None or df.empty:
|
||||
print("没有数据可生成图表")
|
||||
return
|
||||
|
||||
# 创建输出目录
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
# 设置中文字体支持
|
||||
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
|
||||
plt.rcParams["axes.unicode_minus"] = False # 正确显示负号
|
||||
|
||||
# 1. 所有温度点的趋势图
|
||||
plt.figure(figsize=(12, 6))
|
||||
for column in df.columns:
|
||||
# 过滤掉无效值(None)再绘图
|
||||
valid_data = df[column].dropna()
|
||||
plt.plot(valid_data.index, valid_data, label=column, alpha=0.7)
|
||||
|
||||
plt.title('各温度点随时间变化趋势')
|
||||
plt.xlabel('时间')
|
||||
plt.ylabel('温度 (°C)')
|
||||
plt.legend()
|
||||
plt.grid(True, linestyle='--', alpha=0.7)
|
||||
plt.xticks(rotation=45)
|
||||
plt.tight_layout()
|
||||
plt.savefig(os.path.join(output_dir, 'temperature_trends.png'), dpi=300)
|
||||
plt.close()
|
||||
|
||||
# 2. 每个温度点的单独图表
|
||||
for column in df.columns:
|
||||
plt.figure(figsize=(12, 4))
|
||||
# 过滤掉无效值(None)再绘图
|
||||
valid_data = df[column].dropna()
|
||||
plt.plot(valid_data.index, valid_data, label=column, color='blue')
|
||||
|
||||
# 计算并绘制平均值线(仅基于有效值)
|
||||
if not valid_data.empty:
|
||||
mean_temp = valid_data.mean()
|
||||
plt.axhline(y=mean_temp, color='r', linestyle='--', label=f'平均值: {mean_temp:.2f}°C')
|
||||
|
||||
plt.title(f'{column} 温度随时间变化')
|
||||
plt.xlabel('时间')
|
||||
plt.ylabel('温度 (°C)')
|
||||
plt.legend()
|
||||
plt.grid(True, linestyle='--', alpha=0.7)
|
||||
plt.xticks(rotation=45)
|
||||
plt.tight_layout()
|
||||
plt.savefig(os.path.join(output_dir, f'{column}_temperature.png'), dpi=300)
|
||||
plt.close()
|
||||
|
||||
# 3. 温度分布箱线图(自动排除NaN值)
|
||||
plt.figure(figsize=(10, 6))
|
||||
df.boxplot()
|
||||
plt.title('各温度点分布情况')
|
||||
plt.ylabel('温度 (°C)')
|
||||
plt.grid(True, linestyle='--', alpha=0.7)
|
||||
plt.tight_layout()
|
||||
plt.savefig(os.path.join(output_dir, 'temperature_distribution.png'), dpi=300)
|
||||
plt.close()
|
||||
|
||||
print(f"图表已成功生成并保存到 '{output_dir}' 目录")
|
||||
|
||||
def get_sensor_all_image_path(type):
|
||||
match type:
|
||||
case "all":
|
||||
file_path = os.path.join(default_chart_dir, "temperature_trends.png")
|
||||
case "cpu":
|
||||
file_path = os.path.join(default_chart_dir, "CPU_temperature.png")
|
||||
case "fpga":
|
||||
file_path = os.path.join(default_chart_dir, "FPGA_temperature.png")
|
||||
case "dimm":
|
||||
file_path = os.path.join(default_chart_dir, "DIMM_temperature.png")
|
||||
case "inlet_cpu":
|
||||
file_path = os.path.join(default_chart_dir, "Inlet_CPU_temperature.png")
|
||||
case "inlet_fpga":
|
||||
file_path = os.path.join(default_chart_dir, "Inlet_FPGA_temperature.png")
|
||||
case "m2":
|
||||
file_path = os.path.join(default_chart_dir, "M2_Temp_temperature.png")
|
||||
case "outlet_cpu":
|
||||
file_path = os.path.join(default_chart_dir, "Outlet_CPU_temperature.png")
|
||||
case "outlet_fpga":
|
||||
file_path = os.path.join(default_chart_dir, "Outlet_FPGA_temperature.png")
|
||||
case "vr_cpu":
|
||||
file_path = os.path.join(default_chart_dir, "VR_CPU_temperature.png")
|
||||
case "vr_fpga":
|
||||
file_path = os.path.join(default_chart_dir, "VR_FPGA_temperature.png")
|
||||
case _:
|
||||
file_path = os.path.join(default_chart_dir, "temperature_distribution.png")
|
||||
return file_path
|
||||
|
||||
def program_main():
|
||||
# 寻找日志并合并
|
||||
onekeylog_root = os.path.join(cache_dir, "onekeylog")
|
||||
log_dir = os.path.join(onekeylog_root, "log")
|
||||
sensor_file0 = os.path.join(log_dir, "sensorhistory.log")
|
||||
sensor_file1 = os.path.join(log_dir, "sensorhistory.log.1")
|
||||
sensor_merge = os.path.join(cache_dir, "sensorhistory_merge.log")
|
||||
|
||||
if not os.path.exists(sensor_file0):
|
||||
print("无温度数据日志文件")
|
||||
return False
|
||||
|
||||
try:
|
||||
# 合并文件
|
||||
with open(sensor_merge, 'w', encoding='utf-8') as out_f:
|
||||
# 写入第一个文件内容
|
||||
with open(sensor_file0, 'r', encoding='utf-8') as f1:
|
||||
out_f.write(f1.read())
|
||||
out_f.write('\n') # 在两个文件内容之间添加空行分隔
|
||||
|
||||
if os.path.exists(sensor_file1):
|
||||
# 写入第二个文件内容
|
||||
with open(sensor_file1, 'r', encoding='utf-8') as f2:
|
||||
out_f.write(f2.read())
|
||||
|
||||
print(f"成功合并文件到: {sensor_merge}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"合并文件失败: {str(e)}")
|
||||
return False
|
||||
|
||||
# 解析数据
|
||||
print(f"正在解析数据文件: {sensor_merge}")
|
||||
df = parse_temperature_data(sensor_merge)
|
||||
|
||||
if df is not None and not df.empty:
|
||||
print("数据解析成功,包含以下温度点:", ", ".join(df.columns))
|
||||
print(f"时间范围: {df.index.min()} 至 {df.index.max()}")
|
||||
print(f"记录总数: {len(df)}")
|
||||
|
||||
# 生成图表
|
||||
generate_temperature_charts(df, default_chart_dir)
|
||||
else:
|
||||
print("数据解析失败")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def main():
|
||||
# 设置命令行参数
|
||||
parser = argparse.ArgumentParser(description='分析温度数据文件并生成可视化图表')
|
||||
parser.add_argument('file_path', help='温度数据文件路径')
|
||||
parser.add_argument('-o', '--output', default='charts', help='图表输出目录,默认为 "charts"')
|
||||
args = parser.parse_args()
|
||||
|
||||
# 解析数据
|
||||
print(f"正在解析数据文件: {args.file_path}")
|
||||
df = parse_temperature_data(args.file_path)
|
||||
|
||||
if df is not None and not df.empty:
|
||||
print("数据解析成功,包含以下温度点:", ", ".join(df.columns))
|
||||
print(f"时间范围: {df.index.min()} 至 {df.index.max()}")
|
||||
print(f"记录总数: {len(df)}")
|
||||
|
||||
# 生成图表
|
||||
generate_temperature_charts(df, args.output)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
196
src/main.py
Normal file
196
src/main.py
Normal file
@ -0,0 +1,196 @@
|
||||
import sys
|
||||
import os
|
||||
from PyQt5.QtWidgets import (QApplication, QMainWindow, QFileDialog, QTextBrowser,
|
||||
QMessageBox, QTextEdit, QGraphicsScene, QGraphicsPixmapItem)
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QPixmap
|
||||
from PyQt5.QtGui import QPainter # 单独导入QPainter用于抗锯齿设置
|
||||
from MainWindow_ui import Ui_MainWindow # 导入转换后的UI类
|
||||
import service
|
||||
|
||||
|
||||
class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
def __init__(self):
|
||||
super().__init__() # 初始化父类
|
||||
self.setupUi(self) # 调用UI类的方法初始化界面
|
||||
self.init_events() # 绑定事件(自定义方法)
|
||||
self.parseStatus = service.ServiceStatus()
|
||||
self.init_graphic_views()
|
||||
self.init_textbrowser_style()
|
||||
|
||||
self.comboBoxTextDict = {
|
||||
"ALL_Temp": "all",
|
||||
"CPU_Temp": "cpu",
|
||||
"FPGA_Temp": "fpga",
|
||||
"DIMM_Temp": "dimm",
|
||||
"Inlet_CPU_Temp": "inlet_cpu",
|
||||
"Inlet_FPGA_Temp": "inlet_fpga",
|
||||
"M2_Temp": "m2",
|
||||
"Outlet_CPU_Temp": "outlet_cpu",
|
||||
"Outlet_FPGA_Temp": "outlet_fpga",
|
||||
"VR_CPU_Temp": "vr_cpu",
|
||||
"VR_FPGA_Temp": "vr_fpga"
|
||||
}
|
||||
|
||||
def init_textbrowser_style(self):
|
||||
# 设置样式表(全局文本样式)
|
||||
self.textBrowser_info.setStyleSheet("""
|
||||
QTextBrowser {
|
||||
font-family: 'SimHei';
|
||||
font-size: 14px;
|
||||
color: #2c3e50;
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
}
|
||||
""")
|
||||
|
||||
def init_events(self):
|
||||
"""绑定界面元素的事件(如按钮点击、输入框变化等)"""
|
||||
# 示例:给按钮添加点击事件
|
||||
# self.pushButton.clicked.connect(self.on_button_click) # pushButton是UI中定义的按钮对象名
|
||||
|
||||
# 绑定上传菜单项事件
|
||||
self.actionUpload_log.triggered.connect(self.upload_file)
|
||||
self.comboBox.currentTextChanged.connect(self.on_combo_changed)
|
||||
|
||||
def init_graphic_views(self):
|
||||
# 初始化图形场景和视图
|
||||
self.scene = QGraphicsScene(self) # 创建场景
|
||||
self.graphicsView.setScene(self.scene) # 将场景绑定到视图
|
||||
self.graphicsView.setRenderHint(QPainter.Antialiasing) # 抗锯齿
|
||||
self.graphicsView.setRenderHint(QPainter.SmoothPixmapTransform) # 平滑缩放
|
||||
|
||||
# 初始化图形场景和视图
|
||||
# self.scene_temp_cpu = QGraphicsScene(self) # 创建场景
|
||||
# self.graphicsView_temp_cpu.setScene(self.scene_temp_cpu) # 将场景绑定到视图
|
||||
# self.graphicsView_temp_cpu.setRenderHint(QPainter.Antialiasing) # 抗锯齿
|
||||
# self.graphicsView_temp_cpu.setRenderHint(QPainter.SmoothPixmapTransform) # 平滑缩放
|
||||
|
||||
# 存储当前显示的图片项
|
||||
self.pixmap_item = None
|
||||
self.current_image_path = None
|
||||
|
||||
def upload_file(self):
|
||||
"""打开文件选择对话框并处理选中的文件"""
|
||||
# 打开文件选择对话框
|
||||
# 参数说明:
|
||||
# - self:父窗口
|
||||
# - "选择文件":对话框标题
|
||||
# - os.getcwd():默认打开路径(当前工作目录)
|
||||
# - "所有文件 (*);;文本文件 (*.txt);;图片文件 (*.png *.jpg)":文件筛选器
|
||||
file_path, file_type = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"选择文件",
|
||||
os.getcwd(),
|
||||
"所有文件 (*);;日志压缩文件 (*.gz)"
|
||||
)
|
||||
|
||||
# 判断用户是否选择了文件(取消选择时file_path为空)
|
||||
if file_path:
|
||||
self.process_uploaded_file(file_path)
|
||||
# else:
|
||||
# self.text_edit.append("已取消文件选择")
|
||||
|
||||
def process_uploaded_file(self, file_path):
|
||||
"""处理上传的文件(这里仅展示文件信息,可根据需求扩展)"""
|
||||
try:
|
||||
# 获取文件信息
|
||||
file_name = os.path.basename(file_path)
|
||||
file_size = os.path.getsize(file_path)
|
||||
file_size_kb = file_size / 1024 # 转换为KB
|
||||
|
||||
# 显示文件信息
|
||||
info = f"已上传文件:\n"
|
||||
info += f"文件名:{file_name}\n"
|
||||
info += f"路径:{file_path}\n"
|
||||
info += f"大小:{file_size_kb:.2f} KB\n"
|
||||
info += "-" * 50 + "\n"
|
||||
|
||||
self.textBrowser_console.insertPlainText(info)
|
||||
|
||||
# 这里可以添加实际的文件处理逻辑:
|
||||
service.send_log_to_cache(file_path)
|
||||
service.start_diagnose(self.parseStatus)
|
||||
|
||||
if self.parseStatus.baseinfo_status:
|
||||
baseinfo_str = service.get_baseinfo_str()
|
||||
self.textBrowser_info.insertPlainText(baseinfo_str)
|
||||
|
||||
if self.parseStatus.sensorhistory_status:
|
||||
self.display_pic(service.get_sensorhistory_path("all"))
|
||||
|
||||
self.textBrowser_console.insertPlainText("完成文件解析\n")
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "处理失败", f"文件处理出错:{str(e)}")
|
||||
|
||||
def on_combo_changed(self, selected_text):
|
||||
"""下拉列表选项变化时切换图片"""
|
||||
self.display_pic(service.get_sensorhistory_path(self.comboBoxTextDict[selected_text]))
|
||||
|
||||
|
||||
def display_pic(self, image_path):
|
||||
"""在QGraphicsView中显示图片"""
|
||||
try:
|
||||
# 清空场景中已有的内容
|
||||
self.scene.clear()
|
||||
|
||||
# 加载图片并创建图形项
|
||||
pixmap = QPixmap(image_path)
|
||||
if pixmap.isNull():
|
||||
raise Exception("无法加载图片文件")
|
||||
|
||||
# 创建图片项并添加到场景
|
||||
self.pixmap_item = QGraphicsPixmapItem(pixmap)
|
||||
self.scene.addItem(self.pixmap_item)
|
||||
|
||||
# 初始显示时让图片居中
|
||||
self.graphicsView.fitInView(self.scene.sceneRect(), Qt.KeepAspectRatio)
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"显示图片失败:{str(e)}")
|
||||
|
||||
def zoom_with_mouse_wheel(self, event):
|
||||
"""鼠标滚轮缩放图片"""
|
||||
if self.pixmap_item: # 只有存在图片时才允许缩放
|
||||
# 获取当前鼠标位置作为缩放中心
|
||||
mouse_pos = event.pos()
|
||||
scene_pos = self.graphicsView.mapToScene(mouse_pos)
|
||||
|
||||
# 缩放因子(滚轮向前放大,向后缩小)
|
||||
scale_factor = 1.1 if event.angleDelta().y() > 0 else 0.9
|
||||
|
||||
# 缩放视图
|
||||
self.graphicsView.scale(scale_factor, scale_factor)
|
||||
|
||||
# 缩放后将鼠标位置保持在原场景位置(避免缩放时画面跳动)
|
||||
new_mouse_pos = self.graphicsView.mapFromScene(scene_pos)
|
||||
delta = new_mouse_pos - mouse_pos
|
||||
self.graphicsView.horizontalScrollBar().setValue(
|
||||
self.graphicsView.horizontalScrollBar().value() - delta.x()
|
||||
)
|
||||
self.graphicsView.verticalScrollBar().setValue(
|
||||
self.graphicsView.verticalScrollBar().value() - delta.y()
|
||||
)
|
||||
else:
|
||||
# 若无图片,忽略滚轮事件
|
||||
super(type(self.graphicsView), self.graphicsView).wheelEvent(event)
|
||||
|
||||
def resizeEvent(self, event):
|
||||
"""窗口大小改变时,自适应调整图片显示"""
|
||||
if self.pixmap_item and not self.graphicsView.transform().isIdentity():
|
||||
# 仅在未手动缩放时自动适应窗口
|
||||
self.graphicsView.fitInView(self.scene.sceneRect(), Qt.KeepAspectRatio)
|
||||
super().resizeEvent(event)
|
||||
|
||||
if __name__ == "__main__":
|
||||
service.app_cache_init()
|
||||
# 创建应用实例
|
||||
app = QApplication(sys.argv)
|
||||
# 创建主窗口并显示
|
||||
window = MainWindow()
|
||||
window.show()
|
||||
# 进入应用主循环
|
||||
sys.exit(app.exec_())
|
||||
48
src/service.py
Normal file
48
src/service.py
Normal file
@ -0,0 +1,48 @@
|
||||
import sys
|
||||
import os
|
||||
import utils
|
||||
import ZiJin_parse_sensorhistory as sensorparse
|
||||
import ZiJin_parse_baseinfo as baseinfo
|
||||
|
||||
class ServiceStatus():
|
||||
def __init__(self):
|
||||
self.sensorhistory_status = False
|
||||
self.baseinfo_status = False
|
||||
|
||||
def set_sensorhistory_status(self, status):
|
||||
self.sensorhistory_status = status
|
||||
|
||||
def get_sensorhistory_status(self):
|
||||
return self.sensorhistory_status
|
||||
|
||||
def set_baseinfo_status(self, status):
|
||||
self.baseinfo_status = status
|
||||
|
||||
def get_baseinfo_status(self):
|
||||
return self.baseinfo_status
|
||||
|
||||
def app_cache_init():
|
||||
project_root = utils.get_project_root()
|
||||
cache_dir = os.path.join(project_root, "tmp")
|
||||
utils.clean_log_data(cache_dir)
|
||||
|
||||
os.mkdir(cache_dir)
|
||||
pass
|
||||
|
||||
def send_log_to_cache(filepath):
|
||||
project_root = utils.get_project_root()
|
||||
cache_dir = os.path.join(project_root, "tmp")
|
||||
utils.unzip_log(filepath, cache_dir)
|
||||
|
||||
def get_sensorhistory_path(type):
|
||||
return sensorparse.get_sensor_all_image_path(type)
|
||||
|
||||
def get_baseinfo_str():
|
||||
return baseinfo.get_all_infostring()
|
||||
|
||||
def start_diagnose(parseStatus):
|
||||
result_sensorhistory = sensorparse.program_main()
|
||||
parseStatus.set_sensorhistory_status(result_sensorhistory)
|
||||
|
||||
result_baseinfo = baseinfo.program_main()
|
||||
parseStatus.set_baseinfo_status(result_baseinfo)
|
||||
136
src/utils.py
Normal file
136
src/utils.py
Normal file
@ -0,0 +1,136 @@
|
||||
import os
|
||||
import tarfile
|
||||
import shutil
|
||||
import stat
|
||||
|
||||
def get_project_root():
|
||||
current_file = os.path.abspath(__file__)
|
||||
project_root = os.path.dirname(os.path.dirname(current_file))
|
||||
return project_root
|
||||
|
||||
def unzip_log(tar_path, extract_path='.'):
|
||||
"""
|
||||
解压 tar.gz 文件到指定目录
|
||||
|
||||
参数:
|
||||
tar_path (str): tar.gz 文件的路径
|
||||
extract_path (str): 解压目标目录,默认为当前目录
|
||||
"""
|
||||
try:
|
||||
# 检查文件是否存在
|
||||
if not os.path.exists(tar_path):
|
||||
raise FileNotFoundError(f"文件不存在: {tar_path}")
|
||||
|
||||
# 创建解压目录(如果不存在)
|
||||
os.makedirs(extract_path, exist_ok=True)
|
||||
|
||||
# 打开 tar.gz 文件并解压
|
||||
with tarfile.open(tar_path, "r:gz") as tar:
|
||||
# 列出所有文件(可选)
|
||||
print(f"解压文件列表:")
|
||||
for member in tar.getmembers():
|
||||
print(f"- {member.name}")
|
||||
|
||||
# 解压所有文件到目标目录
|
||||
tar.extractall(path=extract_path)
|
||||
print(f"\n成功解压到: {os.path.abspath(extract_path)}")
|
||||
|
||||
except tarfile.TarError as e:
|
||||
print(f"tar 文件处理错误: {e}")
|
||||
except Exception as e:
|
||||
print(f"解压失败: {e}")
|
||||
|
||||
def remove_readonly(func, path, excinfo):
|
||||
"""用于处理删除只读文件的错误回调函数"""
|
||||
# 尝试修改文件权限
|
||||
os.chmod(path, stat.S_IWRITE)
|
||||
# 再次尝试删除
|
||||
func(path)
|
||||
|
||||
def clean_log_data(path):
|
||||
"""删除目录,处理权限问题"""
|
||||
if not os.path.exists(path):
|
||||
print(f"目录不存在: {path}")
|
||||
return
|
||||
|
||||
try:
|
||||
# 方法1: 使用onerror回调处理权限问题
|
||||
shutil.rmtree(path, onerror=remove_readonly)
|
||||
print(f"成功删除目录: {path}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"删除目录时出错: {e}")
|
||||
# 方法2: 先递归修改权限再删除(备选方案)
|
||||
try:
|
||||
# 递归修改目录权限
|
||||
for root, dirs, files in os.walk(path):
|
||||
for dir in dirs:
|
||||
dir_path = os.path.join(root, dir)
|
||||
os.chmod(dir_path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
|
||||
for file in files:
|
||||
file_path = os.path.join(root, file)
|
||||
os.chmod(file_path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
|
||||
|
||||
# 修改权限后再次尝试删除
|
||||
shutil.rmtree(path)
|
||||
print(f"通过修改权限成功删除目录: {path}")
|
||||
except Exception as e2:
|
||||
print(f"修改权限后仍无法删除目录: {e2}")
|
||||
|
||||
def read_specific_line(file_path, line_number):
|
||||
"""
|
||||
读取文件中指定行的内容(行号从1开始)
|
||||
"""
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as file:
|
||||
for current_line, content in enumerate(file, 1): # 从1开始计数
|
||||
if current_line == line_number:
|
||||
return content.strip() # strip() 去除换行符和空格
|
||||
# 若行号超出文件总行数,返回None
|
||||
return None
|
||||
except FileNotFoundError:
|
||||
print(f"错误:文件 '{file_path}' 不存在")
|
||||
return None
|
||||
|
||||
def append_to_file(file_path, content):
|
||||
"""
|
||||
以追加模式将字符串写入文件
|
||||
|
||||
参数:
|
||||
file_path (str): 文件路径
|
||||
content (str): 要写入的字符串内容
|
||||
"""
|
||||
try:
|
||||
# 打开文件,模式为 'a'(追加),编码指定为 utf-8 以支持中文
|
||||
with open(file_path, 'a', encoding='utf-8') as file:
|
||||
# 写入内容(可根据需要添加换行符 '\n')
|
||||
file.write(content + '\n') # 加 '\n' 使每次写入占一行
|
||||
print(f"内容已成功追加到文件:{file_path}")
|
||||
except Exception as e:
|
||||
print(f"写入文件失败:{str(e)}")
|
||||
|
||||
def read_file_to_string(file_path):
|
||||
"""
|
||||
打开文件并将全部内容读取到一个字符串中
|
||||
|
||||
参数:
|
||||
file_path (str): 要读取的文件路径
|
||||
返回:
|
||||
str: 文件内容字符串;若读取失败则返回 None
|
||||
"""
|
||||
try:
|
||||
# 使用 with 语句打开文件(自动处理关闭)
|
||||
# 'r' 表示只读模式,encoding='utf-8' 确保中文正常读取
|
||||
with open(file_path, 'r', encoding='utf-8') as file:
|
||||
# read() 方法读取全部内容并返回字符串
|
||||
content = file.read()
|
||||
return content
|
||||
except FileNotFoundError:
|
||||
print(f"错误:文件 '{file_path}' 不存在")
|
||||
except PermissionError:
|
||||
print(f"错误:没有权限读取文件 '{file_path}'")
|
||||
except UnicodeDecodeError:
|
||||
print(f"错误:文件 '{file_path}' 不是 UTF-8 编码,无法读取")
|
||||
except Exception as e:
|
||||
print(f"读取文件失败:{str(e)}")
|
||||
return None
|
||||
Loading…
Reference in New Issue
Block a user