PHPTrace 设计原理
简单介绍
PHPTrace致力于打造一款实时跟踪PHP函数调用,获取PHP函数调用栈信息以及PHP解释器状态的工具,这个PHP工具应该像系统工具strace/pstack一样强大易用。要设计完成这款工具,需要解决三方面的问题:
外部命令工具如何获取PHP进程内部的相关信息
能够实时开启和关闭,不需要编写代码或者修改配置
单独开启一个PHP进程的trace/stack功能,将线上服务器的影响减至最小
详细分析
1. 如何获取PHP进程内部的相关信息
strace可以通过系统调用ptrace,使被trace的进程每次进入或退出系统调用时暂停, 这样strace通过ptrace(PTRACE_PEEK*)获取此时进程内存的内容,从而获取到系统调用的名称、参数、返回值、调用时间等内容。
由于PHP是一种动态脚本语言,并不是传统的C调用模型,PHP程序的运行依赖于一个PHP的解释器。通过ptrace系统调用获取的是解释器本身的状况,控制系统调用的执行与暂停,而不是PHP脚本的运行情况,因此不能控制一个PHP函数的执行或暂停。
为了解决如何从PHP虚拟机中获取调用信息的难题,我们决定引入PHP扩展,由扩展hook PHP解释器,PHP解释器在每次执行一个PHP函数或扩展函数时,调用zend_execute_*等系列函数,通过 hook zend_execute_*系列函数,这样解释器在调用PHP函数或扩展函数时,先进入我们的PHPTrace的hook函数,PHPTrace获取到调用信息后,调用PHP解释器真正的执行函数,并在PHP真正执行函数退出后, 获取调用结果:返回值,调用耗时。
2. 实时开启和关闭
为了定位线上正在运行中的PHP出现的问题,开启trace或stack的功能不能依赖于修改配置文件和写PHP代码,应该能够想strace和pstack那样给一个进程的ID,实时开启或者关闭。
我们形成了外部命令行工具跟PHP扩展共同协作的设计,由命令通知扩展开始trace,扩展收到通知后才开始真正的抓取PHP调用信息,抓取的信息不断写到共享内存,命令行工具不断读取共享内存,从而实现PHPTrace随时随地实时输出trace结果的目的。
3. 单独开启一个PHP进程的trace/stack功能
开启一个进程的trace/stack功能,必然会降低该进程执行PHP脚本的性能,strace也是如此。但是其它没有开启该功能的进程应该不受影响。
在外部命令工具通知扩展执行trace之前, 扩展并不会抓取任何信息,仅仅判断trace开关的状态,然后调用了原PHP虚拟机的zend_execute_*函数, 基本不会增加开销。
总体设计
PHPTrace从设计上分为三大部分: 扩展、通信共享内存部分以及命令行工具。如下图所示:
扩展用于收集PHP解释器中的相关信息
通信共享内存部分负责在命令行工具和扩展之间传递控制信息和数据信息
命令行工具用于开启某一个进程的trace或stack功能,并输出相关信息
最后
因为解决了以上三个问题,PHPTrace可以帮助排查线上线下的各种PHP问题。
Xdebug虽有trace功能, 但其输出的结果却不能实时开启并打印出来;另外Xdebug挂钩了很多opcode,导致在不启用trace功能时,仍然有大量性能消耗。因此无法达到线上随时trace问题的要求。
本节主要偏重PHPTrace的设计原理,后续会给出每一部分的详细实现,敬请期待!