openubmc-bingo 0.6.45__py3-none-any.whl → 0.6.99__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bmcgo/__init__.py +1 -1
- bmcgo/bmcgo.py +9 -3
- bmcgo/bmcgo_config.py +16 -0
- bmcgo/cli/cli.py +72 -21
- bmcgo/codegen/__init__.py +1 -1
- bmcgo/codegen/lua/codegen.py +2 -2
- bmcgo/codegen/lua/script/check_intfs.py +1 -0
- bmcgo/codegen/lua/script/dto/options.py +1 -0
- bmcgo/codegen/lua/script/gen_db_json.py +4 -3
- bmcgo/codegen/lua/script/gen_rpc_msg_json.py +78 -11
- bmcgo/codegen/lua/script/model_consistency_check.py +1 -1
- bmcgo/codegen/lua/script/render_utils/db_lua.py +5 -6
- bmcgo/codegen/lua/script/render_utils/model_lua.py +5 -1
- bmcgo/codegen/lua/script/template.py +5 -0
- bmcgo/codegen/lua/script/utils.py +50 -8
- bmcgo/codegen/lua/templates/apps/Makefile +2 -2
- bmcgo/codegen/lua/templates/apps/client.lua.mako +1 -1
- bmcgo/codegen/lua/templates/apps/model.lua.mako +4 -3
- bmcgo/codegen/lua/templates/apps/service.lua.mako +1 -1
- bmcgo/codegen/lua/templates/apps/utils/mdb_intf.lua.mako +4 -0
- bmcgo/codegen/lua/templates/new_app_v2/CMakeLists.txt.mako +26 -0
- bmcgo/codegen/lua/templates/new_app_v2/conanfile.py.mako +9 -0
- bmcgo/codegen/lua/v1/script/render_utils/db_lua.py +5 -6
- bmcgo/codegen/lua/v1/script/render_utils/model_lua.py +13 -1
- bmcgo/codegen/lua/v1/templates/apps/client.lua.mako +1 -1
- bmcgo/codegen/lua/v1/templates/apps/local_db.lua.mako +0 -4
- bmcgo/codegen/lua/v1/templates/apps/message.lua.mako +3 -0
- bmcgo/codegen/lua/v1/templates/apps/model.lua.mako +3 -0
- bmcgo/codegen/lua/v1/templates/apps/utils/mdb_intf.lua.mako +6 -4
- bmcgo/component/analysis/analysis.py +9 -4
- bmcgo/component/analysis/dep-rules.json +20 -8
- bmcgo/component/analysis/dep_node.py +2 -0
- bmcgo/component/analysis/intf_validation.py +8 -7
- bmcgo/component/analysis/sr_validation.py +5 -4
- bmcgo/component/busctl_log_parse/busctl_log_parser.py +809 -0
- bmcgo/component/busctl_log_parse/mock_data_save.py +170 -0
- bmcgo/component/busctl_log_parse/test_data_save.py +49 -0
- bmcgo/component/component_helper.py +29 -0
- bmcgo/component/coverage/incremental_cov.py +5 -0
- bmcgo/component/fixture/__init__.py +29 -0
- bmcgo/component/fixture/auto_case_generator.py +490 -0
- bmcgo/component/fixture/busctl_type_converter.py +1081 -0
- bmcgo/component/fixture/common_config.py +15 -0
- bmcgo/component/fixture/dbus_gateway.py +669 -0
- bmcgo/component/fixture/dbus_library.py +250 -0
- bmcgo/component/fixture/dbus_mock_utils.py +514 -0
- bmcgo/component/fixture/dbus_response_handler.py +138 -0
- bmcgo/component/fixture/dbus_signature.py +110 -0
- bmcgo/component/template_v2/conanbase.py.mako +1 -5
- bmcgo/component/test.py +69 -10
- bmcgo/error_analyzer/__init__.py +0 -0
- bmcgo/error_analyzer/case_matcher.py +114 -0
- bmcgo/error_analyzer/log_parser.py +128 -0
- bmcgo/error_analyzer/unified_error_analyzer.py +359 -0
- bmcgo/error_cases/cases.yml +59 -0
- bmcgo/error_cases/cases_template_valid.json +71 -0
- bmcgo/error_cases/conanfile.py +58 -0
- bmcgo/frame.py +0 -4
- bmcgo/functional/analysis.py +18 -12
- bmcgo/functional/bmc_studio_action.py +21 -10
- bmcgo/functional/check.py +86 -42
- bmcgo/functional/conan_index_build.py +1 -1
- bmcgo/functional/config.py +22 -18
- bmcgo/functional/csr_build.py +63 -34
- bmcgo/functional/deploy.py +4 -3
- bmcgo/functional/diff.py +51 -34
- bmcgo/functional/full_component.py +16 -5
- bmcgo/functional/hpm_signer.py +484 -0
- bmcgo/functional/new.py +8 -2
- bmcgo/functional/schema_valid.py +111 -15
- bmcgo/functional/upgrade.py +6 -6
- bmcgo/misc.py +1 -0
- bmcgo/tasks/task_build_conan.py +27 -6
- bmcgo/tasks/task_build_rootfs_img.py +120 -83
- bmcgo/tasks/task_buildgppbin.py +30 -13
- bmcgo/tasks/task_buildhpm_ext4.py +5 -3
- bmcgo/tasks/task_download_buildtools.py +20 -11
- bmcgo/tasks/task_download_dependency.py +29 -20
- bmcgo/tasks/task_hpm_envir_prepare.py +32 -53
- bmcgo/tasks/task_packet_to_supporte.py +12 -4
- bmcgo/tasks/task_prepare.py +1 -1
- bmcgo/tasks/task_sign_and_pack_hpm.py +15 -7
- bmcgo/utils/component_version_check.py +4 -4
- bmcgo/utils/config.py +3 -0
- bmcgo/utils/fetch_component_code.py +148 -17
- bmcgo/utils/install_manager.py +2 -2
- bmcgo/utils/installations/base_installer.py +10 -27
- bmcgo/utils/installations/install_plans/studio.yml +3 -0
- bmcgo/utils/mapping_config_patch.py +5 -4
- bmcgo/utils/tools.py +49 -7
- {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/METADATA +1 -1
- {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/RECORD +95 -74
- bmcgo/tasks/download_buildtools_hm.py +0 -124
- {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/WHEEL +0 -0
- {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/entry_points.txt +0 -0
- {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
# coding: utf-8
|
|
3
|
+
# Copyright (c) 2024 Huawei Technologies Co., Ltd.
|
|
4
|
+
# openUBMC is licensed under Mulan PSL v2.
|
|
5
|
+
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
|
6
|
+
# You may obtain a copy of Mulan PSL v2 at:
|
|
7
|
+
# http://license.coscl.org.cn/MulanPSL2
|
|
8
|
+
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
|
9
|
+
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
10
|
+
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
11
|
+
# See the Mulan PSL v2 for more details.
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import glob
|
|
15
|
+
import logging
|
|
16
|
+
|
|
17
|
+
from .log_parser import LogParser
|
|
18
|
+
from .case_matcher import CaseMatcher
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class UnifiedErrorAnalyzer:
|
|
22
|
+
"""统一的错误分析器 - 结合命令失败和日志分析"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, cases):
|
|
25
|
+
self.log_parser = LogParser()
|
|
26
|
+
self.case_matcher = CaseMatcher()
|
|
27
|
+
self.cases = cases
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def _find_log_files_in_directory(directory, recursive=True):
|
|
31
|
+
"""
|
|
32
|
+
在目录中查找日志文件
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
directory: 目录路径
|
|
36
|
+
recursive: 是否递归查找子目录
|
|
37
|
+
"""
|
|
38
|
+
log_files = []
|
|
39
|
+
|
|
40
|
+
if recursive:
|
|
41
|
+
# 递归查找所有 .log 文件
|
|
42
|
+
pattern = os.path.join(directory, "**", "*.log")
|
|
43
|
+
log_files = glob.glob(pattern, recursive=True)
|
|
44
|
+
else:
|
|
45
|
+
# 只查找当前目录下的 .log 文件
|
|
46
|
+
pattern = os.path.join(directory, "*.log")
|
|
47
|
+
log_files = glob.glob(pattern)
|
|
48
|
+
|
|
49
|
+
# 也可以查找其他常见的日志文件扩展名
|
|
50
|
+
additional_patterns = [
|
|
51
|
+
os.path.join(directory, "**", "*.log.*"), # 滚动日志文件
|
|
52
|
+
os.path.join(directory, "**", "*.txt"), # 文本日志文件
|
|
53
|
+
os.path.join(directory, "**", "*.err"), # 错误日志文件
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
for pattern in additional_patterns:
|
|
57
|
+
log_files.extend(glob.glob(pattern, recursive=True))
|
|
58
|
+
|
|
59
|
+
# 去重并返回
|
|
60
|
+
return list(set(log_files))
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def _extract_error_code_from_failure(failure):
|
|
64
|
+
"""从失败信息中提取错误代码"""
|
|
65
|
+
error = failure.get("error", "")
|
|
66
|
+
error_type = failure.get("error_type", "")
|
|
67
|
+
|
|
68
|
+
# 根据错误类型和内容生成错误代码
|
|
69
|
+
if "TimeoutExpired" in error_type:
|
|
70
|
+
return "COMMAND_TIMEOUT"
|
|
71
|
+
elif "CalledProcessError" in error_type:
|
|
72
|
+
return "COMMAND_FAILED"
|
|
73
|
+
elif "FileNotFoundError" in error_type:
|
|
74
|
+
return "COMMAND_NOT_FOUND"
|
|
75
|
+
elif "PermissionError" in error_type:
|
|
76
|
+
return "PERMISSION_DENIED"
|
|
77
|
+
else:
|
|
78
|
+
return "COMMAND_ERROR"
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def _format_failure_log(failure):
|
|
82
|
+
"""格式化失败日志"""
|
|
83
|
+
parts = []
|
|
84
|
+
|
|
85
|
+
if failure.get("command_key"):
|
|
86
|
+
parts.append(f"[{failure['command_key']}]")
|
|
87
|
+
|
|
88
|
+
parts.append(failure["command_str"])
|
|
89
|
+
parts.append("[FAILED]")
|
|
90
|
+
|
|
91
|
+
exec_time = failure.get("execution_time", 0)
|
|
92
|
+
parts.append(f"[{exec_time:.2f}s]")
|
|
93
|
+
|
|
94
|
+
if "error" in failure:
|
|
95
|
+
parts.append(f"[ERROR: {failure['error']}]")
|
|
96
|
+
|
|
97
|
+
return " ".join(parts)
|
|
98
|
+
|
|
99
|
+
@staticmethod
|
|
100
|
+
def _deduplicate_cases(cases):
|
|
101
|
+
"""案例去重"""
|
|
102
|
+
seen_signatures = set()
|
|
103
|
+
unique_cases = []
|
|
104
|
+
|
|
105
|
+
for case in cases:
|
|
106
|
+
# 创建更严格的案例签名
|
|
107
|
+
title = case.get("title", "")
|
|
108
|
+
|
|
109
|
+
# 使用原始日志内容(去除ANSI颜色码)进行去重
|
|
110
|
+
raw_log = case["log_data"].get("raw_log", "")
|
|
111
|
+
|
|
112
|
+
# 去除ANSI颜色码
|
|
113
|
+
import re
|
|
114
|
+
|
|
115
|
+
ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
|
|
116
|
+
clean_log = ansi_escape.sub("", raw_log).strip()
|
|
117
|
+
|
|
118
|
+
# 创建基于标题和清理后日志内容的签名
|
|
119
|
+
signature = f"{title}_{hash(clean_log)}"
|
|
120
|
+
|
|
121
|
+
if signature not in seen_signatures:
|
|
122
|
+
seen_signatures.add(signature)
|
|
123
|
+
unique_cases.append(case)
|
|
124
|
+
|
|
125
|
+
return unique_cases
|
|
126
|
+
|
|
127
|
+
@staticmethod
|
|
128
|
+
def _output_unified_case(case, case_number):
|
|
129
|
+
"""输出统一格式的案例"""
|
|
130
|
+
source = case["log_data"].get("source", "unknown")
|
|
131
|
+
if source == "log_file":
|
|
132
|
+
source_icon = "📁"
|
|
133
|
+
source_text = "日志文件"
|
|
134
|
+
file_path = case["log_data"].get("file_path", "unknown")
|
|
135
|
+
file_name = file_path
|
|
136
|
+
else:
|
|
137
|
+
source_icon = "🔧"
|
|
138
|
+
source_text = "命令执行"
|
|
139
|
+
file_name = None
|
|
140
|
+
|
|
141
|
+
logging.info(
|
|
142
|
+
f"{source_icon} 案例 {case_number}: {case.get('title', '未命名案例')}"
|
|
143
|
+
)
|
|
144
|
+
logging.info("─" * 50)
|
|
145
|
+
|
|
146
|
+
logging.info(f" 🕐 发生时间: {case['log_data']['timestamp']}")
|
|
147
|
+
logging.info(f" 📍 来源: {source_text}")
|
|
148
|
+
|
|
149
|
+
if file_name:
|
|
150
|
+
logging.info(f" 📄 文件: {file_path}")
|
|
151
|
+
|
|
152
|
+
logging.info(f" 📝 问题描述: {case.get('description', '')}")
|
|
153
|
+
|
|
154
|
+
if case.get("steps"):
|
|
155
|
+
logging.info(" 👣 重现步骤:")
|
|
156
|
+
for j, step in enumerate(case.get("steps", []), 1):
|
|
157
|
+
logging.info(f" {j}. {step}")
|
|
158
|
+
|
|
159
|
+
logging.info(" 📄 相关输出:")
|
|
160
|
+
logging.info(f" {case['log_data']['raw_log']}")
|
|
161
|
+
|
|
162
|
+
# 如果是命令失败案例,显示额外信息
|
|
163
|
+
if source == "command_failure" and "command_data" in case:
|
|
164
|
+
cmd_data = case["command_data"]
|
|
165
|
+
logging.info(" 🔧 命令详情:")
|
|
166
|
+
logging.info(f" 命令: {cmd_data['command_str']}")
|
|
167
|
+
logging.info(f" 执行时间: {cmd_data['execution_time']:.2f}秒")
|
|
168
|
+
logging.info(f" 错误类型: {cmd_data.get('error_type', 'Unknown')}")
|
|
169
|
+
|
|
170
|
+
if case.get("solution"):
|
|
171
|
+
logging.info(f" 💡 解决方案: {case['solution']}")
|
|
172
|
+
|
|
173
|
+
logging.info("─" * 50)
|
|
174
|
+
|
|
175
|
+
def analyze_errors(self, log_sources, command_failures=None):
|
|
176
|
+
"""
|
|
177
|
+
统一分析错误:支持多种日志源
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
log_sources: 可以是以下类型:
|
|
181
|
+
- 单个文件路径 (str)
|
|
182
|
+
- 文件路径列表 (list)
|
|
183
|
+
- 文件夹路径 (str) - 会分析该文件夹下所有 .log 文件
|
|
184
|
+
command_failures: 命令失败信息列表
|
|
185
|
+
"""
|
|
186
|
+
logging.info("\n" + "=" * 60)
|
|
187
|
+
logging.info("🔍 开始统一错误分析")
|
|
188
|
+
logging.info("=" * 60)
|
|
189
|
+
|
|
190
|
+
# 解析日志源
|
|
191
|
+
log_files = self._resolve_log_sources(log_sources)
|
|
192
|
+
|
|
193
|
+
if not log_files:
|
|
194
|
+
logging.warning("❌ 未找到任何日志文件")
|
|
195
|
+
return []
|
|
196
|
+
|
|
197
|
+
all_cases = []
|
|
198
|
+
all_log_entries = []
|
|
199
|
+
|
|
200
|
+
# 1. 分析所有日志文件
|
|
201
|
+
for log_file in log_files:
|
|
202
|
+
file_cases, file_entries = self._analyze_single_log_file(log_file)
|
|
203
|
+
all_cases.extend(file_cases)
|
|
204
|
+
all_log_entries.extend(file_entries)
|
|
205
|
+
|
|
206
|
+
# 2. 分析命令失败信息
|
|
207
|
+
if command_failures:
|
|
208
|
+
command_cases = self._analyze_command_failures(command_failures)
|
|
209
|
+
all_cases.extend(command_cases)
|
|
210
|
+
|
|
211
|
+
# 3. 合并和去重
|
|
212
|
+
unique_cases = self._deduplicate_cases(all_cases)
|
|
213
|
+
|
|
214
|
+
# 4. 输出分析结果
|
|
215
|
+
self._output_unified_analysis(
|
|
216
|
+
unique_cases, log_files, command_failures, all_log_entries
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
return unique_cases
|
|
220
|
+
|
|
221
|
+
def _resolve_log_sources(self, log_sources):
|
|
222
|
+
"""
|
|
223
|
+
解析日志源,返回文件路径列表
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
log_sources: 单个文件路径、文件列表或文件夹路径
|
|
227
|
+
"""
|
|
228
|
+
if isinstance(log_sources, str):
|
|
229
|
+
# 单个路径
|
|
230
|
+
if os.path.isfile(log_sources):
|
|
231
|
+
# 单个文件
|
|
232
|
+
return [log_sources]
|
|
233
|
+
elif os.path.isdir(log_sources):
|
|
234
|
+
# 文件夹 - 查找所有 .log 文件
|
|
235
|
+
return self._find_log_files_in_directory(log_sources)
|
|
236
|
+
else:
|
|
237
|
+
# 可能是通配符模式
|
|
238
|
+
return glob.glob(log_sources)
|
|
239
|
+
|
|
240
|
+
elif isinstance(log_sources, list):
|
|
241
|
+
# 文件列表
|
|
242
|
+
all_files = []
|
|
243
|
+
for source in log_sources:
|
|
244
|
+
if os.path.isfile(source):
|
|
245
|
+
all_files.append(source)
|
|
246
|
+
elif os.path.isdir(source):
|
|
247
|
+
all_files.extend(self._find_log_files_in_directory(source))
|
|
248
|
+
else:
|
|
249
|
+
# 通配符模式
|
|
250
|
+
all_files.extend(glob.glob(source))
|
|
251
|
+
return all_files
|
|
252
|
+
|
|
253
|
+
else:
|
|
254
|
+
return []
|
|
255
|
+
|
|
256
|
+
def _analyze_single_log_file(self, log_file_path):
|
|
257
|
+
"""分析单个日志文件"""
|
|
258
|
+
try:
|
|
259
|
+
log_entries = self.log_parser.parse_logs(log_file_path)
|
|
260
|
+
cases = self.cases
|
|
261
|
+
matched_cases = self.case_matcher.match_cases(log_entries, cases)
|
|
262
|
+
|
|
263
|
+
# 标记来源文件
|
|
264
|
+
for case in matched_cases:
|
|
265
|
+
case["log_data"]["source"] = "log_file"
|
|
266
|
+
case["log_data"]["file_path"] = log_file_path
|
|
267
|
+
|
|
268
|
+
return matched_cases, log_entries
|
|
269
|
+
|
|
270
|
+
except Exception as e:
|
|
271
|
+
logging.error(f" ❌ 分析文件失败 {log_file_path}: {e}")
|
|
272
|
+
return [], []
|
|
273
|
+
|
|
274
|
+
def _analyze_command_failures(self, command_failures):
|
|
275
|
+
"""分析命令失败信息"""
|
|
276
|
+
cases = self.cases
|
|
277
|
+
matched_cases = []
|
|
278
|
+
|
|
279
|
+
for failure in command_failures:
|
|
280
|
+
# 将命令失败信息转换为日志格式进行分析
|
|
281
|
+
log_entry = self._convert_failure_to_log_entry(failure)
|
|
282
|
+
|
|
283
|
+
# 匹配案例
|
|
284
|
+
case = self.case_matcher.find_matching_case(log_entry, cases)
|
|
285
|
+
if case:
|
|
286
|
+
enriched_case = self.case_matcher.enrich_case_with_log(case, log_entry)
|
|
287
|
+
enriched_case["command_data"] = failure # 保留原始命令数据
|
|
288
|
+
matched_cases.append(enriched_case)
|
|
289
|
+
|
|
290
|
+
return matched_cases
|
|
291
|
+
|
|
292
|
+
def _convert_failure_to_log_entry(self, failure):
|
|
293
|
+
"""将命令失败信息转换为日志条目格式"""
|
|
294
|
+
return {
|
|
295
|
+
"timestamp": failure["timestamp"],
|
|
296
|
+
"level": "ERROR",
|
|
297
|
+
"module": "CommandExecutor",
|
|
298
|
+
"message": f"Command failed: {failure['command_str']} - {failure['error']}",
|
|
299
|
+
"error_code": self._extract_error_code_from_failure(failure),
|
|
300
|
+
"parameters": {
|
|
301
|
+
"command": failure["command_str"],
|
|
302
|
+
"execution_time": failure["execution_time"],
|
|
303
|
+
"error_type": failure["error_type"],
|
|
304
|
+
},
|
|
305
|
+
"raw_line": self._format_failure_log(failure),
|
|
306
|
+
"source": "command_failure",
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
def _load_cases(self):
|
|
310
|
+
"""加载案例模板"""
|
|
311
|
+
import yaml
|
|
312
|
+
|
|
313
|
+
try:
|
|
314
|
+
with open(self.case_file_path, "r", encoding="utf-8") as f:
|
|
315
|
+
data = yaml.safe_load(f)
|
|
316
|
+
return data.get("cases", [])
|
|
317
|
+
except Exception as e:
|
|
318
|
+
logging.error(f"❌ 加载案例文件失败: {e}")
|
|
319
|
+
return []
|
|
320
|
+
|
|
321
|
+
def _output_unified_analysis(
|
|
322
|
+
self, cases, log_files, command_failures, all_log_entries
|
|
323
|
+
):
|
|
324
|
+
"""输出统一分析结果"""
|
|
325
|
+
if not cases:
|
|
326
|
+
logging.warning("\n✅ 没有发现匹配的错误案例")
|
|
327
|
+
return
|
|
328
|
+
|
|
329
|
+
# 统计信息
|
|
330
|
+
log_cases = [c for c in cases if c["log_data"].get("source") == "log_file"]
|
|
331
|
+
command_cases = [
|
|
332
|
+
c
|
|
333
|
+
for c in cases
|
|
334
|
+
if c["log_data"].get("source") == "command_failure"
|
|
335
|
+
]
|
|
336
|
+
|
|
337
|
+
# 按文件统计
|
|
338
|
+
file_stats = {}
|
|
339
|
+
for case in log_cases:
|
|
340
|
+
file_path = case["log_data"].get("file_path", "unknown")
|
|
341
|
+
file_stats[file_path] = file_stats.get(file_path, 0) + 1
|
|
342
|
+
|
|
343
|
+
logging.info(f" 📊 统一分析完成!")
|
|
344
|
+
logging.info(f" 分析文件数量: {len(log_files)} 个")
|
|
345
|
+
logging.info(f" 日志条目总数: {len(all_log_entries)} 条")
|
|
346
|
+
logging.info(f" 日志文件案例: {len(log_cases)} 个")
|
|
347
|
+
logging.info(f" 命令失败案例: {len(command_cases)} 个")
|
|
348
|
+
logging.info(f" 总案例: {len(cases)} 个")
|
|
349
|
+
|
|
350
|
+
if command_failures:
|
|
351
|
+
logging.info(f" 分析的命令失败: {len(command_failures)} 个")
|
|
352
|
+
|
|
353
|
+
# 输出所有案例
|
|
354
|
+
logging.info("\n" + "=" * 60)
|
|
355
|
+
logging.info("📋 详细错误分析")
|
|
356
|
+
logging.info("=" * 60)
|
|
357
|
+
|
|
358
|
+
for i, case in enumerate(cases, 1):
|
|
359
|
+
self._output_unified_case(case, i)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# yaml-language-server: $schema=cases_template_valid.json
|
|
2
|
+
version: 1.0.0
|
|
3
|
+
cases:
|
|
4
|
+
bingo_case_001:
|
|
5
|
+
title: 构建运行内存不足
|
|
6
|
+
description: 构建运行内存不足
|
|
7
|
+
matching_rules:
|
|
8
|
+
- field: message
|
|
9
|
+
operator: regex
|
|
10
|
+
value: internal compiler error
|
|
11
|
+
solution: 构建运行内存不足,请检查宿主机资源状态
|
|
12
|
+
bingo_case_002:
|
|
13
|
+
title: conan仓中不存在指定版本的组件
|
|
14
|
+
description: conan仓中不存在指定版本的组件
|
|
15
|
+
matching_rules:
|
|
16
|
+
- field: message
|
|
17
|
+
operator: regex
|
|
18
|
+
value: was not found in remote .*
|
|
19
|
+
solution: conan仓中不存在指定版本的组件,请检查组件是否发布
|
|
20
|
+
bingo_case_003:
|
|
21
|
+
title: 未设置对应的conan远程仓
|
|
22
|
+
description: 未设置对应的conan远程仓
|
|
23
|
+
matching_rules:
|
|
24
|
+
- field: message
|
|
25
|
+
operator: regex
|
|
26
|
+
value: No remote .* defined in remote
|
|
27
|
+
solution: 未设置对应的conan远程仓,请参考conan remote add xxx进行设置
|
|
28
|
+
bingo_case_004:
|
|
29
|
+
title: 环境未初始化
|
|
30
|
+
description: 环境未初始化
|
|
31
|
+
matching_rules:
|
|
32
|
+
- field: message
|
|
33
|
+
operator: regex
|
|
34
|
+
value: CMAKE_C_COMPILER not set, after EnableLanguage
|
|
35
|
+
solution: 请在manifest目录下执行python3 init.py初始化环境
|
|
36
|
+
bingo_case_005:
|
|
37
|
+
title: 无法下载git源码
|
|
38
|
+
description: 无法下载git源码
|
|
39
|
+
matching_rules:
|
|
40
|
+
- field: message
|
|
41
|
+
operator: regex
|
|
42
|
+
value: Couldn't checkout .* git
|
|
43
|
+
solution: 请检查本机git令牌配置,确保存在源码所需令牌
|
|
44
|
+
bingo_case_006:
|
|
45
|
+
title: 当前环境缺少待签名的uboot文件
|
|
46
|
+
description: 当前环境缺少待签名的uboot文件
|
|
47
|
+
matching_rules:
|
|
48
|
+
- field: message
|
|
49
|
+
operator: regex
|
|
50
|
+
value: 执行失败,打开了自签名模式但未找到待签名的uboot文件,构建失败
|
|
51
|
+
solution: 下载官网bmc_sdk,替换本地~/sdk, 确保带时间戳结尾的文件存在
|
|
52
|
+
bingo_case_007:
|
|
53
|
+
title: Linux权限错误
|
|
54
|
+
description: Linux权限错误
|
|
55
|
+
matching_rules:
|
|
56
|
+
- field: message
|
|
57
|
+
operator: regex
|
|
58
|
+
value: permission denied
|
|
59
|
+
solution: 请检查对应出错的文件权限,并确认当前用户是否有该操作权限
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "Bingo Cases Schema",
|
|
4
|
+
"description": "Schema for validating bingo cases configuration",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["version", "cases"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"version": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "版本号,必须符合三段式版本格式",
|
|
11
|
+
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
|
12
|
+
},
|
|
13
|
+
"cases": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"description": "案例集合",
|
|
16
|
+
"minProperties": 1,
|
|
17
|
+
"patternProperties": {
|
|
18
|
+
"^bingo_case_[0-9]{3,}$": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"required": ["title", "description", "matching_rules"],
|
|
21
|
+
"properties": {
|
|
22
|
+
"title": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "案例标题",
|
|
25
|
+
"minLength": 1
|
|
26
|
+
},
|
|
27
|
+
"description": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "案例描述",
|
|
30
|
+
"minLength": 1
|
|
31
|
+
},
|
|
32
|
+
"matching_rules": {
|
|
33
|
+
"type": "array",
|
|
34
|
+
"description": "匹配规则列表",
|
|
35
|
+
"minItems": 1,
|
|
36
|
+
"items": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"required": ["field", "operator", "value"],
|
|
39
|
+
"properties": {
|
|
40
|
+
"field": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
"description": "匹配字段",
|
|
43
|
+
"enum": ["message"]
|
|
44
|
+
},
|
|
45
|
+
"operator": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"description": "匹配操作符",
|
|
48
|
+
"enum": ["regex", "contains", "equals"]
|
|
49
|
+
},
|
|
50
|
+
"value": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"description": "匹配值",
|
|
53
|
+
"minLength": 1
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"additionalProperties": false
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"solution": {
|
|
60
|
+
"type": "string",
|
|
61
|
+
"description": "解决方案"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"additionalProperties": false
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"additionalProperties": false
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"additionalProperties": false
|
|
71
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Copyright (c) 2025 Huawei Technologies Co., Ltd.
|
|
2
|
+
# openUBMC is licensed under Mulan PSL v2.
|
|
3
|
+
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
|
4
|
+
# You may obtain a copy of Mulan PSL v2 at:
|
|
5
|
+
# http://license.coscl.org.cn/MulanPSL2
|
|
6
|
+
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
|
7
|
+
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
8
|
+
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
9
|
+
# See the Mulan PSL v2 for more details.
|
|
10
|
+
import os
|
|
11
|
+
import json
|
|
12
|
+
import stat
|
|
13
|
+
|
|
14
|
+
from conan import ConanFile
|
|
15
|
+
from conan.tools.files import copy, load
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BingoCasesConan(ConanFile):
|
|
19
|
+
name = "bingo_cases"
|
|
20
|
+
user = "openubmc"
|
|
21
|
+
channel = "stable"
|
|
22
|
+
settings = None
|
|
23
|
+
exports_sources = ["cases.yml", "cases_template_valid.json"]
|
|
24
|
+
|
|
25
|
+
def set_version(self):
|
|
26
|
+
import jsonschema
|
|
27
|
+
|
|
28
|
+
cases_data = self._load_cases_data()
|
|
29
|
+
# 验证cases模板
|
|
30
|
+
valid_content = load(self, "cases_template_valid.json")
|
|
31
|
+
valid_data = json.loads(valid_content)
|
|
32
|
+
jsonschema.validate(cases_data, valid_data)
|
|
33
|
+
|
|
34
|
+
self.version = cases_data["version"]
|
|
35
|
+
|
|
36
|
+
def build(self):
|
|
37
|
+
import yaml
|
|
38
|
+
|
|
39
|
+
cases_data = self._load_cases_data()
|
|
40
|
+
with os.fdopen(
|
|
41
|
+
os.open(
|
|
42
|
+
"cases.yml",
|
|
43
|
+
os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
|
|
44
|
+
stat.S_IWUSR | stat.S_IRUSR,
|
|
45
|
+
),
|
|
46
|
+
"w",
|
|
47
|
+
) as file_handler:
|
|
48
|
+
yaml.dump(cases_data, file_handler, encoding="utf-8", allow_unicode=True)
|
|
49
|
+
|
|
50
|
+
def package(self):
|
|
51
|
+
copy(self, "cases.yml", self.build_folder, self.package_folder)
|
|
52
|
+
|
|
53
|
+
def _load_cases_data(self):
|
|
54
|
+
import yaml
|
|
55
|
+
|
|
56
|
+
# 从 case.yml 文件读取
|
|
57
|
+
cases_content = load(self, "cases.yml")
|
|
58
|
+
return yaml.safe_load(cases_content)
|
bmcgo/frame.py
CHANGED
|
@@ -45,7 +45,6 @@ class Frame(object):
|
|
|
45
45
|
self.perf = PerfAnalysis(os.path.join(bconfig.manifest.folder, "output"))
|
|
46
46
|
self.code_path = os.path.join(bconfig.manifest.folder, "build")
|
|
47
47
|
sys.path.append(self.code_path)
|
|
48
|
-
os.makedirs(misc.CACHE_DIR, exist_ok=True)
|
|
49
48
|
self.ws_server = None
|
|
50
49
|
self.config = config
|
|
51
50
|
self.config.log_init()
|
|
@@ -217,8 +216,5 @@ class Frame(object):
|
|
|
217
216
|
|
|
218
217
|
def _prepare(self):
|
|
219
218
|
tool.sudo_passwd_check()
|
|
220
|
-
shutil.rmtree(misc.CACHE_DIR)
|
|
221
|
-
os.makedirs(misc.CACHE_DIR)
|
|
222
|
-
os.chmod(misc.CACHE_DIR, 0o777)
|
|
223
219
|
if shutil.which(misc.CONAN) is not None and misc.conan_v1():
|
|
224
220
|
tool.run_command("conan remove --locks")
|
bmcgo/functional/analysis.py
CHANGED
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
# See the Mulan PSL v2 for more details.
|
|
13
13
|
import os
|
|
14
14
|
import argparse
|
|
15
|
-
from multiprocessing import Process
|
|
16
15
|
import urllib3
|
|
17
16
|
from bmcgo.frame import Frame
|
|
18
17
|
from bmcgo.misc import CommandInfo
|
|
@@ -60,23 +59,30 @@ class BmcgoCommand:
|
|
|
60
59
|
self.out_dir = pre_parsed_args.out_dir
|
|
61
60
|
self.lock_file = pre_parsed_args.lock_file
|
|
62
61
|
self.board_name = pre_parsed_args.board_name
|
|
62
|
+
|
|
63
|
+
def component_analysis(self):
|
|
64
|
+
if not InterfaceValidation(self.remote).run() or not SrValidate(os.getcwd()).run():
|
|
65
|
+
return -1
|
|
66
|
+
return 0
|
|
63
67
|
|
|
68
|
+
def product_analysis(self, custom_sr_dir):
|
|
69
|
+
analysis_task = AnalysisComp(self.board_name, self.out_dir, self.lock_file, custom_sr_dir)
|
|
70
|
+
result = analysis_task.run()
|
|
71
|
+
return result
|
|
72
|
+
|
|
64
73
|
def run(self):
|
|
65
74
|
is_integrated, work_dir = self._is_integrated_project()
|
|
66
75
|
if is_integrated:
|
|
67
76
|
os.chdir(work_dir)
|
|
68
77
|
else:
|
|
69
78
|
os.chdir(self.bconfig.component.folder)
|
|
70
|
-
#
|
|
79
|
+
# 组件级
|
|
71
80
|
if not is_integrated:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
custom_sr_dir =
|
|
76
|
-
|
|
77
|
-
if not is_integrated:
|
|
78
|
-
custom_sr_dir = os.getcwd()
|
|
79
|
-
elif self.rebuild:
|
|
81
|
+
return self.component_analysis()
|
|
82
|
+
|
|
83
|
+
# 产品级
|
|
84
|
+
custom_sr_dir = os.getcwd()
|
|
85
|
+
if self.rebuild:
|
|
80
86
|
parsed = []
|
|
81
87
|
if self.board_name:
|
|
82
88
|
parsed.append("-b")
|
|
@@ -85,9 +91,9 @@ class BmcgoCommand:
|
|
|
85
91
|
frame = Frame(self.bconfig, config)
|
|
86
92
|
frame.parse(parsed)
|
|
87
93
|
frame.run()
|
|
94
|
+
|
|
88
95
|
os.chdir(os.path.join(work_dir, misc.BUILD))
|
|
89
|
-
|
|
90
|
-
rc = analysis_task.run()
|
|
96
|
+
rc = self.product_analysis(custom_sr_dir)
|
|
91
97
|
if not rc:
|
|
92
98
|
return -1
|
|
93
99
|
log.success("BMC 构建分析成功")
|
|
@@ -34,16 +34,10 @@ def if_available(bconfig: BmcgoConfig):
|
|
|
34
34
|
return True
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
class BmcgoCommand(
|
|
37
|
+
class BmcgoCommand():
|
|
38
38
|
def __init__(self, bconfig: BmcgoConfig, *args):
|
|
39
39
|
self.bconfig = bconfig
|
|
40
|
-
parser =
|
|
41
|
-
formatter_class=argparse.RawTextHelpFormatter)
|
|
42
|
-
action_group = parser.add_mutually_exclusive_group()
|
|
43
|
-
action_group.add_argument("-start", action=ACTION_TRUE, help="bmc studio的启动操作")
|
|
44
|
-
action_group.add_argument("-stop", action=ACTION_TRUE, help="bmc studio的停止操作")
|
|
45
|
-
action_group.add_argument("-restart", action=ACTION_TRUE, help="bmc studio的重启操作")
|
|
46
|
-
parser.add_argument("-b", "--backend", help="指定bmc studio是前端运行还是后端运行,默认前端运行", action=ACTION_TRUE)
|
|
40
|
+
parser = self._create_parser()
|
|
47
41
|
parsed_args, _ = parser.parse_known_args(*args)
|
|
48
42
|
self.action = self.get_action(parsed_args)
|
|
49
43
|
self.backend = parsed_args.backend
|
|
@@ -54,7 +48,8 @@ class BmcgoCommand(object):
|
|
|
54
48
|
self.studio_script = f"{self.studio_path}/bmc_studio.sh"
|
|
55
49
|
self.studio_command = f"{self.studio_script} {self.action}"
|
|
56
50
|
|
|
57
|
-
|
|
51
|
+
@staticmethod
|
|
52
|
+
def get_action(parsed_args):
|
|
58
53
|
if parsed_args.stop:
|
|
59
54
|
return "stop"
|
|
60
55
|
elif parsed_args.restart:
|
|
@@ -84,7 +79,7 @@ class BmcgoCommand(object):
|
|
|
84
79
|
|
|
85
80
|
def run_start(self):
|
|
86
81
|
if not self.backend:
|
|
87
|
-
|
|
82
|
+
self._run_front_end()
|
|
88
83
|
return
|
|
89
84
|
|
|
90
85
|
self.studio_command = f"{self.studio_command} backend"
|
|
@@ -96,3 +91,19 @@ class BmcgoCommand(object):
|
|
|
96
91
|
return
|
|
97
92
|
|
|
98
93
|
log.warning(start_out)
|
|
94
|
+
|
|
95
|
+
def _run_front_end(self):
|
|
96
|
+
self.studio_command = f"trap ':' INT; /bin/bash {self.studio_command}; :"
|
|
97
|
+
command_list = ['/bin/bash', '-c', self.studio_command]
|
|
98
|
+
tools.run_command(command_list, command_echo=False, show_log=True, timeout=None)
|
|
99
|
+
|
|
100
|
+
def _create_parser(self):
|
|
101
|
+
_ = self
|
|
102
|
+
parser = argparse.ArgumentParser(prog="bmc studio", description="启动停止bmc studio", add_help=True,
|
|
103
|
+
formatter_class=argparse.RawTextHelpFormatter)
|
|
104
|
+
action_group = parser.add_mutually_exclusive_group()
|
|
105
|
+
action_group.add_argument("-start", action=ACTION_TRUE, help="bmc studio的启动操作")
|
|
106
|
+
action_group.add_argument("-stop", action=ACTION_TRUE, help="bmc studio的停止操作")
|
|
107
|
+
action_group.add_argument("-restart", action=ACTION_TRUE, help="bmc studio的重启操作")
|
|
108
|
+
parser.add_argument("-b", "--backend", help="指定bmc studio是前端运行还是后端运行,默认前端运行", action=ACTION_TRUE)
|
|
109
|
+
return parser
|