init repo

This commit is contained in:
leimingsheng 2025-08-21 18:19:04 +08:00
commit 31a6a0b255
9 changed files with 1053 additions and 0 deletions

3
.gitignore vendored Normal file

@ -0,0 +1,3 @@
tmp/
src/__pycache__/
log.tar.gz

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>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;通过文件上传一键日志压缩包来开始&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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

@ -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"))

@ -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

@ -0,0 +1 @@
import os

@ -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

@ -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

@ -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

@ -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