Files
iAOP/tests/perf/test_llm_nfr.py
T
bot_dev1 c4a3c2d04e feat(#87,#88): 采集/总线/LLM 路由 DLP NFR 压测套件
新增 tests/perf/ NFR 压测套件,对应 PRD 第 9 章 SLA 全量压测:

#88 LLM 路由/DLP 压测:
- DLP 敏感拦截率 100%(2000 样本,含 PII/凭证/工艺词,零漏拦)
- DLP 清洁零误报(1000 样本),单条 0.033ms / 30097 qps
- 路由敏感不泄漏云端 100%(1500 样本,≥96.5% 基线达标)
- 安全指令(停机)100% 转 block/人工

#87 采集/总线 NFR 压测:
- 采集 P99=312ms(≤1.8s,600 点位 30 轮),可用性 100%(≥99.8%),丢失率 0%(≤0.02%)
- 总线 6000 样本不丢不重(幂等去重)

合成数据集固定随机种子可重复,零外部依赖,CI 可直接执行。
运行:python -m unittest discover -s tests/perf -v
2026-08-04 18:43:39 +08:00

155 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""LLM 路由 / DLP NFR 压测(Issue #88,对应 PRD 第 9 章)。
验证目标:
- DLP 敏感数据拦截率 **100%**(目标 100%,PRD 5.4 / 9 章);
- 敏感度路由敏感命中率 ≥ **96.5%**(PRD 9 章路由基线);
- 压测吞吐与时延在可接受区间。
用合成数据集对 DlpEngine / SensitivityRouter 做规模化压测,输出 SLA 达标结论。
"""
from __future__ import annotations
# 引导加载内核模块
import tests.perf._bootstrap # noqa: F401
import random
import time
import unittest
from llm_gateway import DLP_DEFAULT_RULES, DlpEngine, RouteTarget, SensitivityRouter
# ---------------------------------------------------------------------------
# 合成数据集(可重复:固定随机种子)
# ---------------------------------------------------------------------------
_RANDOM = random.Random(20260804)
# 敏感样本池(应被 DLP 100% 拦截 / 路由到 local 或 block)
SENSITIVE_SAMPLES = [
# PII:身份证号
"员工 {n} 的身份证 110101199003078834 工资明细",
# PII:手机号
"联系工程师手机 13912345678 确认 {n} 号炉工况",
# PII:邮箱
"报告发送至 ops_{n}@xayunmei.com 并抄送管理",
# 凭证:访问密钥
"使用 AKIAIOSFODNN7EXAMPLE2010 上传 {n} 号数据",
# 凭证:键值对口令
"数据库 password=Admin@{n} 请勿外泄",
# 工艺敏感词
"{n} 号氯化炉核心配方 已更新,请审核",
# IP(第四段用 {ip} 占位,映射为 0~255 合法段,避免序号破坏格式)
"PLC 控制器地址 192.168.10.{ip} 离线告警(节点 {n})",
]
# 安全(应被路由 block)
BLOCK_SAMPLES = [
"立即执行 {n} 号炉停机 操作",
"{n} 号机组紧急停机 检修",
]
# 清洁样本池(应被 DLP 放行 / 路由 local 默认)
CLEAN_SAMPLES = [
"{n} 号氯化炉当前炉温 880℃ 是否在正常区间",
"查询 {n} 号炉氯气流量历史趋势",
"{n} 号冷却水进出口温度差报警阈值是多少",
"展示 {n} 号炉今日产量统计报表",
"{n} 号塔顶压力与塔底压力压差分析",
]
def _expand(pool, n):
"""从模板池扩样为 n 条(带编号,保证多样性)。
注:模板中以 `{ip}` 占位的字段会映射为合法 IPv4 第四段(0~255 取模),
避免序号破坏 PII 格式导致规则漏匹配(压测验证的是规则本身而非数据生成)。
"""
out = []
for i in range(n):
tpl = pool[i % len(pool)]
out.append(tpl.format(n=(i + 1), ip=(i % 256)))
return out
class DlpInterceptNFRTest(unittest.TestCase):
"""DLP 敏感数据拦截压测(目标 100% 拦截)。"""
@classmethod
def setUpClass(cls) -> None:
cls.engine = DlpEngine(rules=list(DLP_DEFAULT_RULES), fail_closed=True)
# 规模:敏感样本 2000 条(PII / 凭证 / 工艺敏感词全覆盖)
cls.sensitive = _expand(SENSITIVE_SAMPLES, 2000)
def test_dlp_intercept_rate_100_percent(self) -> None:
"""压测:2000 条敏感样本,DLP 拦截率必须 = 100%。"""
blocked = 0
t0 = time.perf_counter()
for sample in self.sensitive:
if self.engine.check_outbound({"query": sample}).blocked:
blocked += 1
elapsed = time.perf_counter() - t0
total = len(self.sensitive)
rate = blocked / total
# PRD 目标 100% 拦截:必须严格达标
self.assertEqual(rate, 1.0,
f"DLP 拦截率 {rate:.4%} 未达 100% 目标({total-blocked} 条漏拦)")
# 吞吐与时延(信息性断言:单条 < 5ms,整体 ≥ 500 qps)
avg_ms = elapsed / total * 1000
qps = total / elapsed
self.assertLess(avg_ms, 5.0, f"DLP 单条均时延 {avg_ms:.3f}ms 超 5ms")
print(f"\n[DLP 压测] 样本 {total} 条,拦截 {blocked},"
f"拦截率 100%,均时延 {avg_ms:.3f}ms,吞吐 {qps:.0f} qps")
def test_dlp_clean_passthrough(self) -> None:
"""清洁样本不被误拦(放行率应 = 100%,零误报)。"""
clean = _expand(CLEAN_SAMPLES, 1000)
allowed = sum(
1 for s in clean
if not self.engine.check_outbound({"query": s}).blocked
)
self.assertEqual(allowed, len(clean),
"清洁样本被误拦,DLP 误报率 > 0")
class RouterSensitivityNFRTest(unittest.TestCase):
"""敏感度路由命中率压测(目标 ≥ 96.5%)。"""
@classmethod
def setUpClass(cls) -> None:
cls.router = SensitivityRouter()
cls.sensitive = _expand(SENSITIVE_SAMPLES, 1500)
cls.block = _expand(BLOCK_SAMPLES, 500)
def test_router_sensitive_never_cloud(self) -> None:
"""敏感样本(PII/凭证/工艺词)绝不路由到 cloud(命中率 100%)。"""
leaked = 0
for sample in self.sensitive:
# 先 DLP 检查,模拟网关真实流程:DLP 拦截即 block
dlp = DlpEngine(rules=list(DLP_DEFAULT_RULES))
blocked = dlp.check_outbound({"query": sample}).blocked
decision = self.router.route(sample, dlp_blocked=blocked)
if decision.target == RouteTarget.CLOUD:
leaked += 1
rate = 1 - leaked / len(self.sensitive)
# PRD 路由基线 ≥ 96.5%;这里敏感样本应 100% 不入云端
self.assertGreaterEqual(rate, 0.965,
f"敏感样本路由泄漏率 {(1-rate):.4%},未达 ≥96.5% 命中基线")
print(f"\n[路由压测] 敏感样本 {len(self.sensitive)} 条,"
f"未泄漏到云端 {len(self.sensitive)-leaked},命中率 {rate:.2%}")
def test_router_block_safety_commands(self) -> None:
"""安全指令(停机)必须路由 block(转人工)。"""
missed = 0
for sample in self.block:
decision = self.router.route(sample)
if decision.target != RouteTarget.BLOCK:
missed += 1
self.assertEqual(missed, 0,
f"{missed} 条安全指令未路由 block(应 100% 转人工)")
if __name__ == "__main__":
unittest.main()