8.7 KiB
8.7 KiB
天璇跨机故障诊断手册
问题描述
同一份项目压缩包、同一份 CSV 数据(SHA256 一致)、同一份 runtime/python/ 运行时,
在不同 Windows 10 x64 机器上出现不同行为:
| 设备 | 表现 |
|---|---|
| 你的机器 | ✅ 正常运行 |
| 第二台 | ❌ "尝试对str类型求mean" |
| 第三台 | ❌ "ZZZ不在{XXX,YYY}中" + 字符串相关错误 |
| (可能的更多) | ❌ 其他奇怪错误 |
1. 立即诊断流程
步骤 A:收集两台机器的日志
在两台问题机器上各执行一次:
# 1. 清理旧日志
del logs\tianxuan.log 2>nul
# 2. 生成测试数据(确保 seed 一致)
runtime\python\python.exe scripts\gen_complex_test.py --rows 5000 --output data\complex_test.csv
# 3. 运行完整管道
runtime\python\python.exe manage.py run_pipeline data\complex_test.csv --algo hdbscan
# 4. 复制日志到桌面备用
copy logs\tianxuan.log %USERPROFILE%\Desktop\log_机器A.txt
在你的机器上也执行一次,生成 log_你的机器.txt。
步骤 B:对比日志
runtime\python\python.exe scripts\diagnose_compare.py log_你的机器.txt log_机器A.txt
输出示例:
[DIFF] 首个差异在行 123:
--- 机器A ---
[LOAD_SCHEMA] col=duration dtype=Utf8 sample=1.2s,0.5s,3.1s
--- 机器B ---
[LOAD_SCHEMA] col=duration dtype=Float64 sample=1.2,0.5,3.1
第一个 [DIFF] 就是导致问题的根源。
2. 关键日志标记解读
日志中所有 [TAG] 标记的含义:
| 标记 | 对应文件 | 含义 |
|---|---|---|
[LOAD] |
data_loader.py |
CSV 加载完成,文件数和预估行数 |
[LOAD_SCHEMA] |
data_loader.py |
每列的推断类型(最重要!) |
[FIND_COL] |
entity_aggregator.py |
关键词匹配结果,显示匹配到的列名和类型 |
[AGGR_EXPR] |
entity_aggregator.py |
聚合表达式 |
[CLUSTER_INPUT] |
tool_registry.py |
聚类输入数据形状 |
[LLM REQUEST] |
llm_orchestrator.py |
LLM 请求内容 |
[LLM RESPONSE] |
llm_orchestrator.py |
LLM 响应状态 |
[TOOL CALL] |
llm_orchestrator.py |
LLM 调用的工具 |
[TOOL RESULT] |
llm_orchestrator.py |
工具执行结果 |
3. "尝试对str类型求mean" 故障排查
根因树
"尝试对str类型求mean" 报错
├── 调用链: build_aggregation_params → pl.mean(col_name)
│
├── [可能A] 列名关键词匹配到了错误的列
│ ├── 检查 [FIND_COL] keywords=duration matched=xxx
│ ├── 如果 matched 列的类型是 Utf8/String → 匹配错误
│ └── 修复: entity_aggregator.py 中 frozenset→tuple (已改)
│
├── [可能B] CSV 列的实际类型与推断类型不同
│ ├── 检查 [LOAD_SCHEMA] col=duration dtype=xxx
│ ├── 对比两台机器的 dtype 是否一致
│ ├── 如果一致但行为不同 → 可能是 Polars 版本差异
│ └── 验证: runtime\python\python.exe -c "import polars; print(polars.__version__)"
│
├── [可能C] PYTHONUTF8 环境变量未生效
│ ├── 验证: runtime\python\python.exe -c "import sys; print(sys.getdefaultencoding())"
│ ├── 期望输出: utf-8
│ ├── 如果输出 cp936/gbk → PYTHONUTF8 没生效
│ └── 修复: run.bat 中 set PYTHONUTF8=1 (已加)
│
└── [可能D] 聚合表达式中混入了 Utf8 列
├── 检查 [FIND_COL] keywords=duration matched=duration dtype=Utf8
├── 这意味着 duration 列被 Polars 推断为字符串
└── 修复: entity_aggregator.py 的 dtype 安全聚合 (已改)
诊断命令
# 1. 检查 Python 默认编码
runtime\python\python.exe -c "import sys; print(f'enc={sys.getdefaultencoding()} fs={sys.getfilesystemencoding()}')"
# 2. 检查 Polars 版本
runtime\python\python.exe -c "import polars; print(polars.__version__)"
# 3. 检查 CSV 加载后的实际列类型
runtime\python\python.exe -c "
import polars as pl
lf = pl.scan_csv(r'data\complex_test.csv', infer_schema_length=10000)
schema = lf.collect_schema()
for name, dtype in zip(schema.names(), schema.dtypes()):
print(f'{name}: {dtype}')
"
# 4. 检查关键词匹配结果
runtime\python\python.exe -c "
import sys; sys.path.insert(0, '.')
import os; os.environ['DJANGO_SETTINGS_MODULE']='tianxuan.settings'
import django; django.setup()
from analysis.session_store import SessionStore
from analysis.data_loader import load_csv_directory
from analysis.entity_aggregator import _find_column, _BUILT_AGGR
store = SessionStore(); store.drop_all()
lf, schema, rc, fc, mem = load_csv_directory(r'data\complex_test.csv')
for kw_name, kw_set in [
('bytes_sent',_BYTES_SENT_KEYWORDS),('duration',_DURATION_KEYWORDS),
('dst_ip',_DST_IP_KEYWORDS),('cipher',_CIPHER_KEYWORDS)]:
col = _find_column(schema, kw_set)
dtype = schema.get(col, 'N/A') if col else 'N/A'
print(f'{kw_name:15s} -> {str(col):25s} dtype={dtype}')
" 2>&1
4. "ZZZ不在{XXX,YYY}中" 故障排查
根因
这个错误来自 Polars 的 str.contains() 或 is_in() 操作。当比较字符串时,
两边的编码不一致导致匹配失败。
诊断
# 检查 Python 编码
runtime\python\python.exe -c "
import sys; print(f'default={sys.getdefaultencoding()}')
print(f'filesystem={sys.getfilesystemencoding()}')
print(f'stdin={sys.stdin.encoding}')
print(f'stdout={sys.stdout.encoding}')
"
# 检查 CSV 文件的实际编码(在 Notepad++ 或 VS Code 中查看右下角编码提示)
# 期望: UTF-8 (无 BOM)
修复尝试
# 修复 1: 确认 run.bat 包含 PYTHONUTF8=1
findstr PYTHONUTF8 run.bat
# 应输出: set PYTHONUTF8=1
# 修复 2: 手动设置编码后再运行
set PYTHONUTF8=1
runtime\python\python.exe manage.py run_pipeline data\complex_test.csv
5. 跨机环境一致性检查清单
在每台机器上执行以下命令,对比输出:
echo === 1. Python 版本 ===
runtime\python\python.exe --version
echo === 2. 编码设置 ===
runtime\python\python.exe -c "
import sys; print(f'defaultenc={sys.getdefaultencoding()}')
print(f'fsenc={sys.getfilesystemencoding()}')
print(f'has_utf8_mode={sys.flags.utf8_mode}')
"
echo === 3. Polars 版本 ===
runtime\python\python.exe -c "import polars; print(polars.__version__)"
echo === 4. numpy 版本 ===
runtime\python\python.exe -c "import numpy; print(numpy.__version__)"
echo === 5. sklearn 版本 ===
runtime\python\python.exe -c "import sklearn; print(sklearn.__version__)"
echo === 6. 系统区域 ===
systeminfo | findstr "区域"
6. 应急修复方案
如果 diagnose_compare.py 显示的第一个差异是 [LOAD_SCHEMA] 列类型不同:
# 强制所有列读为字符串 → 让 Polars 自己推断
runtime\python\python.exe -c "
import polars as pl
lf = pl.scan_csv(r'data\complex_test.csv', infer_schema_length=100000)
schema = lf.collect_schema()
# 打印所有列的推断类型
for n, d in zip(schema.names(), schema.dtypes()):
print(f'{n}: {d}')
"
如果差异在 [FIND_COL] 关键词匹配不同:
# 手动指定实体列后运行
runtime\python\python.exe manage.py run_pipeline data\complex_test.csv --entity-col src_ip
如果完全无法解决:
# 重建运行时(在开发机上有网络的机器上)
# 1. 删除旧的 runtime
rmdir /s runtime
# 2. 重新运行构建脚本
scripts\build_runtime.bat
# 3. 重新压缩分发
7. 已知已修复的问题清单
| 问题编号 | 修复内容 | 涉及文件 | 验证方式 |
|---|---|---|---|
| W4-#1 | frozenset→tuple 消除哈希随机性 |
entity_aggregator.py |
查看文件确认无 frozenset |
| W4-#2 | PYTHONUTF8=1 强制 UTF-8 编码 |
run.bat |
findstr PYTHONUTF8 run.bat |
| W4-#3 | sorted(keywords) 确定性关键词迭代 |
entity_aggregator.py |
查看 _find_column 函数 |
| W4-#4 | 结构化 [TAG] 日志 |
多文件 | 运行后查看 logs/tianxuan.log |
| DB-#1 | sync_to_async 解决异步 ORM 问题 |
tool_registry.py |
管道完成后特征写入 DB |
| DB-#2 | value_counts() 列名兼容 |
data_profiler.py |
profile_data 工具正常返回 |
| DB-#3 | numpy corrcoef 替代 pandas | data_profiler.py |
无 pandas 时也能跑 |
8. 归档与提交诊断结果
# 收集诊断信息到单个文件,发给开发者
(
echo === 天璇故障诊断报告 ===
date /t
time /t
echo.
echo === 环境信息 ===
runtime\python\python.exe --version
runtime\python\python.exe -c "import sys; print(f'enc={sys.getdefaultencoding()}')"
runtime\python\python.exe -c "import polars; print(f'polars={polars.__version__}')"
echo.
echo === 日志最后 50 行 ===
type logs\tianxuan.log 2>nul
) > 诊断报告.txt