TDengine SQL 模糊测试工具 tdsqlsmith 使用与原理详解:SQL 生成、崩溃保护与报告重放
【免费下载链接】TDengineHigh-performance, scalable time-series database designed for Industrial IoT (IIoT) scenarios项目地址: https://gitcode.com/GitHub_Trending/tde/TDengine
导读
tdsqlsmith是 TDengine 仓库中面向 SQL 稳定性的模糊测试(fuzz testing)工具,位于 test/go-sql-fuzz-test/tdsqlsmith,用于面向 TDengine 自动生成海量 SQL 语句并执行,以发现解析器、执行器在极端输入下的崩溃与错误。本文将以官方 README 为主线,结合其入口、配置解析、查询生成、监督进程与报告重放等源码实现,完整讲解run / serve / replay三类入口的用法、关键参数语义、底层工作原理与常见运维方式。读完本文,你将掌握如何构建并运行一次 SQL 稳定性测试、如何解读run_report.json中的崩溃事件,以及如何通过 Web 服务与重放命令复现和验证崩溃。
1. 项目概览:三类入口
tdsqlsmith提供三个子命令入口(详见 cmd/tdsqlsmith/main.go 的参数分发逻辑):
run:执行测试任务并产出run_report.json。它由 supervisor 与 worker 两级进程组成,worker 被作为子进程反复拉起,崩溃后自动重启并从崩溃点恢复。serve:启动 API + Console 服务,通过 HTTP JSON API 暴露历史运行报告,并托管内嵌的 Vue3 前端控制台。replay:从运行报告中提取崩溃 SQL,重放执行以复现事件。
当前版本已将语料和规则内置到程序中(internal/corpusdata与internal/queryrules等包),运行时不再依赖外部sqlparse仓库目录。工具模块划分为 Go 后端与 Vue3 前端两大部分,语料以go:embed编译进二进制,前端构建产物同样通过go:embed内嵌(见 internal/serve/serve.go)。
2. 项目目录与作用
| 目录/文件 | 作用 |
|---|---|
cmd/tdsqlsmith/ | 命令行入口,解析参数并分发到run/replay/serve。 |
internal/run/ | 核心运行流程(任务执行、覆盖统计、报告写入、崩溃处理)。 |
internal/serve/ | Web 服务层,提供 API 与前端静态资源服务。 |
internal/queryrules/ | 查询规则目录解析与规则命中跟踪。 |
internal/branchmodel/ | 分支用例类型定义与覆盖模型。 |
internal/corpusdata/ | 内置语料与语法文件(通过go:embed编译进程序)。 |
internal/report/ | 运行报告数据结构与读写。 |
internal/crashguard/ | 崩溃保护、快照与故障上下文记录。 |
web/console/ | 前端控制台源码(Vue3 + TypeScript)。 |
internal/serve/webdist/ | 前端构建产物目录(供后端嵌入,默认不提交生成文件)。 |
run_parent_child_test.sh | 长时运行脚本,统一参数并产出会话日志/报告。 |
run_web_service.sh | Web 服务启停与状态管理脚本。 |
Makefile | 统一的初始化、构建、打包命令入口。 |
bin/ | 本地编译产物和打包文件输出目录。 |
out/ | 运行时报告与日志输出目录。 |
除 README 列出的目录外,仓库中还包含internal/catalog(共享 catalog 引导与建表)、internal/config(命令行参数解析与校验)、internal/executor(SQL 执行器与结果分类)、internal/querygen(随机 SQL 生成器)、internal/replay(重放实现)、internal/taosdwatch(taosd 进程监视与恢复)、internal/parsergate(SQL 解析门禁)、internal/random(可序列化随机数)与internal/logger、internal/impedance等支撑包,共同构成完整的生成-解析-执行-报告链路。
3. 快速开始
3.1 初始化依赖
make init对应 Makefile 中的实现,该命令执行两部分工作:
go mod download:拉取 Go 模块依赖;cd web/console && npm ci --include=dev:按package-lock.json精确安装前端依赖(包含 dev 依赖,供 Vite 构建使用)。
3.2 构建
make build构建结果(对应 Makefile):
- 后端二进制:
bin/tdsqlsmith(go build -o bin/tdsqlsmith ./cmd/tdsqlsmith); - 前端静态资源:
internal/serve/webdist/(npm exec vite -- build --outDir ../../internal/serve/webdist),该目录将被go:embed编译进后端。
注意构建顺序:先构建前端产物、再编译 Go 二进制,否则内嵌的前端资源会是旧版本。
3.3 打包分发
make package输出(对应 Makefile):
bin/tdsqlsmith-<timestamp>.tar.gz(时间戳格式为YYYYMMDD_HHMMSS);- 包内包含:
tdsqlsmith二进制、run_parent_child_test.sh、run_web_service.sh,并对三个文件赋予可执行权限后压缩。
4. 命令行用法
tdsqlsmith run [flags] tdsqlsmith serve [flags] tdsqlsmith replay [flags]可执行:
./bin/tdsqlsmith --help工具还兼容 go-sqlsmith 的经典无子命令模式(第一参数以--开头时自动进入 legacy 模式,见 internal/config/config.go),例如:
tdsqlsmith --target=... --max-queries=1000 --verbose4.1 run 子命令参数
run是核心子命令,完整参数定义在 internal/config/config.go,含义与默认值如下:
| 参数 | 默认值 | 说明 |
|---|---|---|
--dsn | root:taosdata@tcp(127.0.0.1:6030)/ | TDengine 连接串(--target是其 sqlsmith 兼容别名)。 |
--seed | 当前时间纳秒 | 随机数种子,用于可复现的生成序列。 |
--rng-state | 空 | 反序列化 RNG 状态,覆盖 seed 位置,用于确定性恢复。 |
--cases | 2000 | 生成的查询条数(--max-queries为其 sqlsmith 兼容别名);0 表示仅按时长限制。 |
--duration | 0 | 运行时长,如10m;0 表示仅按条数限制。 |
--stmt-timeout | 2s | 单条 SQL 执行超时。 |
--out-dir | out | 运行产物输出目录。 |
--cleanup-success-run-dir | true | 干净退出时清理临时子进程日志(报告始终保留)。 |
--mutation-level | 1 | SQL 变异强度,取值范围 [0,3]。 |
--stop-when-covered | true | 所有必需查询规则都被覆盖后提前停止。 |
--dry-run | false | 仅做解析门禁(parse-gate),跳过 TDengine 执行。 |
--verbose | false | 向 stderr 输出详细进度。 |
--dump-all-queries | false | 打印/记录每一条生成的查询。 |
--dump-all-graphs | false | 将每条语句的 AST 导出为 graphml 图。 |
--exclude-catalog | false | 保留的兼容性选项。 |
--config | 空 | workload TOML 配置路径(go-sqlsmith 风格,见下)。 |
--exec-profile | strict | 执行档位:strict\|balanced\|aggressive。 |
参数校验要点(从源码可见):--cases必须 ≥ 0;--cases与--duration同时为 0 时回退到 2000 条;--stmt-timeout必须 > 0;--mutation-level必须在 [0,3];--exec-profile必须是三者之一。
--config指向的 workload 配置文件用于控制各类语句的生成权重,仓库提供了 cmd/config.example.toml 示例,其中dml-select权重最高(240),ddl-alter-table、ddl-create-index、dml-delete、dml-update、dml-insert均为 10~30,txn-*权重为 0(TDengine 不适用事务),供按需调整生成分布。
4.2 serve 子命令参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--listen | :8080 | 监听地址。 |
--api-token | tdsqlsmith-dev-token(或环境变量TDSQLSMITH_API_TOKEN) | API 鉴权 bearer token。 |
--data-dir | data | 服务状态数据目录。 |
--out-dir | out | 运行报告输出目录。 |
--allow-origin | * | CORSAccess-Control-Allow-Origin取值。 |
4.3 replay 子命令参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--dsn | root:taosdata@tcp(127.0.0.1:6030)/ | 目标数据库连接串。 |
--file | 必填 | run_report.json路径。 |
--count | 1 | 崩溃语句的执行次数。 |
--stmt-timeout | 2s | 单条语句执行超时。 |
5. SQL 生成原理:从 AST 到覆盖标签
了解生成器实现能帮助你判断run的参数与报告的含义。核心生成器位于 internal/querygen/generator.go,它基于 sqlparser 构建随机 AST 再渲染为 SQL 文本,而不是直接拼接字符串。
- 生成约束:默认
MaxDepth=3(查询表达式与表引用的最大嵌套深度)、MaxSelectItems=4(select 列表最大项数)、MaxExprDepth=3(标量表达式最大嵌套深度),见DefaultConfig()。 - 内置 schema:默认绑定三张结构相同的表
t1/t2/t3,每张表 21 个列,覆盖 timestamp、int/bigint/smallint/tinyint(含 unsigned)、float、double、bool、binary、varchar、nchar、varbinary、geometry、decimal 等 TDengine 常见类型,见defaultSchema()。 - 生成策略:约 18% 概率生成 INSERT,其余生成查询表达式;查询可随机附加
ORDER BY、SLIMIT、LIMIT子句,深度允许时还会生成UNION [ALL]与子查询(见Next()与queryExpressionAST)。 - 解析门禁:每次生成尝试都会把 SQL 交给
parsergate.Parse校验,最多重试 16 次,返回第一条能干净解析的语句(见 generator.go)。 - 覆盖标签:每条语句在生成过程中记录命中的语法标签(如
query_expression、union_query_expression、subquery),经过去重排序后作为Tags返回,供 query-rule 覆盖率统计使用。
运行循环(internal/run/run.go)在每次迭代中按rule_seed(针对缺失规则定向生成)与query_random(随机生成)两种策略生成语句,依次记录待执行语句快照、解析、按--exec-profile判断是否执行、执行并分类结果(OK / DBError / Timeout / ConnLost / Fatal),同时滚动保留最近 64 条已执行语句、每 20 条查询记录一次覆盖进度点、周期性刷写最小运行报告。
6. run_parent_child_test.sh:一键长时测试
该脚本用于快速启动一次带统一参数的run任务,并把输出集中到单独目录。
6.1 用法
./run_parent_child_test.sh <duration>示例:
./run_parent_child_test.sh 30s ./run_parent_child_test.sh 10m ./run_parent_child_test.sh 2h脚本只接受一个位置参数(时长),并会拒绝额外的参数(见 run_parent_child_test.sh)。
6.2 环境变量
| 变量 | 默认值 | 说明 |
|---|---|---|
TDSQLSMITH_BIN | ${ROOT_DIR}/tdsqlsmith | 二进制路径或命令名 |
DSN | root:taosdata@tcp(127.0.0.1:6030)/ | 连接串 |
STMT_TIMEOUT | 2s | 单条 SQL 超时 |
MUTATION_LEVEL | 1 | SQL 变异强度 |
EXEC_PROFILE | balanced | 执行策略(strict/balanced/aggressive) |
CHILD_CASES | 1000000000 | 生成条数上限 |
6.3 固定附加参数
脚本会固定传入(源码中硬编码,见 run_parent_child_test.sh):
--cleanup-success-run-dir=true--stop-when-covered=false(不因覆盖目标提前结束,适合长时间稳定运行)--verbose
同时通过环境变量TDSQLSMITH_RUN_ID与TDSQLSMITH_RUN_DIR将 run ID 与输出目录固定为本次会话目录,保证日志与报告一一对应。
6.4 产物路径
out/pc_YYYYMMDD_HHMMSS/parent_child.log(会话日志,同步落盘,可用tail -f跟进)out/pc_YYYYMMDD_HHMMSS/run_report.json(运行报告)
脚本还负责校验二进制可执行性(找不到时回退到 PATH 查找),并以脚本所在目录作为ROOT_DIR,因此可以从任意目录调用。
7. run_web_service.sh:Web 服务启停管理
用于启动和管理tdsqlsmith serve。
7.1 用法
./run_web_service.sh [start|stop|status|restart] [--daemon]7.2 常用示例
# 前台启动 ./run_web_service.sh # 后台启动 ./run_web_service.sh start --daemon # 查看状态 ./run_web_service.sh status # 停止 ./run_web_service.sh stop7.3 主要环境变量
| 变量 | 默认值 | 说明 |
|---|---|---|
TDSQLSMITH_BIN | tdsqlsmith | 二进制路径或命令名 |
LISTEN | 0.0.0.0:18080 | 监听地址 |
API_TOKEN | tdsqlsmith-dev-token | API token |
DATA_DIR | $(pwd)/data | 服务状态目录 |
OUT_DIR | $(pwd)/out | 报告目录 |
ALLOW_ORIGIN | * | CORS 来源 |
LOG_FILE | $(pwd)/tdsqlsmith-web.log | 后台日志文件 |
PID_FILE | $(pwd)/tdsqlsmith-web.pid | 后台 PID 文件 |
PUBLIC_HOST | 空 | 可选,外部健康检查提示用公网 IP,如43.130.228.76 |
脚本的进程管理细节(见 run_web_service.sh)值得注意:
- 健康检查:通过
curl请求http://<host>:<port>/api/v1/health并匹配"status":"ok"判断服务就绪;0.0.0.0/::等监听地址会被归一化为127.0.0.1做本地检查。 - PID 多重发现:依次从 PID 文件、监听端口(
ss或lsof)、进程命令行(ps中同时匹配二进制名、serve与--listen=)三种途径发现运行中进程,支持 PID 文件丢失/过期后的恢复。 - 后台启动:使用
nohup ... &启动,写 PID 文件并轮询健康接口(30 次 × 0.1s)确认启动成功。 - 前台模式:直接
exec替换当前 shell 进程,便于在终端直接观察日志。
8. serve 的 API 与前端
serve通过 internal/serve/serve.go 注册如下路由(见registerRoutes):
| 路由 | 作用 |
|---|---|
/api/v1/health | 健康检查,返回{"status":"ok"}。 |
/api/v1/auth/verify | token 校验。 |
/api/v1/reports | 报告列表。 |
/api/v1/reports/{id} | 按 run ID 获取单份报告详情。 |
/ | 内嵌的前端静态资源(Vue3 控制台,登录、报告列表与详情页)。 |
服务默认--listen :8080(脚本包装时改为0.0.0.0:18080),API 需要携带--api-token指定的 bearer token;日志中 token 会被脱敏(保留前 3 位与后 2 位,见maskToken)。服务优雅停机:收到 SIGINT/SIGTERM 后最多等待 8 秒完成现有请求再关闭(serve.go)。
前端源码位于 web/console,包含登录页、报告列表页(ReportsView.vue)与报告详情页(ReportDetailView.vue),构建产物输出到internal/serve/webdist/后由go:embed内嵌。
9. replay:复现崩溃 SQL
replay从指定的run_report.json中挑选最近一次含非空崩溃 SQL 的事件(优先 taosd 事件,其次 tdsqlsmith 事件),先执行报告记录的 setup SQL 复现环境,再按--count次重放该崩溃语句,并返回每次执行的分类、耗时与错误信息(实现见 internal/replay/replay.go)。
tdsqlsmith replay --file out/pc_YYYYMMDD_HHMMSS/run_report.json --count 5典型用途:run阶段发现崩溃后,用replay在修复前后的 taosd 上反复重放同一 SQL,验证问题是否复现/是否已修复。
10. 崩溃保护:supervisor、crashguard 与 taosdwatch
这是run命令稳定长跑的关键机制,也是 README 所述“崩溃处理”的底层实现:
- 父子进程结构:
run启动时,父进程作为 supervisor 引导共享 catalog 后,反复以子进程方式拉起 fuzz worker(通过环境变量TDSQLSMITH_RUN_WORKER=1标记 worker 身份)。worker 因信号异常退出后,supervisor 分析退出信号并写入崩溃报告,随后按退避间隔(默认 500ms)重启 worker(见 cmd/tdsqlsmith/main.go)。 - 崩溃识别:
classifyWorkerExit区分退出码、信号名与是否产生 core dump;isCrashSignalName识别 SIGSEGV(segmentation fault)、SIGABRT(aborted)、SIGBUS(bus error)、SIGILL、SIGFPE 等真实崩溃信号。 - 崩溃点恢复:crashguard 在每条语句执行前后持久化“待执行语句 + 序列化 RNG 状态 + 查询号”快照。worker 崩溃后,supervisor 从快照提取
query_no与rng_state,通过环境变量注入重启的 worker,使其从崩溃语句的下一句继续执行,保证长时运行的连续性。 - 运行目录产物:运行目录下的
crash_guard/保存pending.json、window.json、status.json、report.latest.json;崩溃时 supervisor 额外写出coredump_report.json与人类可读的crash_summary.md(含 pending SQL、前序窗口与错误信息)。干净退出且无崩溃时,这些临时文件会被自动清理。 - taosd 崩溃分流:
internal/taosdwatch负责判断错误是否由 taosd 崩溃引起。连接丢失且检测到 taosd 进程退出/core dump 时,事件会被记录到报告的taosd_incidents;只有 worker 侧段错误等且无 taosd 证据的事件才记入tdsqlsmith_incidents。检测到 taosd 崩溃后还会尝试重启并自动重连(recoverConnection)。 - 软/硬截止时间:
--duration是软截止;supervisor 会在软截止之上再追加 15 秒宽限期(supervisorWorkerDeadlineGrace)作为硬截止,强制杀死卡住的 worker。
run_report.json的MinimalRunReport结构(见 internal/report/report.go)聚合了 run ID、开始/生成时间、执行时长、setup SQL、已执行总数、query-rule 覆盖与进度、查询组合计数、taosd 事件与 tdsqlsmith 事件列表,是后续分析与 replay 的唯一数据源。
11. 测试命令
# 默认全量测试 go test ./... # 含 integration tag 的测试 go test -tags=integration ./...后者会启用依赖真实环境的集成用例,例如taosd_crash_report_integration_test.go、taosd_crash_signal_integration_test.go(位于 internal/run),用于验证崩溃检测与报告写入链路。
12. 使用注意事项
internal/serve/webdist下的前端编译产物不建议入库;仓库仅保留占位文件用于go:embed编译。修改前端源码后需重新make build。- 若
run无法连接数据库,请先确认taosd处于可用状态并且 DSN 正确(默认连接127.0.0.1:6030,账号root,密码taosdata)。 - 长时间运行建议通过
run_parent_child_test.sh统一入口执行,它已内置 supervisor 需要的 run ID/目录注入与日志落盘;stop-when-covered=false保证覆盖目标不会提前中断稳定性测试。 serve默认 token 为开发用途的tdsqlsmith-dev-token,部署到可被外部访问的地址时应通过--api-token或TDSQLSMITH_API_TOKEN显式更换。- 由于 crashguard 依赖 pending SQL 与 RNG 状态快照才能恢复,请勿在
run运行期间手工清理其运行目录;报告始终保留,临时快照只在干净退出后自动清理。
【免费下载链接】TDengineHigh-performance, scalable time-series database designed for Industrial IoT (IIoT) scenarios项目地址: https://gitcode.com/GitHub_Trending/tde/TDengine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考