一个对网络库进行压测统计qps的小程序

该程序实现了一个对TCP网络库进行压力测试的工具,通过生成并发送数据包,统计接收到的响应时间,计算QPS(每秒查询率)以及平均延迟。程序包括发送工作、接收工作和处理工作三个主要部分,用于生成数据包、发送数据、接收数据和检查性能指标。同时,程序还具备错误检查和性能统计功能,如最大延迟和平均延迟的计算。

一个对网络库进行压测统计qps的小程序

参考:https://gist.github.com/Tsiannian/137b722f1b5dcebea0830a9e12720de0

package main

import (
	"bufio"
	"encoding/binary"
	"fmt"
	"net"
	"os"
	"time"
)

func timeStamp() uint64 {
	return uint64(time.Now().UnixNano())
}

func encode(index uint64, stamp uint64) []byte {
	sendData := make([]byte, 128)
	binary.LittleEndian.PutUint64(sendData[0:64], index)//发送序列号
	binary.LittleEndian.PutUint64(sendData[64:128], stamp)//发送时间
	return sendData
}

func decode(buff []byte) (uint64, uint64) {
	index := binary.LittleEndian.Uint64(buff[0:64])//解析发送序列号
	stamp := binary.LittleEndian.Uint64(buff[64:128])//发送时间
	return index, stamp
}

func handleWork(recv chan []byte, send chan []byte) {
	fmt.Println("begin to handle")
	defer fmt.Println("end to handle")

	countChan := make(chan uint64, 10240)

	go func() {
		var index uint64
		for {
			index++
			now := timeStamp()
			data := encode(index, now)//生成data
			send <- data//放到发送通道
			countChan <- index//index放到count通道
		}
	}()

	for {
		inIndex := <-countChan//发送index
		tcppacket := <-recv//收包的通道
		now := timeStamp()
		outindex, stamp := decode(tcppacket)//解析返回的数据index,时间
		check(inIndex, outindex, now-stamp)//检查发送index到返回index,时间差
	}
}

var couter, latency, begin int64
var max_latency uint64

func check(inIndex, outindex, delay uint64) {//检查发送index到返回index,时间差
	if inIndex != outindex {//不相等,中间有出错,未返回
		fmt.Println("index error", inIndex, outindex)
		os.Exit(-1)
	}
	timeNow := time.Now().Unix()

	couter++
	latency += int64(delay)//总时间差

	if delay > max_latency {
		max_latency = delay
	}

	if begin < timeNow-1 {//1秒
		fmt.Println("ops", couter, "all latency", latency, "max", max_latency, "avg lantency", latency/couter)
		begin = timeNow
		couter = 0
		latency = 0
		max_latency = 0
	}
}

func sendWorker(data chan []byte, nc net.Conn) {
	fmt.Println("begin to send")
	defer fmt.Println("end to send")
	for {
		temp := <-data
		remain := len(data)
		for i := 0; i < remain; i++ {
			left := <-data
			temp = append(temp, left...)
		}
		size, err := nc.Write(temp)
		if size != len(temp) || err != nil {
			fmt.Println("send work error", size, err)
			return
		}
	}
}

func recvWorker(data chan []byte, nc net.Conn) {
	fmt.Println("begin to recv")
	defer fmt.Println("end to recv")

	reader := bufio.NewReaderSize(nc, 8192)
	buff := make([]byte, 4096)
	send := []byte{}
	for {
		size, err := reader.Read(buff)
		if err != nil {
			fmt.Println("recv work err", size, err)
			return
		}
		send = append(send, buff[0:size]...)
		for len(send) >= 128 {
			data <- send[0:128]
			send = send[128:]
		}
	}
}

func main() {
	conn, err := net.Dial("tcp", "127.0.0.1:3050")
	if err != nil {
		fmt.Println(err)
		return
	}

	sendChan := make(chan []byte, 10240)
	recvChan := make(chan []byte, 10240)

	go recvWorker(recvChan, conn)
	go sendWorker(sendChan, conn)

	go handleWork(recvChan, sendChan)

	q := make(chan int)
	<-q
	fmt.Print("client exist")
}

 

<think>好的,我需要帮助用户对Flask接口进行试,记录并发10和100个请求时的平均返回时间、首次响应时间和接口完成时间。首先,我需要回顾用户提供的引用内容,看看有没有相关的信息可以利用。 用户提到了引用[1]和[2],其中引用[1]讨论了使用Python进行并发请求时限制QPS的方法,提到了使用time.sleep来控制并发数量,但指出这种方法不够精确。引用[2]提供了一个curl命令的示例,可以量各个时间指标,比如time_namelookup、time_connect等,这些可能对量响应时间有帮助。引用[5]展示了使用Flask的多进程和多线程处理并发请求的示例代码,这可能与如何设置试环境有关。 首先,试通常需要工具来模拟多个并发请求。常用的工具有Apache Bench (ab)、JMeter、Locust等。用户可能没有提到具体工具,但引用中提到了Python代码,可能倾向于使用Python实现的工具,比如Locust或者自己编写多线程/多进程的试脚本。 接下来,用户需要量的指标有三个:平均返回时间、首次响应时间、接口完成时间。需要明确这些指标的定义: - 平均返回时间:可能是指所有请求从发送到接收到响应所花费时间的平均值。 - 首次响应时间:可能是指第一个请求得到响应的时间,或者每个请求的第一个字节到达的时间(Time to First Byte, TTFB)。 - 接口完成时间:可能是指所有并发请求完成处理的总时间。 根据引用[2]中的curl命令,可以获取每个请求的时间详细信息,括time_starttransfer(从请求开始到第一个字节传输的时间,即TTFB)和time_total(总时间)。这些数据可以用来计算平均返回时间和首次响应时间。 现在,如何设计试脚本呢?用户可能需要一个能够并发发送10或100个请求的脚本,并记录每个请求的时间数据。Python的多线程或多进程库可以用来实现并发请求,比如使用concurrent.futures中的ThreadPoolExecutor。每个线程发送一个请求,并记录时间。 步骤可能括: 1. 编写一个发送请求的函数,使用requests库或http.client,并记录时间信息。 2. 使用多线程并发发送10或100个请求。 3. 收集每个请求的时间数据,计算平均值和其他指标。 4. 输出结果。 但用户引用[1]提到使用time.sleep来控制QPS,这可能不适用于试,因为试需要尽可能多的并发请求,而不是限制速率。不过用户的问题是关于并发数为10和100的情况,所以需要同时发起10或100个请求,并等待它们完成。 另外,需要考虑服务器的配置,比如Flask是否以多线程或多进程模式运行。默认情况下,Flask的开发服务器是单线程的,无法处理并发请求,需要使用threaded=True或者使用生产服务器如Gunicorn。引用[5]中的示例使用了multiprocessing.Process来处理请求,但可能在实际试中,需要确保服务器能够处理并发请求,否则试结果可能不准确。 接下来,如何具体实现试脚本? 可以使用Python的concurrent.futures模块中的ThreadPoolExecutor来并发发送请求。每个请求使用requests库发送,并记录时间。例如: 1. 定义发送请求的函数,记录开始时间、响应时间、完成时间。 2. 使用ThreadPoolExecutor并发执行多个请求。 3. 收集所有请求的时间数据,计算平均时间、首次响应时间等。 但需要注意,使用多线程发送请求时,由于GIL的存在,可能无法真正实现并行,但对于IO密集型任务(如HTTP请求),多线程是有效的。 另外,引用[2]中的curl命令可以获取详细的各阶段时间,但使用Python的requests库可能无法直接获取这些细粒度的时间数据。不过,可以通过在发送请求前后记录时间戳来计算总时间,TTFB可以通过响应对象的elapsed属性获取,或者使用requests的钩子函数来记录更详细的时间。 例如,使用requests的elapsed属性: response.elapsed.total_seconds() # 获取从发送请求到响应到达的总时间 但可能无法获取到DNS解析时间等更详细的信息。如果需要更详细的数据,可能需要使用更低层的库,如http.client,或者解析curl的输出。 不过,为了简化,可以假设用户只需要总时间和TTFB。或者,参考引用[2],在Python中模拟类似curl命令的时间量。 例如,在发送请求前记录开始时间,收到响应后记录结束时间,计算总时间。而TTFB可以通过响应对象的elapsed属性,或者使用开始传输的时间点。 可能需要在发送请求后,立即记录第一个字节到达的时间,这可能需要更底层的处理。不过,对于大多数情况,使用response.elapsed可以近似得到。 现在,结合这些思路,编写一个试脚本的大纲: 使用concurrent.futures.ThreadPoolExecutor来并发发送请求,每个请求使用requests.get,记录每个请求的开始时间、TTFB(response.elapsed)、总时间。然后汇总所有请求的数据,计算平均时间、首次响应时间(即第一个完成的请求的TTFB),以及接口完成时间(所有请求完成的时间差)。 具体步骤: 1. 导入必要的库:requests, concurrent.futures, time, threading等。 2. 定义试的URL,比如http://localhost:5000/ 3. 定义发送单个请求的函数,记录时间数据。 4. 使用线程池并发发送N个请求(10或100)。 5. 收集所有请求的时间数据。 6. 计算平均返回时间(总时间的平均值)、首次响应时间(最小的TTFB),接口完成时间(最后一个请求结束时间 - 第一个请求开始时间)。 但需要注意,服务器处理并发请求的能力可能影响结果。比如,如果Flask服务器是单线程的,处理100个并发请求会有明显的排队,导致完成时间较长。 此外,还需要处理可能的错误,比如请求失败,超时等,但用户的问题可能假设接口正常。 现在,参考引用[1]中的代码,用户可能已经尝试过使用time.sleep来控制QPS,但这里需要的是并发请求,而不是限制速率。所以不需要sleep,而是同时启动所有线程。 示例代码结构可能如下: import requests import time from concurrent.futures import ThreadPoolExecutor url = "http://localhost:5000/" def send_request(): start_time = time.time() response = requests.get(url) elapsed = response.elapsed.total_seconds() # TTFB total_time = time.time() - start_time return { 'start': start_time, 'ttfb': elapsed, 'total': total_time } def run_test(concurrent_requests): start_overall = time.time() with ThreadPoolExecutor(max_workers=concurrent_requests) as executor: futures = [executor.submit(send_request) for _ in range(concurrent_requests)] results = [future.result() for future in futures] end_overall = time.time() # 处理结果 ttfb_list = [r['ttfb'] for r in results] total_list = [r['total'] for r in results] start_times = [r['start'] for r in results] end_times = [r['start'] + r['total'] for r in results] avg_ttfb = sum(ttfb_list) / len(ttfb_list) avg_total = sum(total_list) / len(total_list) first_response = min(ttfb_list) completion_time = max(end_times) - min(start_times) print(f"并发数: {concurrent_requests}") print(f"平均TTFB: {avg_ttfb:.3f}s") print(f"平均总时间: {avg_total:.3f}s") print(f"首次响应时间: {first_response:.3f}s") print(f"接口完成时间: {completion_time:.3f}s") if __name__ == "__main__": run_test(10) run_test(100) 不过,这里可能存在一些问题: 1. 使用ThreadPoolExecutor的max_workers设置为并发请求数,这可能会创建大量线程,尤其是100个的时候,可能会受系统限制。 2. requests.get默认是同步的,每个线程会阻塞直到收到响应,因此并发数由max_workers控制。 3. 时间的准确性:由于线程调度的延迟,实际并发可能不如预期,但这是试的常见问题。 4. response.elapsed可能只量requests库内部的时间,即从发送请求到响应头到达的时间,可能不括DNS解析等时间,但用户可能关心的是端到端的时间。 此外,用户可能需要区分平均返回时间是指TTFB的平均还是总时间的平均。根据问题描述,“平均返回时间”可能指的是总时间的平均,而“首次响应时间”可能指第一个收到的响应时间,或者每个请求的TTFB中的最小值。 在代码中,计算了每个请求的TTFB(elapsed)和总时间(从start_time到结束),然后取平均值。首次响应时间取所有TTFB中的最小值。接口完成时间是最后一个请求结束的时间减去第一个请求开始的时间,这反映了整个试的持续时间。 但需要注意,如果所有请求几乎同时启动,那么接口完成时间可能接近最慢请求的总时间,但实际中由于线程调度,启动时间可能有微小差异。 此外,服务器性能也会影响结果,因此需要确保Flask应用处于生产模式或能够处理并发请求。比如,使用app.run(threaded=True)或者使用Gunicorn。 引用[5]中的示例使用了multiprocessing.Process来处理请求,但在试中,客户端应该模拟多个并发请求,而不是服务器端处理多进程。因此,用户需要确保Flask服务器配置正确,能够处理并发,比如设置threaded=True或者使用多进程服务器。 另外,试时,可能需要关闭Flask的调试模式,以避免调试器的开销和单线程模式。 总结步骤: 1. 确保Flask应用配置正确,能够处理并发请求(如使用threaded=True或部署到生产服务器)。 2. 编写试脚本,使用多线程发送并发请求,记录每个请求的时间。 3. 运行试脚本,分别试10和100并发。 4. 分析结果,计算所需的指标。 现在,根据这些思考,组织回答,提供具体的步骤和代码示例,并引用相关的引用内容。 比如,在试中记录时间的方法可以参考引用[2]的curl命令,但使用Python实现。而并发请求的实现可以参考引用[1]中的多线程方法,但去掉sleep,直接并发。 在回答中,需要提到如何配置Flask以处理并发,可能引用[5]中的多线程设置,即app.run(threaded=True),或者使用Gunicorn。 可能的问题括: - Flask开发服务器的并发能力有限,不适合高并发试,建议使用生产服务器。 - 试脚本的并发实现方式,如使用ThreadPoolExecutor。 - 时间量的准确性,引用[1]提到使用time.sleep不够精确,同样,多线程的时间量可能存在误差,但可以接受。 最终,回答的结构应括: 1. 试步骤说明。 2. 试脚本代码示例。 3. 指标计算方法。 4. 注意事项(如服务器配置、试环境等)。 5. 相关问题的生成。 确保回答符合用户的要求,使用中文,结构清晰,正确使用LaTeX格式,并引用相关引用。</think>### Flask接口试方法 #### 一、试步骤说明 1. **配置Flask服务器**:确保启用多线程模式或使用生产服务器(如Gunicorn)[^5]。 ```python app.run(host='0.0.0.0', port=5000, threaded=True) ``` 2. **编写试脚本**:使用Python多线程模拟并发请求,记录关键时间指标。 3. **运行试**:分别试10和100并发请求,统计结果。 --- #### 二、试脚本示例 ```python import requests import time from concurrent.futures import ThreadPoolExecutor def send_request(url): start_time = time.time() try: response = requests.get(url) ttfb = response.elapsed.total_seconds() # 首次响应时间(TTFB) total_time = time.time() - start_time # 总耗时 return {"ttfb": ttfb, "total": total_time, "start": start_time} except Exception as e: return {"error": str(e)} def run_test(url, concurrency): start_all = time.time() with ThreadPoolExecutor(max_workers=concurrency) as executor: futures = [executor.submit(send_request, url) for _ in range(concurrency)] results = [f.result() for f in futures if not f.result().get("error")] # 计算指标 end_all = time.time() ttfb_list = [r["ttfb"] for r in results] total_list = [r["total"] for r in results] start_times = [r["start"] for r in results] end_times = [r["start"] + r["total"] for r in results] avg_ttfb = sum(ttfb_list) / len(ttfb_list) avg_total = sum(total_list) / len(total_list) first_response = min(ttfb_list) completion_time = max(end_times) - min(start_times) print(f"并发数: {concurrency}") print(f"平均首次响应时间(TTFB): {avg_ttfb:.3f}s") print(f"平均总耗时: {avg_total:.3f}s") print(f"最快首次响应时间: {first_response:.3f}s") print(f"接口完成时间: {completion_time:.3f}s") if __name__ == "__main__": target_url = "http://localhost:5000/" run_test(target_url, 10) # 试10并发 run_test(target_url, 100) # 试100并发 ``` --- #### 三、关键指标解释 1. **平均首次响应时间**:所有请求从发起到收到第一个字节的平均耗时(TTFB)[^2]。 2. **平均总耗时**:所有请求从发起到完成响应的平均耗时。 3. **接口完成时间**:从第一个请求开始到最后一个请求结束的总时长,公式为: $$ \text{完成时间} = \max(\text{end\_times}) - \min(\text{start\_times}) $$ --- #### 四、注意事项 1. **服务器配置**:Flask开发服务器(`app.run()`)默认单线程,需设置`threaded=True`或使用Gunicorn[^5]。 2. **环境隔离**:试时关闭其他占用资源的程序,避免干扰。 3. **误差控制**:多线程调度存在微小误差,多次试取平均值更准确[^1]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值