简介:这是一套面向暗黑破坏神2模组PD2玩家的自动化脚本集,基于Kolbot框架构建,能够实现自动建房、角色移动、技能释放、物品拾取等常用功能,适合有一定脚本基础并希望深度定制机器人行为的玩家使用。压缩包共包含229个文件,整体体积约707KB,其中大部分为脚本文件(151个),其余为配置说明、拾取规则、任务入口文件等,各文件用途清晰,方便按需修改和扩展。已有 241 人浏览学习。脚本内提供了服务器配置项,可通过修改获得更合适的游戏服务器;同时包含技能编号查询和拾取规则说明,帮助玩家自定义过滤逻辑;针对程序崩溃问题,也整理了更新版本、设置管理员权限等常见的修复办法。无论是优化联机路径、控制角色行为,还是排查运行异常,这套脚本都能为暗黑破坏神2的自动化游戏提供一套易于上手的扩展基础。 开头直接切入:
接触pd2bs也有一段时间了,这工具在设备老化测试圈子里口碑一直不错,但真正让它在团队里落地跑起来的,还是配套的pd2bs-scripts脚本集。单纯用pd2bs裸跑也能做老化,可一旦涉及批量设备、轮次切换、日志回传、异常重试这些真实工程需求,没有一套脚本去编排,人肉盯场能把你消耗到怀疑人生。这套脚本解决的核心问题就是:把老化测试从“手动一条命令跑一轮”升级成“全自动多轮循环跑完还能自己出报告”。
1. 项目背景与整体设计思路
1.1 pd2bs到底在测什么
pd2bs是一款面向硬件设备稳定性验证的命令行测试工具,核心能力是给被测设备施加持续的压力负载,在高强度运行状态下暴露潜在的可靠性缺陷。老化测试(Burn-in Test)本身就是个时间密集型工作,一轮跑下来可能几小时甚至几天,测试期间必须保证压力不间断、数据有记录、异常能感知。
pd2bs本身的设计哲学是“单次任务只管执行”,它不关心你今天要跑几轮、跑完要不要汇总、某个设备中途掉了要不要补测。这些工程化的编排逻辑,恰恰是pd2bs-scripts存在的意义。
1.2 为什么需要一套脚本而不是直接敲命令
第一批用pd2bs的人多半是从手动敲命令开始的,一轮一块设备,命令敲完盯着终端等结果。设备少还行,超过三块就开始乱了——每块设备一个终端窗口,每轮结束要人工拷贝日志,下轮开始再手动切配置,一旦有几块设备掉线,恢复后还得自己数轮次,错一轮整批数据就废了。
pd2bs-scripts的思路就是把这类重复劳动全部脚本化,人工只负责“把设备接上、跑起来、回来看报告”。设计上遵循三个原则:
- 配置与代码分离:测试参数写在独立配置文件里,脚本不硬编码业务数据,要调设备数量、循环轮次、压力时长,改配置文件即可,不用碰脚本本体。
- 单设备故障不影响整批:老化测试中单台设备掉线是常态,脚本要能感知到掉线并把该设备剔除出本轮,其他设备继续跑,而不是整批罢工。
- 日志和报告自动归档:每台设备、每一轮、每一种压力的输出都按既定目录结构存放,结束之后有一条命令汇总出报告,不用手动翻找合并。
这套设计直接决定了脚本集的目录组织方式和每个脚本的职责边界。
2. 脚本集整体结构与核心模块拆解
2.1 目录结构一眼看懂
一个设计良好的脚本集,拿到手先看目录就应该能猜出它是怎么工作的。pd2bs-scripts的目录划分就是围绕配置、执行、日志、报告四件事展开的:
pd2bs-scripts/ ├── config/ │ ├── devices.ini │ └── test_profile.json ├── scripts/ │ ├── precheck.sh │ ├── run_burnin.py │ ├── monitor.py │ └── report.py ├── logs/ │ └── (按日期+批次自动生成) ├── reports/ │ └── (汇总报告输出目录) └── README.mdconfig目录放设备清单和测试档位,scripts目录放四个核心脚本,logs和reports是运行产物的落盘位置。这个结构看起来简单,但实际用下来会发现,把“配置”独立出来是老化测试脚本最关键的设计决策——因为老化测试的参数调整频率极高,测一批新机型、换一种压力策略、调整循环次数,都需要动参数,如果参数埋在脚本逻辑里,每次改动都要小心翼翼地找代码位置,改错一个数整批测试报废。
2.2 四个核心脚本各管一摊
- precheck.sh:环境预检。检查pd2bs是否安装、版本是否符合要求、依赖库是否齐全、设备连接状态是否正常。它解决的是“跑到一半才发现环境不对”的尴尬。
- run_burnin.py:主执行脚本。读取配置,按设备逐台发起老化任务,管理轮次循环,处理设备掉线重试。
- monitor.py:运行监控。定时探测各设备状态,收集实时数据,发现异常设备自动标记。
- report.py:报告生成。汇总logs目录下的所有设备日志,生成结果汇总表和异常列表。
四个脚本通过文件系统解耦,run_burnin.py只管执行和写日志,monitor.py从日志和系统状态读取信息,report.py最后汇总。这种松耦合设计的好处是,某一块逻辑重写不会波及其他模块,比如以后想换监控策略,只动monitor.py就够了。
3. 核心脚本实操与关键环节实现
3.1 环境预检:把问题拦截在开跑之前
老化测试最怕的不是测试中出故障,而是准备了两小时环境、设备全部接好了,结果跑起来发现pd2bs版本不对,或者某个依赖库缺失,整个排期又得往后推。precheck.sh就是干这个的,把前置校验全部自动化。
脚本核心逻辑分三块。第一块检查pd2bs本身:
#!/bin/bash # scripts/precheck.sh # 检查pd2bs是否安装及版本兼容性 REQUIRED_VERSION="2.4.0" INSTALLED_VERSION=$(pd2bs --version 2>/dev/null | grep -oP '\d+\.\d+\.\d+') if [ -z "$INSTALLED_VERSION" ]; then echo "[ERROR] pd2bs 未安装或不在PATH中,请先安装 pd2bs >= $REQUIRED_VERSION" exit 1 fi # 将版本号按点号拆解为三段,逐级比较,确保主版本和次版本都满足要求 IFS='.' read -r MAJOR MINOR PATCH <<< "$INSTALLED_VERSION" IFS='.' read -r REQ_MAJOR REQ_MINOR REQ_PATCH <<< "$REQUIRED_VERSION" if [ "$MAJOR" -lt "$REQ_MAJOR" ] || \ { [ "$MAJOR" -eq "$REQ_MAJOR" ] && [ "$MINOR" -lt "$REQ_MINOR" ]; }; then echo "[ERROR] pd2bs 版本过低: 当前 $INSTALLED_VERSION, 需要 >= $REQUIRED_VERSION" exit 1 fi echo "[INFO] pd2bs 版本检查通过: $INSTALLED_VERSION"版本比较这里有个细节,直接用字符串比较>=在shell里会出问题,因为"2.10.0"会被判为比"2.4.0"小,所以必须把版本号拆开逐段比较。这块是我实际踩过坑的地方,一开始图省事直接用字符串比,结果某些设备上的pd2bs版本被误判为过低,排查了半天才发现是比较逻辑的问题。
第二块检查设备连接状态,读取config/devices.ini中的设备ID列表,逐个调用pd2bs的设备探测接口,确认设备在线且可通信。第三块检查磁盘空间,老化测试跑起来日志增长很快,磁盘写满会直接导致测试中断,所以预检脚本里预留了空间阈值判断,默认低于5GB直接报警。
3.2 主执行脚本:轮次循环与异常容错
run_burnin.py是整个脚本集的核心,承担了最复杂的编排逻辑。它的工作流程是:读取配置 → 建立设备列表 → 按循环次数发起老化任务 → 每轮结束后检查结果 → 设备掉线自动跳过 → 执行下一轮 → 全部结束后标记完成。
代码骨架如下:
#!/usr/bin/env python3 # scripts/run_burnin.py """pd2bs老化测试主执行脚本:管理多设备多轮次的循环执行。""" import json import logging import subprocess import sys import time from pathlib import Path def load_config(config_path: Path) -> dict: """加载测试档位配置。""" with open(config_path, "r", encoding="utf-8") as f: cfg = json.load(f) # 配置合法性校验 assert "devices" in cfg and len(cfg["devices"]) > 0, "设备列表不能为空" assert "cycles" in cfg and cfg["cycles"] > 0, "循环次数必须大于0" assert "stress_duration" in cfg and cfg["stress_duration"] > 0, "压力时长必须大于0" return cfg def run_single_device(device_id: str, cfg: dict) -> bool: """在指定设备上执行一轮老化测试,返回是否成功。""" cmd = [ "pd2bs", "run", "--device", device_id, "--duration", str(cfg["stress_duration"]), "--profile", cfg["stress_profile"], ] try: result = subprocess.run(cmd, capture_output=True, text=True, timeout=cfg.get("timeout", 3600)) if result.returncode != 0: logging.error(f"[{device_id}] pd2bs 执行失败: {result.stderr[-500:]}") return False return True except subprocess.TimeoutExpired: logging.error(f"[{device_id}] 执行超时,强制终止") return False except FileNotFoundError: logging.error(f"[{device_id}] pd2bs 命令不存在,请检查PATH") sys.exit(1) def main(): logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", handlers=[ logging.FileHandler("logs/run_burnin.log", encoding="utf-8"), logging.StreamHandler(), ], ) cfg = load_config(Path("config/test_profile.json")) devices = cfg["devices"] cycles = cfg["cycles"] # 统计字段:记录每台设备的完成轮次、失败轮次和当前状态 stats = {dev: {"finished": 0, "failed": 0, "skipped": 0} for dev in devices} for cycle in range(1, cycles + 1): logging.info(f"=== 第 {cycle}/{cycles} 轮开始 ===") alive_devices = get_alive_devices(devices) # 每轮动态探测在线设备 for dev in alive_devices: if is_device_offline(dev): stats[dev]["skipped"] += 1 logging.warning(f"[{dev}] 本轮离线,跳过") continue logging.info(f"[{dev}] 开始老化测试 (第{cycle}轮)") success = run_single_device(dev, cfg) if success: stats[dev]["finished"] += 1 else: stats[dev]["failed"] += 1 mark_cycle_done(dev, cycle) # 在日志中标记该设备该轮已完成 # 每轮结束后等待冷却时间,避免设备过热影响下一轮表现 cooldown = cfg.get("cooldown_seconds", 0) if cooldown > 0 and cycle < cycles: logging.info(f"本轮结束,冷却 {cooldown}s") time.sleep(cooldown) # 输出汇总统计 print(json.dumps(stats, ensure_ascii=False, indent=2)) logging.info("全部轮次执行完毕") if __name__ == "__main__": main()这里有一个关键点在get_alive_devices函数——每轮开始前动态探测一次设备在线状态,而不是用开始时的固定列表。原因很简单:老化测试期间设备可能因为过热重启、系统崩溃、接口松动等原因掉线,如果脚本还坚持对掉线设备发起测试,不仅浪费时间,还会让测试结果完全不可信。动态探测之后,掉线设备自动跳过,等下一轮再探测,如果恢复了就重新参与测试,这在日常执行里非常实用。
轮次之间的冷却时间也是经验值。不同设备对高温的容忍度不一样,有些设备在八轮连续压力之后温度会明显异常,但留出两三分钟的冷却,让设备喘口气,下一轮的稳定性会有肉眼可见的提升。冷却时长作为配置项暴露出来,具体值需要结合设备规格和现场实测来定,我给的建议参数是3到5分钟,设备温度敏感型的话可以放宽到10分钟。
3.3 监控脚本:实时盯场,异常早发现
run_burnin.py是发起任务的主力,但老化测试跑起来之后,不能完全“跑完再看结果”,尤其是多设备批量测试的时候,某台设备可能第一轮就挂了,结果没人发现,后面几轮全是无效测试。monitor.py就承担了这个盯场的角色。
它的实现思路是每隔一段时间去探测一次设备状态,把在线状态、温度、本轮执行进度写入一个状态文件,同时对比上一次探测结果,如果设备从在线变成离线,立刻发出告警。这个“状态变化检测”远比单纯的“设备是否在线”更有价值——一台设备从开始就一直离线,和一台设备中途掉线,处理优先级完全不同,后者说明可能出现了真实故障。
# scripts/monitor.py 核心逻辑片段 def check_device_health(device_id: str) -> dict: """探测单台设备状态,返回状态字典。""" try: result = subprocess.run( ["pd2bs", "status", "--device", device_id], capture_output=True, text=True, timeout=10, ) if result.returncode != 0: return {"id": device_id, "online": False, "temp": None, "running": False} temp = parse_temperature(result.stdout) return {"id": device_id, "online": True, "temp": temp, "running": True} except subprocess.TimeoutExpired: return {"id": device_id, "online": False, "temp": None, "running": False}监控脚本还可以扩展一个功能:设备温度过高时主动跳过下一轮任务。办法是让monitor.py写一个冷却标记文件,run_burnin.py在每轮开始前读取这个标记,如果发现某设备被标记为“当前过温”,就临时跳过该设备。
3.4 报告生成:多设备多轮日志汇总
老化测试结束后最痛苦的事情是什么?是把几十个日志文件翻一遍,人工确认哪些设备过了、哪些挂了、挂在哪一轮。report.py就是把这个手工活自动化了。脚本逻辑很简单:遍历logs目录下按设备编号分组的所有日志,检查每一轮是否有正常完成的标记,最后生成一个CSV格式的汇总表,包含设备ID、总轮次、完成轮次、失败轮次、首次失败轮次和状态结论。
def generate_report(log_root: Path, output_path: Path) -> None: rows = [] for device_log_dir in sorted(log_root.iterdir()): if not device_log_dir.is_dir(): continue device_id = device_log_dir.name total_cycles, finished, failed, first_failed = analyze_device_log(device_log_dir) status = "PASS" if failed == 0 else "FAIL" rows.append({ "device": device_id, "total_cycles": total_cycles, "finished": finished, "failed": failed, "first_failed_cycle": first_failed, "status": status, }) write_csv(rows, output_path)汇总表一键生成,看到FAIL的设备再单独去翻对应日志,排查效率比人工翻日志高了一个数量级。
4. 任务编排与自动化集成
4.1 定时任务让老化测试无人值守
脚本本身解决了“怎么跑”的问题,但真正的无人值守还差一块拼图:定时拉起。老化测试经常要安排在夜间执行,白天设备还要做其他功能测试。这时候Linux环境用crontab,Windows环境用计划任务,把precheck.sh和run_burnin.py串成一条命令链。
Linux下的定时任务配置示例:
# 每晚22:00执行预检,预检通过后启动老化测试 0 22 * * * cd /opt/pd2bs-scripts && ./scripts/precheck.sh && python3 scripts/run_burnin.py >> logs/cron.log 2>&1这里有个小技巧:cd /opt/pd2bs-scripts不能省。crontab执行命令时的当前目录默认是用户主目录,如果脚本内部用了相对路径访问config目录,不先cd过去必然会报文件找不到。这个问题几乎每个用crontab跑脚本的人都踩过,提前养成交作业系统里写绝对路径的习惯能省很多排查时间。
Windows环境用任务计划程序,操作路径是:创建基本任务 → 触发器选每天22:00 → 操作选启动程序 → 程序填powershell.exe,参数填-ExecutionPolicy Bypass -File C:\pd2bs-scripts\scripts\run_burnin.ps1。注意-ExecutionPolicy Bypass这个参数,Windows PowerShell默认执行策略是Restricted,跑未签名的脚本文件会被拦下来,必须显式指定Bypass或RemoteSigned才能放行。这是热词列表中“无法加载文件因为禁止运行脚本”这类报错的标准解法,后面排查章节还会详细说。
4.2 与CI/CD流水线集成
如果团队已经有了一套CI系统(GitLab CI、Jenkins等都行),pd2bs-scripts也可以作为流水线中的一个测试阶段来运行。这种方式的价值在于:把老化测试纳入产品发布前的自动门禁,任何一次代码合并或固件更新都自动触发一轮老化验证,通过才允许继续发布流程。
以GitLab CI为例,在.gitlab-ci.yml中定义一个老化测试job:
burnin_test: stage: test script: - bash scripts/precheck.sh - python3 scripts/run_burnin.py - python3 scripts/report.py artifacts: paths: - logs/ - reports/ expire_in: 7 days将logs和reports目录作为流水线产物保存,这样每次跑完的测试结果都自动归档到CI系统,后续追溯问题、对比不同版本的稳定性数据,都直接从CI端下载,不用再跑现场去拷日志。
4.3 多设备并行调度的经验
pd2bs本身支持单条命令指定一个设备,但如果有多块设备要同时测,脚本层面就要考虑并行度控制。直接for循环串行跑当然最简单,但每轮耗时乘以设备数量,总时间会非常长。翻经验来看,合理的做法是用Python的ThreadPoolExecutor按设备维度并行发起pd2bs进程,同时控制最大并发数——一般建议同时跑4到6台,超过这个数字,主机的CPU和IO会成为瓶颈,反而拖慢整体效率。
from concurrent.futures import ThreadPoolExecutor, as_completed with ThreadPoolExecutor(max_workers=cfg.get("max_parallel", 4)) as executor: future_map = { executor.submit(run_single_device, dev, cfg): dev for dev in alive_devices } for future in as_completed(future_map): dev = future_map[future] try: success = future.result() stats[dev]["finished" if success else "failed"] += 1 except Exception as e: logging.error(f"[{dev}] 执行异常: {e}") stats[dev]["failed"] += 1线程数量不是越大越好,这算是并行调度中最容易栽的坑。我自己试过把并发拉到8,结果设备侧倒是没问题,主机的CPU使用率飙到90%以上,反而导致部分pd2bs进程响应变慢,个别设备出现了不明原因的执行超时。后来压回4并发,一切都正常了。这个经验具体数值可能因主机配置而异,但思路是通用的:调并发数要用压测的眼光去测,看效果曲线找拐点,而不是想当然地给个很大的数。
5. 常见问题与排查技巧实录
5.1 PowerShell禁止运行脚本
很多用Windows做自动化的人,第一次跑.ps1脚本都会撞见那句魔性的报错:“无法加载文件 xxx.ps1,因为在此系统上禁止运行脚本。有关详细信息,请参阅 https:/go.microsoft.com/fwlink/?LinkID=135170 中的 about_Execution_Policies。”
原因就是PowerShell的默认执行策略是Restricted,所有.ps1脚本都被视为不可信。解决办法有两种。一种是一劳永逸地把当前用户的执行策略改成RemoteSigned:
Set-ExecutionPolicy -Scope CurrentUser RemoteSignedRemoteSigned意味着本地创建的脚本可以运行,从网络上下载的脚本必须带数字签名才能运行,安全性和便利性兼顾,是最推荐的选项。
另一种是只在执行单条命令时临时放行,不修改系统策略:
powershell.exe -ExecutionPolicy Bypass -File .\scripts\run_burnin.ps1这种方式适合偶尔跑一次、不想动系统设置的场景。需要注意的坑是:Set-ExecutionPolicy改的是注册表里的持久配置,有些团队机器上有组策略锁定了这项设置,本地执行时会报“拒绝访问”,那种情况只能用-ExecutionPolicy Bypass方式绕过去。
5.2 找不到命令/脚本文件不存在
热词列表中频繁出现的“claude、git、opencode、pnpm、mvn无法识别”本质上是一个问题:命令所在的目录不在系统PATH环境变量中。常见于新装的软件、或者是用Node.js包管理器安装的CLI工具,安装路径没有被自动加入PATH。
排查思路分三步。第一步,确认软件到底装没装,Windows下看安装目录是否存在,Linux下用which或whereis找一下:
which pd2bs whereis pd2bs如果命令存在但不在PATH里,输出会显示完整路径。第二步,确认输出路径是否在PATH环境变量里:
echo $PATHWindows PowerShell则用:
$env:Path -split ";"第三步,把缺少的路径加进PATH。Linux临时生效是export PATH=$PATH:/opt/pd2bs/bin,永久写入~/.bashrc或/etc/profile.d/xxx.sh。Windows在系统设置里编辑环境变量界面添加路径即可,但要注意添加后必须重新打开终端窗口才生效,已经打开的老窗口不会自动刷新环境变量,这一点经常让人误以为没改成功。
5.3 设备掉线导致测试中断
用pd2bs跑长时间老化时,最常见的失败模式是设备在深夜掉线,然后整批测试卡住或者退出。排查掉线原因要分物理层和系统层。物理层先看连接线接口是否松动,老化测试期间设备有震动,连接线很容易松脱;系统层要看设备是否因为过热或驱动异常导致系统挂死。
脚本侧的建议是两层防护。第一层是run_burnin.py里每轮开始前的动态设备探测,掉线就跳过不阻塞整批;第二层是monitor.py持续记录设备状态变化,掉线时间点、恢复时间点都有据可查,事后分析原因定位问题非常有用。很多团队的设备掉线问题长期说不清楚,就是因为没有这种连续的状态监控记录。
5.4 日志占用空间过大
老化测试动辄数十轮,每轮每台设备都会产生几百MB日志,logs目录的膨胀速度快得惊人。磁盘满时pd2bs会直接写不进日志,进程崩溃。建议在precheck.sh里加一个磁盘空间检查,低于阈值时直接拒绝启动测试。
另一个实用做法是定期清理历史日志,保留最近两个批次的完整日志,更早的打包压缩后转存到文件服务器。这个策略可以用一个简单的crontab任务实现:
# 每天凌晨4点清理7天前的日志,保留压缩包 30 4 * * * find /opt/pd2bs-scripts/logs -name "*.log" -mtime +7 -exec gzip {} \;压缩后的日志直接改成.gz后缀,report.py读取时做一个透明解压处理就行。老化测试日志一般是文本密集型,gzip压缩率通常在80%以上,一个1GB的日志压完不到200MB,对存储的友好程度不言而喻。
6. 老测试工程师的几点体会
用pd2bs-scripts跑了小半年,最深的感受是:老化测试的自动化并不难,真正难的是把各种边界情况纳入考虑。刚开始只写了run_burnin.py一个脚本,跑起来发现设备掉线没人管、日志多了磁盘爆掉、定时任务里相对路径找不到文件,一个个问题往外冒,才逐步补上了precheck、monitor、report这几个配套模块。现在回头看,四个脚本各司其职,缺一个都有明显的痛点。
一个小技巧作为收尾分享:在配置里加一个batch_name字段,每批测试手动指定一个批次名(比如20260610_batch1),日志和报告的归档路径自动带上这个批次名。别小看这个细节,当测试跨月积累、需要回溯某批设备的数据时,批次名比时间戳好用得多——时间戳只能告诉你什么时候跑的,批次名还能传达这一批是谁在什么场景下安排的,追溯效率完全不是一个级别。
本文还有配套的精品资源,点击获取