283 lines
8.7 KiB
Markdown
283 lines
8.7 KiB
Markdown
# 天璇跨机故障诊断手册
|
|
|
|
## 问题描述
|
|
|
|
同一份项目压缩包、同一份 CSV 数据(SHA256 一致)、同一份 `runtime/python/` 运行时,
|
|
在不同 Windows 10 x64 机器上出现不同行为:
|
|
|
|
| 设备 | 表现 |
|
|
|------|------|
|
|
| 你的机器 | ✅ 正常运行 |
|
|
| 第二台 | ❌ "尝试对str类型求mean" |
|
|
| 第三台 | ❌ "ZZZ不在{XXX,YYY}中" + 字符串相关错误 |
|
|
| (可能的更多) | ❌ 其他奇怪错误 |
|
|
|
|
---
|
|
|
|
## 1. 立即诊断流程
|
|
|
|
### 步骤 A:收集两台机器的日志
|
|
|
|
在两台**问题机器**上各执行一次:
|
|
|
|
```bash
|
|
# 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:对比日志
|
|
|
|
```bash
|
|
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 安全聚合 (已改)
|
|
```
|
|
|
|
### 诊断命令
|
|
|
|
```bash
|
|
# 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()` 操作。当比较字符串时,
|
|
两边的编码不一致导致匹配失败。
|
|
|
|
### 诊断
|
|
|
|
```bash
|
|
# 检查 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)
|
|
```
|
|
|
|
### 修复尝试
|
|
|
|
```bash
|
|
# 修复 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. 跨机环境一致性检查清单
|
|
|
|
在**每台机器**上执行以下命令,对比输出:
|
|
|
|
```bash
|
|
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]` 列类型不同:
|
|
|
|
```bash
|
|
# 强制所有列读为字符串 → 让 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]` 关键词匹配不同:
|
|
|
|
```bash
|
|
# 手动指定实体列后运行
|
|
runtime\python\python.exe manage.py run_pipeline data\complex_test.csv --entity-col src_ip
|
|
```
|
|
|
|
如果完全无法解决:
|
|
|
|
```bash
|
|
# 重建运行时(在开发机上有网络的机器上)
|
|
# 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. 归档与提交诊断结果
|
|
|
|
```bash
|
|
# 收集诊断信息到单个文件,发给开发者
|
|
(
|
|
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
|
|
```
|