from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
from tensorflow_serving.apis import predict_pb2
from tensorflow_serving.apis import prediction_service_pb2_grpc
from statistics import mean
def debug_print(message, debug=False):
def generate_single_request(batch_size=1):
request = predict_pb2.PredictRequest()
request.model_spec.name = 'wdl'
request.model_spec.signature_name = "serving_default"
request.inputs[f"I{i}"].CopyFrom(
tf.make_tensor_proto(np.random.uniform(0, 1, [batch_size, 1]), dtype=tf.float32)
categories = [f"cat_{i}_{j}" for j in range(5)]
batch_values = [np.random.choice(categories) for _ in range(batch_size)]
request.inputs[f"C{i}"].CopyFrom(
tf.make_tensor_proto(batch_values, dtype=tf.string)
def generate_all_requests(num_requests, batch_size=1, num_workers=None, debug=False):
num_workers = min(multiprocessing.cpu_count(), num_requests)
debug_print(f"使用 {num_workers} 个进程并行生成请求数据...", debug)
requests_per_worker = [num_requests // num_workers] * num_workers
for i in range(num_requests % num_workers):
requests_per_worker[i] += 1
with ProcessPoolExecutor(max_workers=num_workers) as executor:
for count in requests_per_worker:
seed = np.random.randint(0, 1000000)
future = executor.submit(
_worker_generate_requests, count, batch_size, seed
requests.extend(future.result())
def _worker_generate_requests(num_requests, batch_size, seed):
return [generate_single_request(batch_size) for _ in range(num_requests)]
async def get_request(requests, request_rate, burstiness, max_batches, batch_counter, counter_lock, start_time, duration, debug=False):
assert burstiness > 0, "Burstiness factor must be positive."
theta = 1.0 / (request_rate * burstiness)
total_requests = len(requests)
current_time = time.time()
elapsed_time = current_time - start_time
if elapsed_time > duration:
debug_print(f"[get_request] 时间限制触发: {elapsed_time:.4f}s > {duration}s", debug)
async with counter_lock:
if request_idx >= total_requests or batch_counter[0] >= max_batches:
reason = "请求索引耗尽" if request_idx >= total_requests else "批次计数达到上限"
debug_print(f"[get_request] 计数器限制触发: {reason}, 批次计数: {batch_counter[0]}/{max_batches}", debug)
current_request = requests[request_idx]
if request_rate != float("inf"):
interval = np.random.gamma(shape=burstiness, scale=theta)
await asyncio.sleep(interval)
def call_grpc_stub(stub, request):
start_time = time.time()
result = stub.Predict(request, 10.0)
return end_time - start_time
class ConcurrencyMonitor:
def __init__(self, max_concurrency):
self.max_concurrency = max_concurrency
self.lock = asyncio.Lock()
async def increment(self):
if self.current > self.highest:
self.highest = self.current
async def decrement(self):
async def get_current(self):
async def get_highest(self):
async def concurrency_monitor(monitor, interval=3, debug=False):
current = await monitor.get_current()
highest = await monitor.get_highest()
print(f"[监控] 当前并发数: {current}/{monitor.max_concurrency}, 历史最高: {highest}")
await asyncio.sleep(interval)
except asyncio.CancelledError:
debug_print("[监控] 监控任务已停止", debug)
debug_print(f"[监控] 监控任务异常: {e}", debug)
def __init__(self, max_workers, stub, request_generator, semaphore, latencies, monitor, loop, start_time, duration, debug=False):
self.max_workers = max_workers
self.request_generator = request_generator
self.semaphore = semaphore
self.latencies = latencies
self.start_time = start_time
self.duration = duration
self.queue = asyncio.Queue()
for i in range(min(max_workers, 5)):
def _create_worker(self):
worker_id = len(self.workers) + 1
task = asyncio.create_task(self._worker_loop(worker_id))
self.workers.append(task)
debug_print(f"[WorkerPool] 创建工作协程 {worker_id}, 总数: {len(self.workers)}", self.debug)
async def _worker_loop(self, worker_id):
debug_print(f"[worker-{worker_id}] 启动", self.debug)
request = await asyncio.wait_for(self.queue.get(), timeout=1.0)
current_time = time.time()
if current_time - self.start_time > self.duration:
debug_print(f"[worker-{worker_id}] 退出: 时间限制", self.debug)
await self.monitor.increment()
async with self.semaphore:
remaining_time = self.duration - (time.time() - self.start_time)
debug_print(f"[worker-{worker_id}] 请求剩余时间不足: {remaining_time:.4f}s", self.debug)
latency = await asyncio.wait_for(
self.loop.run_in_executor(ThreadPoolExecutor(), call_grpc_stub, self.stub, request),
self.latencies.append(latency)
debug_print(f"[worker-{worker_id}] 完成请求, 延迟: {latency:.4f}s", self.debug)
except asyncio.TimeoutError:
debug_print(f"[worker-{worker_id}] 请求超时: 剩余时间 {remaining_time:.4f}s", self.debug)
debug_print(f"[worker-{worker_id}] 请求异常: {e}", self.debug)
await self.monitor.decrement()
except asyncio.TimeoutError:
async def submit(self, request):
if self.queue.qsize() >= len(self.workers) and len(self.workers) < self.max_workers:
await self.queue.put(request)
for _ in range(len(self.workers)):
await self.queue.put(None)
await asyncio.gather(*self.workers, return_exceptions=True)
debug_print(f"[WorkerPool] 所有工作协程已停止, 总数: {len(self.workers)}", self.debug)
async def async_call_grpc(stub, requests, request_rate, burstiness, max_concurrency, duration, max_batches, debug=False):
start_time = time.time()
semaphore = asyncio.Semaphore(max_concurrency)
loop = asyncio.get_running_loop()
counter_lock = asyncio.Lock()
stop_event = asyncio.Event()
request_generator = get_request(requests, request_rate, burstiness, max_batches, batch_counter, counter_lock, start_time, duration, debug)
concurrency_monitor_obj = ConcurrencyMonitor(max_concurrency)
monitor_task = asyncio.create_task(concurrency_monitor(concurrency_monitor_obj, 3, debug))
worker_pool = WorkerPool(
max_workers=max_concurrency,
request_generator=request_generator,
monitor=concurrency_monitor_obj,
async for request in request_generator:
if time.time() - start_time > duration or stop_event.is_set():
await worker_pool.submit(request)
debug_print(f"[feeder] 异常: {e}", debug)
await worker_pool.close()
feeder_task = asyncio.create_task(feeder())
await asyncio.gather(feeder_task, return_exceptions=True)
highest_concurrency = await concurrency_monitor_obj.get_highest()
debug_print(f"[async_call_grpc] 最高并发数: {highest_concurrency}/{max_concurrency}", debug)
valid_latencies = [t for t in latencies if t <= duration]
return batch_counter[0], valid_latencies, highest_concurrency
parser = argparse.ArgumentParser(description='TensorFlow Serving 压测工具')
parser.add_argument('--request-rate', type=float, default=10.0, help='目标QPS')
parser.add_argument('--burstiness', type=float, default=1.0, help='突发因子')
parser.add_argument('--duration', type=float, default=60.0, help='压测时长(秒)')
parser.add_argument('--server', type=str, default='localhost:8500', help='服务地址')
parser.add_argument('--num-requests', type=int, default=1000, help='总请求批次数量')
parser.add_argument('--max-concurrency', type=int, default=50, help='最大并发数')
parser.add_argument('--batch-size', type=int, default=1, help='每个请求的样本批量大小')
parser.add_argument('--processes', type=int, default=None, help='生成请求的进程数')
parser.add_argument('--debug', action='store_true', help='启用调试信息和监控')
args = parser.parse_args()
print(f" 请求速率: {args.request_rate} QPS")
print(f" 压测时长: {args.duration} 秒")
print(f" 最大请求数: {args.num_requests} 批次")
print(f" 最大并发数: {args.max_concurrency}")
print(f" 批量大小: {args.batch_size}")
print(f" 目标服务器: {args.server}")
print(f" 调试模式: {'启用' if args.debug else '禁用'}")
generate_count = max(args.num_requests, args.max_concurrency * 2)
print(f"正在生成 {generate_count} 条请求数据,批量大小为 {args.batch_size}...")
requests = generate_all_requests(generate_count, args.batch_size, args.processes, args.debug)
print(f"请求数据生成完成,耗时 {time.time()-start_gen:.2f} 秒")
channel = grpc.insecure_channel(args.server)
stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)
print(f"开始压测,目标QPS={args.request_rate}, 并发数={args.max_concurrency}, 批量大小={args.batch_size}")
start_time = time.time()
completed_batches, latencies, highest_concurrency = await async_call_grpc(
stub, requests, args.request_rate, args.burstiness,
args.max_concurrency, args.duration, args.num_requests,
total_time = max(time.time() - start_time, 0.001)
throughput = completed_batches / total_time
p99_latency = np.percentile(latencies, 99) if latencies else 0
avg_latency = np.mean(latencies) if latencies else 0
total_samples = completed_batches * args.batch_size
samples_per_second = total_samples / total_time
print("\n============ 压测结果 ============")
print(f"完成请求数(批次): {completed_batches} / {args.num_requests}")
print(f"完成样本数: {total_samples}")
print(f"总耗时: {total_time:.2f} 秒")
print(f"请求吞吐量 (QPS): {throughput:.2f}")
print(f"样本吞吐量 (样本/秒): {samples_per_second:.2f}")
print(f"平均延迟: {avg_latency:.4f} 秒")
print(f"P99 延迟: {p99_latency:.4f} 秒")
print(f"最大并发数: {args.max_concurrency}")
print(f"实际最高并发数: {highest_concurrency}")
print(f"批量大小: {args.batch_size}")
if __name__ == "__main__":
if hasattr(multiprocessing, 'freeze_support'):
multiprocessing.freeze_support()