# 天璇跨机故障诊断手册 ## 问题描述 同一份项目压缩包、同一份 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 ```