PHP xhprof 扩展分析原理

简介:由于公司项目,最近需要分析后端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);
    }
}

 

上一篇:灯具产品CB认证测试标准详解


下一篇:DataTable到Access