mitmproxy 代理性能剖析:test/bench 基准插件使用与源码解析
2026/9/10 16:15:37 网站建设 项目流程

mitmproxy 代理性能剖析:test/bench 基准插件使用与源码解析

【免费下载链接】mitmproxyAn interactive TLS-capable intercepting HTTP proxy for penetration testers and software developers.项目地址: https://gitcode.com/GitHub_Trending/mi/mitmproxy

mitmproxy 仓库的test/bench目录内置了一套面向开发者自身的代理基准与剖析(benchmark & profiling)工具:以 addon 形式加载,借助 wrk 与 devd 在真实 HTTP 负载下压测代理数据通路,并同时输出 cProfile 统计结果。本文以 test/bench/README.md 为纲,结合插件源码 benchmark.py、一键脚本与 addon 生命周期钩子实现,完整讲解这套基准工具的安装、运行、产物解读与底层原理,帮助你评估一次改动对 mitmproxy 代理吞吐与 CPU 开销的影响。

这套基准工具是做什么的

在 mitmproxy 源码仓库中,test/bench是一个面向 mitmproxy 开发者的专用目录,而不是给最终用户做的黑盒压测框架。它设计为:

  • 加载一个自定义 addon(benchmark.py);
  • 自动拉起一个 HTTP 后端并生成真实并发请求;
  • 让请求穿过完整的 mitmproxy 代理链路;
  • 同时输出两类结果:wrk 生成的吞吐/延迟报告(.bench)与 PythoncProfile生成的性能剖析数据(.prof)。

README 中说明了它的初衷:为开发者提供一种快速看到自己的工作对性能影响的方式;长远来看,它可能演化成一个带历史数据的性能看板,用于跨版本跟踪性能变化。也就是说,它的价值更多是"改完代码自己跑一下、对比前后的剖析数据",而非发布给用户的性能承诺。

运行前提:需要安装的工具

开始之前需要准备三样外部工具(README 同时注明推荐安装 snakeviz 用于查看 profile)。它们的作用在源码中可以一一对应:

工具用途在本目录中的角色
wrkHTTP 负载压测工具由 benchmark.py 作为子进程启动,对代理端口发起-c50-d5s的请求
devd静态文件/开发服务器由 benchmark.py 作为子进程启动,作为被测的后端服务
snakevizcProfile 结果可视化可选,用于查看.prof剖析文件

安装方式:

  • 安装 wrk(HTTP 压测工具)与 devd(其作者 cortesi 同是 mitmproxy 社区工具 devd 的维护者,安装命令为 Go 模块方式):
go get github.com/cortesi/devd/cmd/devd
  • 安装 snakeviz 以便更方便地浏览剖析结果:
pip install snakeviz

说明:wrk 与 devd 均以子进程形式由插件 spawn,请确保它们位于PATH中。

快速开始:一条命令跑完整基准

test/bench目录内执行下述命令即可:

mitmdump -p0 -q --set benchmark_save_path=/tmp/foo -s ./benchmark.py

参数含义:

  • -p0:让 mitmproxy 的监听端口交给系统动态分配(插件随后通过ctx.master.server.address[1]读取实际端口供 wrk 使用,见 benchmark.py);
  • -q:安静模式,减少终端噪音;
  • --set benchmark_save_path=/tmp/foo:指定结果文件的前缀路径,这是该 addon 通过loader.add_option注册的自定义选项(详见下文);
  • -s ./benchmark.py:以脚本(addon)方式加载基准插件。

命令执行后将自动完成"启动后端 → 运行压测 → 保存结果 → 退出"的全流程,产出两个文件:

  • /tmp/foo.bench—— wrk 输出的基准报告(文本);
  • /tmp/foo.prof—— cProfile 二进制剖析数据。

两个内置的一键脚本

目录中还提供了两个 shell 脚本,用于把结果集中保存到本地results/目录,方便后续比较:

  • run-mitmdump:用mitmdump运行(无控制台界面,纯数据通路):
mkdir -p results mitmdump -p0 -q --set benchmark_save_path=./results/mitmdump -s ./benchmark.py
  • run-mitmproxy:用带交互界面的mitmproxy运行,产物前缀改为./results/mitmproxy
mkdir -p results mitmproxy -p0 -q --set benchmark_save_path=./results/mitmproxy -s ./benchmark.py

两者的唯一区别是使用的可执行文件与结果文件前缀。开发者在对比"控制台界面本身对代理主循环的开销"时,可以分别运行这两个脚本并对照.prof剖析结果。

插件源码逐段走读

整个基准逻辑集中在约 65 行的 benchmark.py,理解它就能理解整套工具的数据流。我们从 addon 的声明说起。

addon 入口

文件末尾以标准 addon 导出方式声明:

addons = [Benchmark()]

Benchmark类(benchmark.py)在__init__中创建cProfile.Profile()实例,并初始化请求/响应计数:

self.pr = cProfile.Profile() self.started = False self.resps = 0 self.reqs = 0

load 阶段:注册选项并开启剖析

load(self, loader)(benchmark.py)是 addon 生命周期中最早被调用的钩子之一,做了三件事:

  1. 注册自定义选项,用于控制结果文件的落盘路径:
loader.add_option( "benchmark_save_path", str, "/tmp/profile", "Destination for the .prof and .bench result files", )

注意它的默认值是/tmp/profile——也就是说即使你不传--set benchmark_save_path=...,也会在结束时生成/tmp/profile.bench/tmp/profile.prof

  1. 通过ctx.options.update(...)将当前 mitmproxy 实例切换为反向代理模式
ctx.options.update( mode="reverse:http://devd.io:10001", )

这是这套基准运行形态的关键:wrk 发出的请求会经 mitmproxy 反向代理语义转发到后端 devd 服务。

  1. 调用self.pr.enable()开始cProfile采样。

ctx在此是 mitmproxy 提供的运行期上下文单例,其中暴露ctx.masterctx.options,源码见 mitmproxy/ctx.py。

running 阶段:编排压测主流程

addon 生命周期钩子running(benchmark.py)在代理完全就绪后被触发。由于一个会话中该钩子可能多次回调,代码用self.started标志保证只启动一次基准任务:

def running(self): if not self.started: self.started = True self._task = asyncio.create_task(self.procs())

真正干活的是协程procs()(benchmark.py),其编排逻辑为:

  1. 以子进程方式启动后端 devd,-q安静模式、监听本机10001端口、以.(当前工作目录)作为静态文件根:
backend = await asyncio.create_subprocess_exec("devd", "-q", "-p", "10001", ".")
  1. 以子进程方式启动 wrk,用50 个并发连接、持续 5 秒向代理端口发起请求:
traf = await asyncio.create_subprocess_exec( "wrk", "-c50", "-d5s", "http://localhost:%s/benchmark.py" % ctx.master.server.address[1], stdout=asyncio.subprocess.PIPE, )

ctx.master.server.address[1]读取的是代理实际监听的端口——这与命令行-p0(系统动态分配端口)配合,保证无论分配到哪个空闲端口都能被正确压测。

  1. 等待 wrk 结束并把其标准输出原样写入benchmark_save_path + ".bench"
stdout, _ = await traf.communicate() with open(ctx.options.benchmark_save_path + ".bench", mode="wb") as f: f.write(stdout)
  1. 打印代理内部统计日志与 wrk 报告,清理后端并退出整个 mitmproxy 进程:
logging.error(f"Proxy saw {self.reqs} requests, {self.resps} responses") logging.error(stdout.decode("ascii")) backend.kill() ctx.master.shutdown()

这一行日志非常有价值:它告诉你的是从代理内部视角实际流经的请求/响应数,与 wrk 侧看到的吞吐互为印证,可以用来发现"请求到达了但代理没处理完"这类不一致。

request / response 钩子:代理视角的计数

类中实现的两个数据流钩子(benchmark.py)分别对流经代理的 HTTP 请求和响应做累加:

def request(self, f): self.reqs += 1 def response(self, f): self.resps += 1

done 阶段:落盘剖析数据

done(benchmark.py)是 addon 收到的最后一个事件,在进程退出前把 cProfile 采样结果 dump 成.prof文件:

def done(self): self.pr.dump_stats(ctx.options.benchmark_save_path + ".prof")

addon 生命周期钩子背后的实现依据

benchmark.py用到的load/running/done/request/response并不是随意命名的魔法方法,而是 mitmproxy addon 钩子机制的一部分。在 mitmproxy/hooks.py 中可以看到,所有钩子类都会把类名自动转换为小写下划线形式的事件名,例如RunningHookrunningDoneHookdone。其中与本工具直接相关的两个生命周期钩子定义如下:

  • RunningHook(mitmproxy/hooks.py):在代理完全启动、所有 addon 与选项就绪后触发,其 docstring 注明此时可以放心依赖所有 addon 与选项——这正是benchmark.py选择在running里发起压测子进程的原因;
  • DoneHook(mitmproxy/hooks.py):在 addon 被移除或 mitmproxy 自身关闭时触发,且保证是 addon 收到的最后一个事件,同时提示此阶段日志处理器可能已关闭。benchmark.py在此阶段 dump profile,正是利用了它"最后收尾"的语义。

如何解读基准产物

.bench 文件

它是 wrk 的原始文本输出,包含请求总数、每秒请求数(Requests/sec)、平均/最大延迟、传输字节数等统计。这是吞吐与延迟视角的结果,用于回答"改动后代理每秒能扛多少请求、延迟是否恶化"。

.prof 文件

它是cProfile的二进制统计文件,用于回答"CPU 时间花在了哪些函数上"。推荐用 snakeviz 打开以树状图交互式浏览调用热点:

snakeviz /tmp/foo.prof

开发者在优化前后各跑一次,用 snakeviz 对比热点函数占比变化,即可量化本次改动的剖析开销。

终端日志

即使不解析文件,插件退出前还会在终端打印Proxy saw N requests, M responses与 wrk 报告全文(benchmark.py),方便快速扫一眼结论。

注意事项与适用边界

基于代码实现,使用这套工具时有几点需要留意:

  • 运行目录有讲究:devd 以.为静态根目录、-s ./benchmark.py也依赖相对路径,因此推荐在 test/bench 目录内执行命令或直接运行./run-mitmdump/./run-mitmproxy,且mitmdump/mitmproxy需在PATH中。
  • 一次会话只跑一轮running钩子通过started标志保证仅启动一次,随后ctx.master.shutdown()会让整个进程自行退出。
  • 结果文件是覆盖式写入benchmark_save_path指向的目标会直接被覆写,若要保留历史对比请每次更换路径(这正是两个 run 脚本用results/目录的原因)。
  • 结果偏向整机环境:作为本地快速回归工具,其吞吐数据受宿主 CPU、后端 devd 性能与并发设置(固定-c50 -d5s)影响,适合做改动前后的相对对比,不适合作为对外宣称的绝对性能指标。README 也明确当前仅用于"开发者快速查看自己改动的影响"。

另一种基准思路:pytest-benchmark 单元基准

如果你关注的是代理各层协议栈内部的单点开销而不是端到端吞吐,仓库还提供了一条基于pytest-benchmark的路线:test/mitmproxy/proxy/bench.py。它的用法(文件头部 docstring)为:

- pip install pytest-benchmark - pytest bench.py

其中定义了多个基准用例,覆盖 HTTP、HTTP/2、TCP、TLS(仅服务端、仅客户端、双向)等往返场景,例如:

  • test_bench_http_roundtrip:复用test_http的代理往返测试,交给 pytest 的benchmarkfixture 计时;
  • test_bench_tcp_roundtrip:对 TCP 简单用例做deepcopy隔离后计时;
  • test_bench_server_tls/test_bench_client_tls/test_bench_tls_both:分别测量 TLS 握手涉及服务端证书、客户端认证、双向认证的开销。

它与 test/bench 形成互补:前者在单元层用固定测试夹具反复执行某个协议往返并取统计;后者在集成层用真实 wrk 负载穿透整个代理。两者都锚定在test/目录下(benchmark 相关测试),改动代理核心后配合使用可同时获得"宏观吞吐 + 微观热点"两方面的反馈。

小结

test/bench是一套轻量而完整的 mitmproxy 性能回归工具:外围靠 wrk + devd 构造真实负载,内核由一个生命周期清晰的 addon(benchmark.py)驱动——load注册选项并开启剖析、running编排子进程发起压测、request/response做代理视角计数、done落盘.prof。配合.bench文本报告、snakeviz可视化以及 proxy 层的 pytest 单元基准,你可以快速验证自己的改动究竟给这个 TLS 拦截代理带来了多大的性能影响。

【免费下载链接】mitmproxyAn interactive TLS-capable intercepting HTTP proxy for penetration testers and software developers.项目地址: https://gitcode.com/GitHub_Trending/mi/mitmproxy

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询