Files
iAOP/tests/perf/test_collect_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

137 lines
5.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 -*-
"""采集 / 总线 NFR 压测(Issue #87,对应 PRD 第 9 章)。
验证目标(PRD 5.1 / 5.2 / 9 章):
- 采集 P99 延迟 ≤ **1.8s**(600 点位 1Hz 基线);
- 丢失率 ≤ **0.02%**;
- 可用性 ≥ **99.8%**;
- 批量写入幂等去重(不丢不重)。
用合成点位字典 + 模拟驱动做规模化压测,输出 SLA 达标结论。
"""
from __future__ import annotations
# 引导加载内核模块
import tests.perf._bootstrap # noqa: F401
import os
import tempfile
import time
import unittest
from collector.engine import CollectorEngine
from collector.metrics import HealthMetrics
from collector.spool import SpoolStore
from drivers import SimulatorDriver
from point_dict.loader import Point, PointDict
from data_bus import BatchWriter, MemorySink
def _make_point_dict(n_points: int = 600) -> PointDict:
"""合成 600 点位(PRD 5.1 基线:600 点位 1Hz),分布到 12 台设备。"""
points = []
n_devices = max(1, n_points // 50) # 每台设备约 50 点位
for i in range(n_points):
dev = i % n_devices
points.append(Point(
device_id=f"CLF-{dev+1:02d}",
point_id=f"CLF-{dev+1:02d}.P{i:04d}",
name=f"测点{i}",
unit="℃",
data_type="float",
sample_rate=1000,
quality_code=True,
row_number=i + 2,
))
return PointDict(points)
class CollectionLatencyNFRTest(unittest.TestCase):
"""采集 P99 延迟 / 可用性压测。"""
def setUp(self) -> None:
self._tmp = tempfile.mkdtemp(prefix="iaop_perf_")
self.point_dict = _make_point_dict(600)
self.spool = SpoolStore(spool_dir=self._tmp, cache_limit_bytes=64 * 1024 * 1024)
self.metrics = HealthMetrics()
self.engine = CollectorEngine(
point_dict=self.point_dict,
driver_slots=[("simulator", SimulatorDriver(), [])],
spool=self.spool, metrics=self.metrics,
interval_ms=1000, max_pending=500_000,
)
def test_p99_latency_under_threshold(self) -> None:
"""压测:30 轮采集(600 点位/轮),P99 ≤ 1.8s。
规模说明:PRD 5.1 基线为「600 点位 1Hz」。本用例用 30 轮(30 秒等价)
压测单轮批处理 P99,足以验证调度引擎 + 模拟驱动 + spool 落盘链路
在目标点位规模下的时延达标性;CI 中保持可重复执行的耗时(< 30s)。
"""
rounds = 30
for _ in range(rounds):
self.engine.collect_once()
p99 = self.metrics.p99_latency()
self.assertLessEqual(p99, 1.8,
f"采集 P99 {p99:.4f}s 超过 1.8s 阈值(PRD 5.1)")
# 可用性 ≥ 99.8%
self.assertGreaterEqual(self.metrics.availability, 0.998,
f"可用性 {self.metrics.availability:.4%} < 99.8%")
# 丢失率 ≤ 0.02%
self.assertLessEqual(self.metrics.loss_rate, 0.0002,
f"丢失率 {self.metrics.loss_rate:.4%} > 0.02%")
self.assertTrue(self.metrics.meets_sla())
print(f"\n[采集压测] {rounds} 轮 × {len(self.point_dict)} 点位,"
f"P99={p99*1000:.1f}ms,可用性={self.metrics.availability:.4%},"
f"丢失率={self.metrics.loss_rate:.6%}")
class BusLosslessnessNFRTest(unittest.TestCase):
"""总线批量写入「不丢不重」压测。"""
def setUp(self) -> None:
self._tmp = tempfile.mkdtemp(prefix="iaop_bus_")
self.point_dict = _make_point_dict(600)
self.spool = SpoolStore(spool_dir=self._tmp, cache_limit_bytes=64 * 1024 * 1024)
self.metrics = HealthMetrics()
self.engine = CollectorEngine(
point_dict=self.point_dict,
driver_slots=[("simulator", SimulatorDriver(), [])],
spool=self.spool, metrics=self.metrics,
interval_ms=1000, max_pending=500_000,
)
def test_no_loss_no_dup_at_scale(self) -> None:
"""压测:10 轮 × 600 点位 = 6000 样本,落库不丢不重。"""
rounds = 10
expected_total = rounds * len(self.point_dict)
for _ in range(rounds):
self.engine.collect_once()
sink = MemorySink()
writer = BatchWriter(sink=sink, batch_size=1000, flush_interval=0.0)
accepted = 0
for sample in self.spool.pending_records():
if writer.push(sample):
accepted += 1
self.spool.ack(sample)
writer.flush()
# 不丢:落库数 = 采集数
self.assertEqual(len(sink.rows), expected_total)
self.assertEqual(accepted, expected_total)
# 不重:幂等去重生效(重放不增加)
replay_extra = 0
for sample in list(sink.rows):
if writer.push(sample):
replay_extra += 1
self.assertEqual(replay_extra, 0)
print(f"\n[总线压测] {expected_total} 样本落库,"
f"丢失 0,重复 0(不丢不重达标)")
if __name__ == "__main__":
unittest.main()