简介:由于公司项目,最近需要分析后端PHP接口的性能数据,就采用了FACEBOOK之前开源的一个扩展,现在市面上很多分支都是基于FB最开始的线开发的,但是由于FB已经停止维护,所以现在其他线都是自己个人在维护。
今天我分析的这个分支是兼容PHP7+的版本收集性能数据,先贴出GITHUB的链接 https://github.com/longxinH/xhprof
扩展的安装方式和PHP调用的API用法在github上readme.md上有详细说明,可以参考,我现在做的项目就是用这个来收集的,因为是私人维护的,不敢保证以后会不会启用,所以先自己了解下XHPROF扩展源码,防止后面维护人不维护,可以自己在继续维护起来。
分析:
起始外部扩展就是相当于内核一个模块,都是zend_module_entry结构体, 收集性能数据的原理,就是在模块初始化的时候代理了这个,代理了这个编译和执行OPCODE 的函数,覆盖了一层,加了自己的处理,自己的处理就是C代码的执行时间和PHP申请堆内存的计算。
/* Replace zend_compile with our proxy */
_zend_compile_file = zend_compile_file;
zend_compile_file = hp_compile_file;
/* Replace zend_compile_string with our proxy */
_zend_compile_string = zend_compile_string;
zend_compile_string = hp_compile_string;
/* Replace zend_execute with our proxy */
_zend_execute_ex = zend_execute_ex;
zend_execute_ex = hp_execute_ex;
这是zend_module_entry,第三方扩展都需要实现这个结构体,用来保存扩展的基本信息,类似设计模式里面的实现接口
/* Callback functions for the xhprof extension */
zend_module_entry xhprof_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
"xhprof", /* Name of the extension */
xhprof_functions, /* List of functions exposed */
PHP_MINIT(xhprof), /* Module init callback */
PHP_MSHUTDOWN(xhprof), /* Module shutdown callback */
PHP_RINIT(xhprof), /* Request init callback */
PHP_RSHUTDOWN(xhprof), /* Request shutdown callback */
PHP_MINFO(xhprof), /* Module info callback */
#if ZEND_MODULE_API_NO >= 20010901
XHPROF_VERSION,
#endif
STANDARD_MODULE_PROPERTIES
};
/* Xhprof's global state.
*
* This structure is instantiated once. Initialize defaults for attributes in
* hp_init_profiler_state() Cleanup/free attributes in
* hp_clean_profiler_state() */
存储扩展性能数据,是否开启,等信息的结构体
ZEND_BEGIN_MODULE_GLOBALS(xhprof)
/* ---------- Global attributes: ----------- */
/* Indicates if xhprof is currently enabled */
int enabled;
/* Indicates if xhprof was ever enabled during this request */
int ever_enabled;
/* Holds all the xhprof statistics */
zval stats_count;
/* Indicates the current xhprof mode or level */
int profiler_level;
/* Top of the profile stack */
hp_entry_t *entries;
/* freelist of hp_entry_t chunks for reuse... */
hp_entry_t *entry_free_list;
/* Callbacks for various xhprof modes */
hp_mode_cb mode_cb;
/* ---------- Mode specific attributes: ----------- */
/* Global to track the time of the last sample in time and ticks */
struct timeval last_sample_time;
uint64 last_sample_tsc;
/* XHPROF_SAMPLING_INTERVAL in ticks */
long sampling_interval;
uint64 sampling_interval_tsc;
int sampling_depth;
/* XHProf flags */
uint32 xhprof_flags;
char *root;
/* counter table indexed by hash value of function names. */
uint8 func_hash_counters[256];
HashTable *trace_callbacks;
/* Table of ignored function names and their filter */
hp_ignored_functions *ignored_functions;
ZEND_END_MODULE_GLOBALS(xhprof)
/**
static const zend_ini_entry_def ini_entries[] = {
{ xhprof.output_dir, NULL, arg1, arg2, arg3, '', displayer, 7, sizeof(name)-1, sizeof('')-1 },
{ xhprof.collect_additional_info, NULL, arg1, arg2, arg3, '0', displayer, 7, sizeof(name)-1, sizeof('')-1 },
{ xhprof.sampling_interval, NULL, arg1, arg2, arg3, '100000', displayer, 7, sizeof(name)-1, sizeof('')-1 },
{ xhprof.sampling_depth, NULL, arg1, arg2, arg3, '100000', displayer, 7, sizeof(name)-1, sizeof('')-1 },
{ NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, 0}
}
*/
这是注册xhprof扩展所有的php.ini配置项
PHP_INI_BEGIN()
/* output directory:
* Currently this is not used by the extension itself.
* But some implementations of iXHProfRuns interface might
* choose to save/restore XHProf profiler runs in the
* directory specified by this ini setting.
*/
PHP_INI_ENTRY("xhprof.output_dir", "", PHP_INI_ALL, NULL)
/*
* collect_additional_info
* Collect mysql_query, curl_exec internal info. The default is 0.
*/
PHP_INI_ENTRY("xhprof.collect_additional_info", "0", PHP_INI_ALL, NULL)
/* sampling_interval:
* Sampling interval to be used by the sampling profiler, in microseconds.
*/
#define STRINGIFY_(X) #X
#define STRINGIFY(X) STRINGIFY_(X)
STD_PHP_INI_ENTRY("xhprof.sampling_interval", STRINGIFY(XHPROF_DEFAULT_SAMPLING_INTERVAL), PHP_INI_ALL, OnUpdateLong, sampling_interval, zend_xhprof_globals, xhprof_globals)
/* sampling_depth:
* Depth to trace call-chain by the sampling profiler
*/
STD_PHP_INI_ENTRY("xhprof.sampling_depth", STRINGIFY(INT_MAX), PHP_INI_ALL, OnUpdateLong, sampling_depth, zend_xhprof_globals, xhprof_globals)
PHP_INI_END()
/**
* Module init callback.
*
* @author cjiang
*/
模块初始化的调用的函数
//int zm_startup_xhprof(xhprof)(int type, int module_number)
PHP_MINIT_FUNCTION(xhprof)
{
ZEND_INIT_MODULE_GLOBALS(xhprof, php_xhprof_init_globals, NULL);
REGISTER_INI_ENTRIES();
hp_register_constants(INIT_FUNC_ARGS_PASSTHRU);
/* Replace zend_compile with our proxy */
_zend_compile_file = zend_compile_file;
zend_compile_file = hp_compile_file;
/* Replace zend_compile_string with our proxy */
_zend_compile_string = zend_compile_string;
zend_compile_string = hp_compile_string;
/* Replace zend_execute with our proxy */
_zend_execute_ex = zend_execute_ex;
zend_execute_ex = hp_execute_ex;
/* Replace zend_execute_internal with our proxy */
_zend_execute_internal = zend_execute_internal;
zend_execute_internal = hp_execute_internal;
#if defined(DEBUG)
/* To make it random number generator repeatable to ease testing. */
srand(0);
#endif
return SUCCESS;
}
/**
* Module shutdown callback.
*/
模块关闭的调用的函数
//int zm_shutdown_xhprof(xhprof)(int type, int module_number)
PHP_MSHUTDOWN_FUNCTION(xhprof)
{
/* free any remaining items in the free list */
hp_free_the_free_list();
/* Remove proxies, restore the originals */
zend_execute_ex = _zend_execute_ex;
zend_execute_internal = _zend_execute_internal;
zend_compile_file = _zend_compile_file;
zend_compile_string = _zend_compile_string;
UNREGISTER_INI_ENTRIES();
return SUCCESS;
}
/**
* Request init callback. Nothing to do yet!
*/
请求初始化的调用的函数
//int zm_activate_xhprof(xhprof)(int type, int module_number)
PHP_RINIT_FUNCTION(xhprof)
{
#if defined(ZTS) && defined(COMPILE_DL_XHPROF)
ZEND_TSRMLS_CACHE_UPDATE();
#endif
return SUCCESS;
}
/**
* Request shutdown callback. Stop profiling and return.
*/
//int zm_deactivate_xhprof(xhprof)(int type, int module_number)
请求完成的调用的函数
PHP_RSHUTDOWN_FUNCTION(xhprof)
{
hp_end();
return SUCCESS;
}
/**
* Module info callback. Returns the xhprof version.
*/
phpinfo显示模块基本信息调用的函数
//int zm_info_xhprof(xhprof)(zend_module_entry *zend_module)
PHP_MINFO_FUNCTION(xhprof)
{
php_info_print_table_start();
php_info_print_table_header(2, "xhprof support", "enabled");
php_info_print_table_row(2, "Version", XHPROF_VERSION);
php_info_print_table_end();
DISPLAY_INI_ENTRIES();
}
/**
* Start XHProf profiling in hierarchical mode.
*
* @param long $flags flags for hierarchical mode
* @return void
* @author kannan
*/
PHP语言中 xhprof_enable函数
//zif_xhprof_enable(zend_execute_data *execute_data, zval *return_value)
PHP_FUNCTION(xhprof_enable)
{
long xhprof_flags = 0; /* XHProf flags */
zval *optional_array = NULL; /* optional array arg: for future use */
if (zend_parse_parameters(ZEND_NUM_ARGS(), "|lz", &xhprof_flags, &optional_array) == FAILURE) {
return;
}
hp_get_ignored_functions_from_arg(optional_array);
hp_begin(XHPROF_MODE_HIERARCHICAL, xhprof_flags);
}
/**
* Stops XHProf from profiling in hierarchical mode anymore and returns the
* profile info.
*
* @param void
* @return array hash-array of XHProf's profile info
* @author kannan, hzhao
*/
PHP语言中 xhprof_disable函数
//zif_xhprof_disable(zend_execute_data *execute_data, zval *return_value)
PHP_FUNCTION(xhprof_disable)
{
if (XHPROF_G(enabled)) {
hp_stop();
RETURN_ZVAL(&XHPROF_G(stats_count), 1, 0);
}
/* else null is returned */
}
/**
* Start XHProf profiling in sampling mode.
*
* @return void
* @author cjiang
*/
PHP语言中 xhprof_sample_enable函数
//zif_xhprof_sample_enable(zend_execute_data *execute_data, zval *return_value)
PHP_FUNCTION(xhprof_sample_enable)
{
long xhprof_flags = 0; /* XHProf flags */
hp_get_ignored_functions_from_arg(NULL);
hp_begin(XHPROF_MODE_SAMPLED, xhprof_flags);
}
/**
* Stops XHProf from profiling in sampling mode anymore and returns the profile
* info.
*
* @param void
* @return array hash-array of XHProf's profile info
* @author cjiang
*/
PHP语言中 xhprof_sample_disable函数
//zif_xhprof_sample_disable(zend_execute_data *execute_data, zval *return_value)
PHP_FUNCTION(xhprof_sample_disable)
{
if (XHPROF_G(enabled)) {
hp_stop();
RETURN_ZVAL(&XHPROF_G(stats_count), 1, 0);
}
/* else null is returned */
}
这个扩展的基本接口就是上面这些代码,接下来用一个例子来详细解释一下,怎么收集性能数据的。
贴出一段PHP代码
<?php
//phpinfo();
xhprof_enable(XHPROF_FLAGS_NO_BUILTINS | XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY);
aaa();
$xhprof_data = xhprof_disable();
print_r($xhprof_data);
function aaa()
{
bbb();
}
function bbb()
{
}
输出:
Array
(
[aaa==>bbb] => Array
(
[ct] => 1
[wt] => 23
[cpu] => 29
[mu] => 832
[pmu] => 0
)
[main()==>aaa] => Array
(
[ct] => 1
[wt] => 88
[cpu] => 89
[mu] => 1408
[pmu] => 0
)
[main()] => Array
(
[ct] => 1
[wt] => 99
[cpu] => 99
[mu] => 1976
[pmu] => 0
)
)
xhprof_enable(XHPROF_FLAGS_NO_BUILTINS | XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY);
这个函数相当于执行了扩展里面的,这个函数相当自己造了一个虚拟的 main()节点,来存当前调用PHP函数的性能数据 ct wt mu pmu等数据
PHP_FUNCTION(xhprof_enable)
{
long xhprof_flags = 0; /* XHProf flags */
zval *optional_array = NULL; /* optional array arg: for future use */
if (zend_parse_parameters(ZEND_NUM_ARGS(), "|lz", &xhprof_flags, &optional_array) == FAILURE) {
return;
}
hp_get_ignored_functions_from_arg(optional_array);
hp_begin(XHPROF_MODE_HIERARCHICAL, xhprof_flags);
}
static void hp_begin(long level, long xhprof_flags)
{
if (!XHPROF_G(enabled)) {
int hp_profile_flag = 1;
XHPROF_G(enabled) = 1;
XHPROF_G(xhprof_flags) = (uint32)xhprof_flags;
/* Initialize with the dummy mode first Having these dummy callbacks saves
* us from checking if any of the callbacks are NULL everywhere. */
XHPROF_G(mode_cb).init_cb = hp_mode_dummy_init_cb;
XHPROF_G(mode_cb).exit_cb = hp_mode_dummy_exit_cb;
XHPROF_G(mode_cb).begin_fn_cb = hp_mode_dummy_beginfn_cb;
XHPROF_G(mode_cb).end_fn_cb = hp_mode_dummy_endfn_cb;
/* Register the appropriate callback functions Override just a subset of
* all the callbacks is OK. */
switch (level) {
case XHPROF_MODE_HIERARCHICAL:
XHPROF_G(mode_cb).begin_fn_cb = hp_mode_hier_beginfn_cb;
XHPROF_G(mode_cb).end_fn_cb = hp_mode_hier_endfn_cb;
break;
case XHPROF_MODE_SAMPLED:
XHPROF_G(mode_cb).init_cb = hp_mode_sampled_init_cb;
XHPROF_G(mode_cb).begin_fn_cb = hp_mode_sampled_beginfn_cb;
XHPROF_G(mode_cb).end_fn_cb = hp_mode_sampled_endfn_cb;
break;
}
/* one time initializations */
hp_init_profiler_state(level);
/* start profiling from fictitious main() */
XHPROF_G(root) = estrdup(ROOT_SYMBOL);
/* start profiling from fictitious main() */
BEGIN_PROFILING(&XHPROF_G(entries), XHPROF_G(root), hp_profile_flag, NULL);
}
}
#define BEGIN_PROFILING(entries, symbol, profile_curr, execute_data) \
do { \
/* Use a hash code to filter most of the string comparisons. */ \
uint8 hash_code = hp_inline_hash(symbol); \
profile_curr = !hp_ignore_entry_work(hash_code, symbol); \
if (profile_curr) { \
if (execute_data != NULL) { \
symbol = hp_get_trace_callback(symbol, execute_data); \
} \
hp_entry_t *cur_entry = hp_fast_alloc_hprof_entry(); \
(cur_entry)->hash_code = hash_code; \
(cur_entry)->name_hprof = symbol; \
(cur_entry)->prev_hprof = (*(entries)); \
/* Call the universal callback */ \
hp_mode_common_beginfn((entries), (cur_entry)); \
/* Call the mode's beginfn callback */ \
XHPROF_G(mode_cb).begin_fn_cb((entries), (cur_entry)); \
/* Update entries linked list */ \
(*(entries)) = (cur_entry); \
} \
} while (0)
begin_fn_cb
|| 这个就是记录当前函数开始,记录的CPU和内存大小
void hp_mode_hier_beginfn_cb(hp_entry_t **entries, hp_entry_t *current)
{
/* Get start tsc counter */
current->tsc_start = cycle_timer();
/* Get CPU usage */
if (XHPROF_G(xhprof_flags) & XHPROF_FLAGS_CPU) {
current->cpu_start = cpu_timer();
}
/* Get memory usage */
if (XHPROF_G(xhprof_flags) & XHPROF_FLAGS_MEMORY) {
current->mu_start_hprof = zend_memory_usage(0);
current->pmu_start_hprof = zend_memory_peak_usage(0);
}
}