diff --git a/extension/xhprof.c b/extension/xhprof.c index 7676797..8167d66 100644 --- a/extension/xhprof.c +++ b/extension/xhprof.c @@ -1,1802 +1,1735 @@ /* * Copyright (c) 2009 Facebook * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef linux /* To enable CPU_ZERO and CPU_SET, etc. */ # define _GNU_SOURCE #endif #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" #include "php_xhprof.h" #include "zend_extensions.h" #include #include #include #include #ifdef __FreeBSD__ # if __FreeBSD_version >= 700110 # include # include # define cpu_set_t cpuset_t # define SET_AFFINITY(pid, size, mask) \ cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1, size, mask) # define GET_AFFINITY(pid, size, mask) \ cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1, size, mask) # else # error "This version of FreeBSD does not support cpusets" # endif /* __FreeBSD_version */ #elif __APPLE__ /* * Patch for compiling in Mac OS X Leopard * @author Svilen Spasov */ # include # include # define cpu_set_t thread_affinity_policy_data_t # define CPU_SET(cpu_id, new_mask) \ (*(new_mask)).affinity_tag = (cpu_id + 1) # define CPU_ZERO(new_mask) \ (*(new_mask)).affinity_tag = THREAD_AFFINITY_TAG_NULL # define SET_AFFINITY(pid, size, mask) \ thread_policy_set(mach_thread_self(), THREAD_AFFINITY_POLICY, mask, \ THREAD_AFFINITY_POLICY_COUNT) #else /* For sched_getaffinity, sched_setaffinity */ # include # define SET_AFFINITY(pid, size, mask) sched_setaffinity(0, size, mask) # define GET_AFFINITY(pid, size, mask) sched_getaffinity(0, size, mask) #endif /* __FreeBSD__ */ /** * ********************** * GLOBAL MACRO CONSTANTS * ********************** */ /* XHProf version */ #define XHPROF_VERSION "0.9.2" /* Fictitious function name to represent top of the call tree. The paranthesis * in the name is to ensure we don't conflict with user function names. */ #define ROOT_SYMBOL "main()" /* Size of a temp scratch buffer */ #define SCRATCH_BUF_LEN 512 /* Various XHPROF modes. If you are adding a new mode, register the appropriate * callbacks in hp_begin() */ #define XHPROF_MODE_HIERARCHICAL 1 #define XHPROF_MODE_SAMPLED 620002 /* Rockfort's zip code */ /* Hierarchical profiling flags. * * Note: Function call counts and wall (elapsed) time are always profiled. * The following optional flags can be used to control other aspects of * profiling. */ #define XHPROF_FLAGS_NO_BUILTINS 0x0001 /* do not profile builtins */ #define XHPROF_FLAGS_CPU 0x0002 /* gather CPU times for funcs */ #define XHPROF_FLAGS_MEMORY 0x0004 /* gather memory usage for funcs */ /* Constants for XHPROF_MODE_SAMPLED */ #define XHPROF_SAMPLING_INTERVAL 100000 /* In microsecs */ #if !defined(uint64) typedef unsigned long long uint64; #endif #if !defined(uint32) typedef unsigned int uint32; #endif #if !defined(uint8) typedef unsigned char uint8; #endif /** * ***************************** * GLOBAL DATATYPES AND TYPEDEFS * ***************************** */ /* XHProf maintains a stack of entries being profiled. The memory for the entry * is passed by the layer that invokes BEGIN_PROFILING(), e.g. the hp_execute() * function. Often, this is just C-stack memory. * * This structure is a convenient place to track start time of a particular * profile operation, recursion depth, and the name of the function being * profiled. */ typedef struct hp_entry_t { char *name_hprof; /* function name */ int rlvl_hprof; /* recursion level for function */ uint64 tsc_start; /* start value for TSC counter */ long int mu_start_hprof; /* memory usage */ long int pmu_start_hprof; /* peak memory usage */ struct rusage ru_start_hprof; /* user/sys time start */ struct hp_entry_t *prev_hprof; /* ptr to prev entry being profiled */ uint8 hash_code; /* hash_code for the function name */ } hp_entry_t; /* Various types for XHPROF callbacks */ typedef void (*hp_init_cb) (TSRMLS_D); typedef void (*hp_exit_cb) (TSRMLS_D); typedef void (*hp_begin_function_cb) (hp_entry_t **entries, hp_entry_t *current TSRMLS_DC); typedef void (*hp_end_function_cb) (hp_entry_t **entries TSRMLS_DC); /* Struct to hold the various callbacks for a single xhprof mode */ typedef struct hp_mode_cb { hp_init_cb init_cb; hp_exit_cb exit_cb; hp_begin_function_cb begin_fn_cb; hp_end_function_cb end_fn_cb; } hp_mode_cb; /* 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() */ typedef struct hp_global_t { /* ---------- 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 */ uint64 sampling_interval_tsc; /* This array is used to store cpu frequencies for all available logical * cpus. For now, we assume the cpu frequencies will not change for power * saving or other reasons. If we need to worry about that in the future, we * can use a periodical timer to re-calculate this arrary every once in a * while (for example, every 1 or 5 seconds). */ double *cpu_frequencies; /* The number of logical CPUs this machine has. */ uint32 cpu_num; /* The saved cpu affinity. */ cpu_set_t prev_mask; /* The cpu id current process is bound to. (default 0) */ uint32 cur_cpu_id; /* XHProf flags */ uint32 xhprof_flags; /* counter table indexed by hash value of function names. */ uint8 func_hash_counters[256]; } hp_global_t; /** * *********************** * GLOBAL STATIC VARIABLES * *********************** */ /* XHProf global state */ static hp_global_t hp_globals; -#if PHP_VERSION_ID < 50500 -/* Pointer to the original execute function */ -static ZEND_DLEXPORT void (*_zend_execute) (zend_op_array *ops TSRMLS_DC); - -/* Pointer to the origianl execute_internal function */ -static ZEND_DLEXPORT void (*_zend_execute_internal) (zend_execute_data *data, - int ret TSRMLS_DC); -#else /* Pointer to the original execute function */ static void (*_zend_execute_ex) (zend_execute_data *execute_data TSRMLS_DC); /* Pointer to the origianl execute_internal function */ static void (*_zend_execute_internal) (zend_execute_data *data, struct _zend_fcall_info *fci, int ret TSRMLS_DC); -#endif /* Pointer to the original compile function */ static zend_op_array * (*_zend_compile_file) (zend_file_handle *file_handle, int type TSRMLS_DC); /* Pointer to the original compile string function (used by eval) */ static zend_op_array * (*_zend_compile_string) (zval *source_string, char *filename TSRMLS_DC); /** * **************************** * STATIC FUNCTION DECLARATIONS * **************************** */ static void hp_register_constants(INIT_FUNC_ARGS); static void hp_begin(long level, long xhprof_flags TSRMLS_DC); static void hp_stop(TSRMLS_D); static void hp_end(TSRMLS_D); static inline uint64 cycle_timer(); static double get_cpu_frequency(); static void clear_frequencies(); static void hp_free_the_free_list(); static hp_entry_t *hp_fast_alloc_hprof_entry(); static void hp_fast_free_hprof_entry(hp_entry_t *p); static inline uint8 hp_inline_hash(char * str); static void get_all_cpu_frequencies(); static long get_us_interval(struct timeval *start, struct timeval *end); static void incr_us_interval(struct timeval *start, uint64 incr); /* {{{ arginfo */ ZEND_BEGIN_ARG_INFO_EX(arginfo_xhprof_enable, 0, 0, 0) ZEND_ARG_INFO(0, flags) ZEND_ARG_INFO(0, options) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO(arginfo_xhprof_disable, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO(arginfo_xhprof_sample_enable, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO(arginfo_xhprof_sample_disable, 0) ZEND_END_ARG_INFO() /* }}} */ /** * ********************* * FUNCTION PROTOTYPES * ********************* */ int restore_cpu_affinity(cpu_set_t * prev_mask); int bind_to_cpu(uint32 cpu_id); /** * ********************* * PHP EXTENSION GLOBALS * ********************* */ /* List of functions implemented/exposed by xhprof */ zend_function_entry xhprof_functions[] = { PHP_FE(xhprof_enable, arginfo_xhprof_enable) PHP_FE(xhprof_disable, arginfo_xhprof_disable) PHP_FE(xhprof_sample_enable, arginfo_xhprof_sample_enable) PHP_FE(xhprof_sample_disable, arginfo_xhprof_sample_disable) {NULL, NULL, NULL} }; /* 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 }; 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) PHP_INI_END() /* Init module */ ZEND_GET_MODULE(xhprof) /** * ********************************** * PHP EXTENSION FUNCTION DEFINITIONS * ********************************** */ /** * Start XHProf profiling in hierarchical mode. * * @param long $flags flags for hierarchical mode * @return void * @author kannan */ 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() TSRMLS_CC, "|lz", &xhprof_flags, &optional_array) == FAILURE) { return; } hp_begin(XHPROF_MODE_HIERARCHICAL, xhprof_flags TSRMLS_CC); } /** * 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_FUNCTION(xhprof_disable) { if (hp_globals.enabled) { hp_stop(TSRMLS_C); RETURN_ZVAL(hp_globals.stats_count, 1, 0); } /* else null is returned */ } /** * Start XHProf profiling in sampling mode. * * @return void * @author cjiang */ PHP_FUNCTION(xhprof_sample_enable) { long xhprof_flags = 0; /* XHProf flags */ hp_begin(XHPROF_MODE_SAMPLED, xhprof_flags TSRMLS_CC); } /** * 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_FUNCTION(xhprof_sample_disable) { if (hp_globals.enabled) { hp_stop(TSRMLS_C); RETURN_ZVAL(hp_globals.stats_count, 1, 0); } /* else null is returned */ } /** * Module init callback. * * @author cjiang */ PHP_MINIT_FUNCTION(xhprof) { int i; REGISTER_INI_ENTRIES(); hp_register_constants(INIT_FUNC_ARGS_PASSTHRU); /* Get the number of available logical CPUs. */ hp_globals.cpu_num = sysconf(_SC_NPROCESSORS_CONF); /* Get the cpu affinity mask. */ #ifndef __APPLE__ if (GET_AFFINITY(0, sizeof(cpu_set_t), &hp_globals.prev_mask) < 0) { perror("getaffinity"); return FAILURE; } #else CPU_ZERO(&(hp_globals.prev_mask)); #endif /* Initialize cpu_frequencies and cur_cpu_id. */ hp_globals.cpu_frequencies = NULL; hp_globals.cur_cpu_id = 0; hp_globals.stats_count = NULL; /* no free hp_entry_t structures to start with */ hp_globals.entry_free_list = NULL; for (i = 0; i < 256; i++) { hp_globals.func_hash_counters[i] = 0; } #if defined(DEBUG) /* To make it random number generator repeatable to ease testing. */ srand(0); #endif return SUCCESS; } /** * Module shutdown callback. */ PHP_MSHUTDOWN_FUNCTION(xhprof) { /* Make sure cpu_frequencies is free'ed. */ clear_frequencies(); /* free any remaining items in the free list */ hp_free_the_free_list(); UNREGISTER_INI_ENTRIES(); return SUCCESS; } /** * Request init callback. Nothing to do yet! */ PHP_RINIT_FUNCTION(xhprof) { return SUCCESS; } /** * Request shutdown callback. Stop profiling and return. */ PHP_RSHUTDOWN_FUNCTION(xhprof) { hp_end(TSRMLS_C); return SUCCESS; } /** * Module info callback. Returns the xhprof version. */ PHP_MINFO_FUNCTION(xhprof) { char buf[SCRATCH_BUF_LEN]; char tmp[SCRATCH_BUF_LEN]; int i; int len; php_info_print_table_start(); php_info_print_table_header(2, "xhprof", XHPROF_VERSION); len = snprintf(buf, SCRATCH_BUF_LEN, "%d", hp_globals.cpu_num); buf[len] = 0; php_info_print_table_header(2, "CPU num", buf); if (hp_globals.cpu_frequencies) { /* Print available cpu frequencies here. */ php_info_print_table_header(2, "CPU logical id", " Clock Rate (MHz) "); for (i = 0; i < hp_globals.cpu_num; ++i) { len = snprintf(buf, SCRATCH_BUF_LEN, " CPU %d ", i); buf[len] = 0; len = snprintf(tmp, SCRATCH_BUF_LEN, "%f", hp_globals.cpu_frequencies[i]); tmp[len] = 0; php_info_print_table_row(2, buf, tmp); } } php_info_print_table_end(); } /** * *************************************************** * COMMON HELPER FUNCTION DEFINITIONS AND LOCAL MACROS * *************************************************** */ static void hp_register_constants(INIT_FUNC_ARGS) { REGISTER_LONG_CONSTANT("XHPROF_FLAGS_NO_BUILTINS", XHPROF_FLAGS_NO_BUILTINS, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("XHPROF_FLAGS_CPU", XHPROF_FLAGS_CPU, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("XHPROF_FLAGS_MEMORY", XHPROF_FLAGS_MEMORY, CONST_CS | CONST_PERSISTENT); } /** * Initialize profiler state * * @author kannan, veeve */ void hp_init_profiler_state(int level TSRMLS_DC) { /* Setup globals */ if (!hp_globals.ever_enabled) { hp_globals.ever_enabled = 1; hp_globals.entries = NULL; } hp_globals.profiler_level = (int) level; /* Init stats_count */ if (hp_globals.stats_count) { zval_dtor(hp_globals.stats_count); FREE_ZVAL(hp_globals.stats_count); } MAKE_STD_ZVAL(hp_globals.stats_count); array_init(hp_globals.stats_count); /* NOTE(cjiang): some fields such as cpu_frequencies take relatively longer * to initialize, (5 milisecond per logical cpu right now), therefore we * calculate them lazily. */ if (hp_globals.cpu_frequencies == NULL) { get_all_cpu_frequencies(); restore_cpu_affinity(&hp_globals.prev_mask); } /* bind to a random cpu so that we can use rdtsc instruction. */ bind_to_cpu((int) (rand() % hp_globals.cpu_num)); /* Call current mode's init cb */ hp_globals.mode_cb.init_cb(TSRMLS_C); } /** * Cleanup profiler state * * @author kannan, veeve */ void hp_clean_profiler_state(TSRMLS_D) { /* Call current mode's exit cb */ hp_globals.mode_cb.exit_cb(TSRMLS_C); /* Clear globals */ if (hp_globals.stats_count) { zval_dtor(hp_globals.stats_count); FREE_ZVAL(hp_globals.stats_count); hp_globals.stats_count = NULL; } hp_globals.entries = NULL; hp_globals.profiler_level = 1; hp_globals.ever_enabled = 0; } /* * Start profiling - called just before calling the actual function * NOTE: PLEASE MAKE SURE TSRMLS_CC IS AVAILABLE IN THE CONTEXT * OF THE FUNCTION WHERE THIS MACRO IS CALLED. * TSRMLS_CC CAN BE MADE AVAILABLE VIA TSRMLS_DC IN THE * CALLING FUNCTION OR BY CALLING TSRMLS_FETCH() * TSRMLS_FETCH() IS RELATIVELY EXPENSIVE. */ #define BEGIN_PROFILING(entries, symbol, profile_curr) \ do { \ /* Use a hash code to filter most of the string comparisons. */ \ uint8 hash_code = hp_inline_hash(symbol); \ 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) TSRMLS_CC); \ /* Call the mode's beginfn callback */ \ hp_globals.mode_cb.begin_fn_cb((entries), (cur_entry) TSRMLS_CC); \ /* Update entries linked list */ \ (*(entries)) = (cur_entry); \ } while (0) /* * Stop profiling - called just after calling the actual function * NOTE: PLEASE MAKE SURE TSRMLS_CC IS AVAILABLE IN THE CONTEXT * OF THE FUNCTION WHERE THIS MACRO IS CALLED. * TSRMLS_CC CAN BE MADE AVAILABLE VIA TSRMLS_DC IN THE * CALLING FUNCTION OR BY CALLING TSRMLS_FETCH() * TSRMLS_FETCH() IS RELATIVELY EXPENSIVE. */ #define END_PROFILING(entries, profile_curr) \ do { \ if (profile_curr) { \ hp_entry_t *cur_entry; \ /* Call the mode's endfn callback. */ \ /* NOTE(cjiang): we want to call this 'end_fn_cb' before */ \ /* 'hp_mode_common_endfn' to avoid including the time in */ \ /* 'hp_mode_common_endfn' in the profiling results. */ \ hp_globals.mode_cb.end_fn_cb((entries) TSRMLS_CC); \ cur_entry = (*(entries)); \ /* Call the universal callback */ \ hp_mode_common_endfn((entries), (cur_entry) TSRMLS_CC); \ /* Free top entry and update entries linked list */ \ (*(entries)) = (*(entries))->prev_hprof; \ hp_fast_free_hprof_entry(cur_entry); \ } \ } while (0) /** * Returns formatted function name * * @param entry hp_entry * @param result_buf ptr to result buf * @param result_len max size of result buf * @return total size of the function name returned in result_buf * @author veeve */ size_t hp_get_entry_name(hp_entry_t *entry, char *result_buf, size_t result_len) { /* Validate result_len */ if (result_len <= 1) { /* Insufficient result_bug. Bail! */ return 0; } /* Add '@recurse_level' if required */ /* NOTE: Dont use snprintf's return val as it is compiler dependent */ if (entry->rlvl_hprof) { snprintf(result_buf, result_len, "%s@%d", entry->name_hprof, entry->rlvl_hprof); } else { snprintf(result_buf, result_len, "%s", entry->name_hprof); } /* Force null-termination at MAX */ result_buf[result_len - 1] = 0; return strlen(result_buf); } /** * Build a caller qualified name for a callee. * * For example, if A() is caller for B(), then it returns "A==>B". * Recursive invokations are denoted with @ where n is the recursion * depth. * * For example, "foo==>foo@1", and "foo@2==>foo@3" are examples of direct * recursion. And "bar==>foo@1" is an example of an indirect recursive * call to foo (implying the foo() is on the call stack some levels * above). * * @author kannan, veeve */ size_t hp_get_function_stack(hp_entry_t *entry, int level, char *result_buf, size_t result_len) { size_t len = 0; /* End recursion if we dont need deeper levels or we dont have any deeper * levels */ if (!entry->prev_hprof || (level <= 1)) { return hp_get_entry_name(entry, result_buf, result_len); } /* Take care of all ancestors first */ len = hp_get_function_stack(entry->prev_hprof, level - 1, result_buf, result_len); /* Append the delimiter */ # define HP_STACK_DELIM "==>" # define HP_STACK_DELIM_LEN (sizeof(HP_STACK_DELIM) - 1) if (result_len < (len + HP_STACK_DELIM_LEN)) { /* Insufficient result_buf. Bail out! */ return len; } /* Add delimiter only if entry had ancestors */ if (len) { strncat(result_buf + len, HP_STACK_DELIM, result_len - len); len += HP_STACK_DELIM_LEN; } # undef HP_STACK_DELIM_LEN # undef HP_STACK_DELIM /* Append the current function name */ return len + hp_get_entry_name(entry, result_buf + len, result_len - len); } /** * Takes an input of the form /a/b/c/d/foo.php and returns * a pointer to one-level directory and basefile name * (d/foo.php) in the same string. */ static const char *hp_get_base_filename(const char *filename) { const char *ptr; int found = 0; if (!filename) return ""; /* reverse search for "/" and return a ptr to the next char */ for (ptr = filename + strlen(filename) - 1; ptr >= filename; ptr--) { if (*ptr == '/') { found++; } if (found == 2) { return ptr + 1; } } /* no "/" char found, so return the whole string */ return filename; } /** * Get the name of the current function. The name is qualified with * the class name if the function is in a class. * * @author kannan, hzhao */ static char *hp_get_function_name(zend_op_array *ops TSRMLS_DC) { zend_execute_data *data; const char *func = NULL; const char *cls = NULL; char *ret = NULL; int len; zend_function *curr_func; data = EG(current_execute_data); if (data) { /* shared meta data for function on the call stack */ curr_func = data->function_state.function; /* extract function name from the meta info */ func = curr_func->common.function_name; if (func) { /* previously, the order of the tests in the "if" below was * flipped, leading to incorrect function names in profiler * reports. When a method in a super-type is invoked the * profiler should qualify the function name with the super-type * class name (not the class name based on the run-time type * of the object. */ if (curr_func->common.scope) { cls = curr_func->common.scope->name; } else if (data->object) { cls = Z_OBJCE(*data->object)->name; } if (cls) { len = strlen(cls) + strlen(func) + 10; ret = (char*)emalloc(len); snprintf(ret, len, "%s::%s", cls, func); } else { ret = estrdup(func); } } else { long curr_op; int add_filename = 0; /* we are dealing with a special directive/function like * include, eval, etc. */ -#if ZEND_EXTENSION_API_NO >= 220121212 if (data->prev_execute_data) { curr_op = data->prev_execute_data->opline->extended_value; } else { curr_op = data->opline->extended_value; } -#elif ZEND_EXTENSION_API_NO >= 220100525 - curr_op = data->opline->extended_value; -#else - curr_op = data->opline->op2.u.constant.value.lval; -#endif switch (curr_op) { case ZEND_EVAL: func = "eval"; break; case ZEND_INCLUDE: func = "include"; add_filename = 1; break; case ZEND_REQUIRE: func = "require"; add_filename = 1; break; case ZEND_INCLUDE_ONCE: func = "include_once"; add_filename = 1; break; case ZEND_REQUIRE_ONCE: func = "require_once"; add_filename = 1; break; default: func = "???_op"; break; } /* For some operations, we'll add the filename as part of the function * name to make the reports more useful. So rather than just "include" * you'll see something like "run_init::foo.php" in your reports. */ if (add_filename){ const char *filename; int len; filename = hp_get_base_filename((curr_func->op_array).filename); len = strlen("run_init") + strlen(filename) + 3; ret = (char *)emalloc(len); snprintf(ret, len, "run_init::%s", filename); } else { ret = estrdup(func); } } } return ret; } /** * Free any items in the free list. */ static void hp_free_the_free_list() { hp_entry_t *p = hp_globals.entry_free_list; hp_entry_t *cur; while (p) { cur = p; p = p->prev_hprof; free(cur); } } /** * Fast allocate a hp_entry_t structure. Picks one from the * free list if available, else does an actual allocate. * * Doesn't bother initializing allocated memory. * * @author kannan */ static hp_entry_t *hp_fast_alloc_hprof_entry() { hp_entry_t *p; p = hp_globals.entry_free_list; if (p) { hp_globals.entry_free_list = p->prev_hprof; return p; } else { return (hp_entry_t *)malloc(sizeof(hp_entry_t)); } } /** * Fast free a hp_entry_t structure. Simply returns back * the hp_entry_t to a free list and doesn't actually * perform the free. * * @author kannan */ static void hp_fast_free_hprof_entry(hp_entry_t *p) { /* we use/overload the prev_hprof field in the structure to link entries in * the free list. */ p->prev_hprof = hp_globals.entry_free_list; hp_globals.entry_free_list = p; } /** * Increment the count of the given stat with the given count * If the stat was not set before, inits the stat to the given count * * @param zval *counts Zend hash table pointer * @param char *name Name of the stat * @param long count Value of the stat to incr by * @return void * @author kannan */ void hp_inc_count(zval *counts, char *name, long count TSRMLS_DC) { HashTable *ht; void *data; if (!counts) return; ht = HASH_OF(counts); if (!ht) return; if (zend_hash_find(ht, name, strlen(name) + 1, &data) == SUCCESS) { ZVAL_LONG(*(zval**)data, Z_LVAL_PP((zval**)data) + count); } else { add_assoc_long(counts, name, count); } } /** * Looksup the hash table for the given symbol * Initializes a new array() if symbol is not present * * @author kannan, veeve */ zval * hp_hash_lookup(char *symbol TSRMLS_DC) { HashTable *ht; void *data; zval *counts = (zval *) 0; /* Bail if something is goofy */ if (!hp_globals.stats_count || !(ht = HASH_OF(hp_globals.stats_count))) { return (zval *) 0; } /* Lookup our hash table */ if (zend_hash_find(ht, symbol, strlen(symbol) + 1, &data) == SUCCESS) { /* Symbol already exists */ counts = *(zval **) data; } else { /* Add symbol to hash table */ MAKE_STD_ZVAL(counts); array_init(counts); add_assoc_zval(hp_globals.stats_count, symbol, counts); } return counts; } /** * Truncates the given timeval to the nearest slot begin, where * the slot size is determined by intr * * @param tv Input timeval to be truncated in place * @param intr Time interval in microsecs - slot width * @return void * @author veeve */ void hp_trunc_time(struct timeval *tv, uint64 intr) { uint64 time_in_micro; /* Convert to microsecs and trunc that first */ time_in_micro = (tv->tv_sec * 1000000) + tv->tv_usec; time_in_micro /= intr; time_in_micro *= intr; /* Update tv */ tv->tv_sec = (time_in_micro / 1000000); tv->tv_usec = (time_in_micro % 1000000); } /** * Sample the stack. Add it to the stats_count global. * * @param tv current time * @param entries func stack as linked list of hp_entry_t * @return void * @author veeve */ void hp_sample_stack(hp_entry_t **entries TSRMLS_DC) { char key[SCRATCH_BUF_LEN]; char symbol[SCRATCH_BUF_LEN * 1000]; /* Build key */ snprintf(key, sizeof(key), "%d.%06d", hp_globals.last_sample_time.tv_sec, hp_globals.last_sample_time.tv_usec); /* Init stats in the global stats_count hashtable */ hp_get_function_stack(*entries, INT_MAX, symbol, sizeof(symbol)); add_assoc_string(hp_globals.stats_count, key, symbol, 1); return; } /** * Checks to see if it is time to sample the stack. * Calls hp_sample_stack() if its time. * * @param entries func stack as linked list of hp_entry_t * @param last_sample time the last sample was taken * @param sampling_intr sampling interval in microsecs * @return void * @author veeve */ void hp_sample_check(hp_entry_t **entries TSRMLS_DC) { /* Validate input */ if (!entries || !(*entries)) { return; } /* See if its time to sample. While loop is to handle a single function * taking a long time and passing several sampling intervals. */ while ((cycle_timer() - hp_globals.last_sample_tsc) > hp_globals.sampling_interval_tsc) { /* bump last_sample_tsc */ hp_globals.last_sample_tsc += hp_globals.sampling_interval_tsc; /* bump last_sample_time - HAS TO BE UPDATED BEFORE calling hp_sample_stack */ incr_us_interval(&hp_globals.last_sample_time, XHPROF_SAMPLING_INTERVAL); /* sample the stack */ hp_sample_stack(entries TSRMLS_CC); } return; } /** * *********************** * High precision timer related functions. * *********************** */ /** * Get time stamp counter (TSC) value via 'rdtsc' instruction. * * @return 64 bit unsigned integer * @author cjiang */ static inline uint64 cycle_timer() { uint32 __a,__d; uint64 val; asm volatile("rdtsc" : "=a" (__a), "=d" (__d)); (val) = ((uint64)__a) | (((uint64)__d)<<32); return val; } /** * Bind the current process to a specified CPU. This function is to ensure that * the OS won't schedule the process to different processors, which would make * values read by rdtsc unreliable. * * @param uint32 cpu_id, the id of the logical cpu to be bound to. * @return int, 0 on success, and -1 on failure. * * @author cjiang */ int bind_to_cpu(uint32 cpu_id) { cpu_set_t new_mask; CPU_ZERO(&new_mask); CPU_SET(cpu_id, &new_mask); if (SET_AFFINITY(0, sizeof(cpu_set_t), &new_mask) < 0) { perror("setaffinity"); return -1; } /* record the cpu_id the process is bound to. */ hp_globals.cur_cpu_id = cpu_id; return 0; } /** * Get time delta in microseconds. */ static long get_us_interval(struct timeval *start, struct timeval *end) { return (((end->tv_sec - start->tv_sec) * 1000000) + (end->tv_usec - start->tv_usec)); } /** * Incr time with the given microseconds. */ static void incr_us_interval(struct timeval *start, uint64 incr) { incr += (start->tv_sec * 1000000 + start->tv_usec); start->tv_sec = incr/1000000; start->tv_usec = incr%1000000; return; } /** * Convert from TSC counter values to equivalent microseconds. * * @param uint64 count, TSC count value * @param double cpu_frequency, the CPU clock rate (MHz) * @return 64 bit unsigned integer * * @author cjiang */ static inline double get_us_from_tsc(uint64 count, double cpu_frequency) { return count / cpu_frequency; } /** * Convert microseconds to equivalent TSC counter ticks * * @param uint64 microseconds * @param double cpu_frequency, the CPU clock rate (MHz) * @return 64 bit unsigned integer * * @author veeve */ static inline uint64 get_tsc_from_us(uint64 usecs, double cpu_frequency) { return (uint64) (usecs * cpu_frequency); } /** * This is a microbenchmark to get cpu frequency the process is running on. The * returned value is used to convert TSC counter values to microseconds. * * @return double. * @author cjiang */ static double get_cpu_frequency() { struct timeval start; struct timeval end; if (gettimeofday(&start, 0)) { perror("gettimeofday"); return 0.0; } uint64 tsc_start = cycle_timer(); /* Sleep for 5 miliseconds. Comparaing with gettimeofday's few microseconds * execution time, this should be enough. */ usleep(5000); if (gettimeofday(&end, 0)) { perror("gettimeofday"); return 0.0; } uint64 tsc_end = cycle_timer(); return (tsc_end - tsc_start) * 1.0 / (get_us_interval(&start, &end)); } /** * Calculate frequencies for all available cpus. * * @author cjiang */ static void get_all_cpu_frequencies() { int id; double frequency; hp_globals.cpu_frequencies = malloc(sizeof(double) * hp_globals.cpu_num); if (hp_globals.cpu_frequencies == NULL) { return; } /* Iterate over all cpus found on the machine. */ for (id = 0; id < hp_globals.cpu_num; ++id) { /* Only get the previous cpu affinity mask for the first call. */ if (bind_to_cpu(id)) { clear_frequencies(); return; } /* Make sure the current process gets scheduled to the target cpu. This * might not be necessary though. */ usleep(0); frequency = get_cpu_frequency(); if (frequency == 0.0) { clear_frequencies(); return; } hp_globals.cpu_frequencies[id] = frequency; } } /** * Restore cpu affinity mask to a specified value. It returns 0 on success and * -1 on failure. * * @param cpu_set_t * prev_mask, previous cpu affinity mask to be restored to. * @return int, 0 on success, and -1 on failure. * * @author cjiang */ int restore_cpu_affinity(cpu_set_t * prev_mask) { if (SET_AFFINITY(0, sizeof(cpu_set_t), prev_mask) < 0) { perror("restore setaffinity"); return -1; } /* default value ofor cur_cpu_id is 0. */ hp_globals.cur_cpu_id = 0; return 0; } /** * Reclaim the memory allocated for cpu_frequencies. * * @author cjiang */ static void clear_frequencies() { if (hp_globals.cpu_frequencies) { free(hp_globals.cpu_frequencies); hp_globals.cpu_frequencies = NULL; } restore_cpu_affinity(&hp_globals.prev_mask); } /** * *************************** * XHPROF DUMMY CALLBACKS * *************************** */ void hp_mode_dummy_init_cb(TSRMLS_D) { } void hp_mode_dummy_exit_cb(TSRMLS_D) { } void hp_mode_dummy_beginfn_cb(hp_entry_t **entries, hp_entry_t *current TSRMLS_DC) { } void hp_mode_dummy_endfn_cb(hp_entry_t **entries TSRMLS_DC) { } /** * **************************** * XHPROF COMMON CALLBACKS * **************************** */ /** * XHPROF universal begin function. * This function is called for all modes before the * mode's specific begin_function callback is called. * * @param hp_entry_t **entries linked list (stack) * of hprof entries * @param hp_entry_t *current hprof entry for the current fn * @return void * @author kannan, veeve */ void hp_mode_common_beginfn(hp_entry_t **entries, hp_entry_t *current TSRMLS_DC) { hp_entry_t *p; /* This symbol's recursive level */ int recurse_level = 0; if (hp_globals.func_hash_counters[current->hash_code] > 0) { /* Find this symbols recurse level */ for(p = (*entries); p; p = p->prev_hprof) { if (!strcmp(current->name_hprof, p->name_hprof)) { recurse_level = (p->rlvl_hprof) + 1; break; } } } hp_globals.func_hash_counters[current->hash_code]++; /* Init current function's recurse level */ current->rlvl_hprof = recurse_level; } /** * XHPROF universal end function. This function is called for all modes after * the mode's specific end_function callback is called. * * @param hp_entry_t **entries linked list (stack) of hprof entries * @return void * @author kannan, veeve */ void hp_mode_common_endfn(hp_entry_t **entries, hp_entry_t *current TSRMLS_DC) { hp_globals.func_hash_counters[current->hash_code]--; } /** * ********************************* * XHPROF INIT MODULE CALLBACKS * ********************************* */ /** * XHPROF_MODE_SAMPLED's init callback * * @author veeve */ void hp_mode_sampled_init_cb(TSRMLS_D) { struct timeval now; uint64 truncated_us; uint64 truncated_tsc; double cpu_freq = hp_globals.cpu_frequencies[hp_globals.cur_cpu_id]; /* Init the last_sample in tsc */ hp_globals.last_sample_tsc = cycle_timer(); /* Find the microseconds that need to be truncated */ gettimeofday(&hp_globals.last_sample_time, 0); now = hp_globals.last_sample_time; hp_trunc_time(&hp_globals.last_sample_time, XHPROF_SAMPLING_INTERVAL); /* Subtract truncated time from last_sample_tsc */ truncated_us = get_us_interval(&hp_globals.last_sample_time, &now); truncated_tsc = get_tsc_from_us(truncated_us, cpu_freq); if (hp_globals.last_sample_tsc > truncated_tsc) { /* just to be safe while subtracting unsigned ints */ hp_globals.last_sample_tsc -= truncated_tsc; } /* Convert sampling interval to ticks */ hp_globals.sampling_interval_tsc = get_tsc_from_us(XHPROF_SAMPLING_INTERVAL, cpu_freq); } /** * ************************************ * XHPROF BEGIN FUNCTION CALLBACKS * ************************************ */ /** * XHPROF_MODE_HIERARCHICAL's begin function callback * * @author kannan */ void hp_mode_hier_beginfn_cb(hp_entry_t **entries, hp_entry_t *current TSRMLS_DC) { /* Get start tsc counter */ current->tsc_start = cycle_timer(); /* Get CPU usage */ if (hp_globals.xhprof_flags & XHPROF_FLAGS_CPU) { getrusage(RUSAGE_SELF, &(current->ru_start_hprof)); } /* Get memory usage */ if (hp_globals.xhprof_flags & XHPROF_FLAGS_MEMORY) { current->mu_start_hprof = zend_memory_usage(0 TSRMLS_CC); current->pmu_start_hprof = zend_memory_peak_usage(0 TSRMLS_CC); } } /** * XHPROF_MODE_SAMPLED's begin function callback * * @author veeve */ void hp_mode_sampled_beginfn_cb(hp_entry_t **entries, hp_entry_t *current TSRMLS_DC) { /* See if its time to take a sample */ hp_sample_check(entries TSRMLS_CC); } /** * ********************************** * XHPROF END FUNCTION CALLBACKS * ********************************** */ /** * XHPROF shared end function callback * * @author kannan */ zval * hp_mode_shared_endfn_cb(hp_entry_t *top, char *symbol TSRMLS_DC) { zval *counts; uint64 tsc_end; /* Get end tsc counter */ tsc_end = cycle_timer(); /* Get the stat array */ if (!(counts = hp_hash_lookup(symbol TSRMLS_CC))) { return (zval *) 0; } /* Bump stats in the counts hashtable */ hp_inc_count(counts, "ct", 1 TSRMLS_CC); hp_inc_count(counts, "wt", get_us_from_tsc(tsc_end - top->tsc_start, hp_globals.cpu_frequencies[hp_globals.cur_cpu_id]) TSRMLS_CC); return counts; } /** * XHPROF_MODE_HIERARCHICAL's end function callback * * @author kannan */ void hp_mode_hier_endfn_cb(hp_entry_t **entries TSRMLS_DC) { hp_entry_t *top = (*entries); zval *counts; struct rusage ru_end; char symbol[SCRATCH_BUF_LEN]; long int mu_end; long int pmu_end; /* Get the stat array */ hp_get_function_stack(top, 2, symbol, sizeof(symbol)); if (!(counts = hp_mode_shared_endfn_cb(top, symbol TSRMLS_CC))) { return; } if (hp_globals.xhprof_flags & XHPROF_FLAGS_CPU) { /* Get CPU usage */ getrusage(RUSAGE_SELF, &ru_end); /* Bump CPU stats in the counts hashtable */ hp_inc_count(counts, "cpu", (get_us_interval(&(top->ru_start_hprof.ru_utime), &(ru_end.ru_utime)) + get_us_interval(&(top->ru_start_hprof.ru_stime), &(ru_end.ru_stime))) TSRMLS_CC); } if (hp_globals.xhprof_flags & XHPROF_FLAGS_MEMORY) { /* Get Memory usage */ mu_end = zend_memory_usage(0 TSRMLS_CC); pmu_end = zend_memory_peak_usage(0 TSRMLS_CC); /* Bump Memory stats in the counts hashtable */ hp_inc_count(counts, "mu", mu_end - top->mu_start_hprof TSRMLS_CC); hp_inc_count(counts, "pmu", pmu_end - top->pmu_start_hprof TSRMLS_CC); } } /** * XHPROF_MODE_SAMPLED's end function callback * * @author veeve */ void hp_mode_sampled_endfn_cb(hp_entry_t **entries TSRMLS_DC) { /* See if its time to take a sample */ hp_sample_check(entries TSRMLS_CC); } /** * *************************** * PHP EXECUTE/COMPILE PROXIES * *************************** */ /** * XHProf enable replaced the zend_execute function with this * new execute function. We can do whatever profiling we need to * before and after calling the actual zend_execute(). * * @author hzhao, kannan */ -#if PHP_VERSION_ID < 50500 -ZEND_DLEXPORT void hp_execute (zend_op_array *ops TSRMLS_DC) { -#else ZEND_DLEXPORT void hp_execute_ex (zend_execute_data *execute_data TSRMLS_DC) { zend_op_array *ops = execute_data->op_array; -#endif char *func = NULL; int hp_profile_flag = 1; func = hp_get_function_name(ops TSRMLS_CC); if (!func) { -#if PHP_VERSION_ID < 50500 - _zend_execute(ops TSRMLS_CC); -#else _zend_execute_ex(execute_data TSRMLS_CC); -#endif return; } BEGIN_PROFILING(&hp_globals.entries, func, hp_profile_flag); -#if PHP_VERSION_ID < 50500 - _zend_execute(ops TSRMLS_CC); -#else _zend_execute_ex(execute_data TSRMLS_CC); -#endif if (hp_globals.entries) { END_PROFILING(&hp_globals.entries, hp_profile_flag); } efree(func); } #undef EX #define EX(element) ((execute_data)->element) /** * Very similar to hp_execute. Proxy for zend_execute_internal(). * Applies to zend builtin functions. * * @author hzhao, kannan */ -#if PHP_VERSION_ID < 50500 -#define EX_T(offset) (*(temp_variable *)((char *) EX(Ts) + offset)) - -ZEND_DLEXPORT void hp_execute_internal(zend_execute_data *execute_data, - int ret TSRMLS_DC) { -#else #define EX_T(offset) (*EX_TMP_VAR(execute_data, offset)) ZEND_DLEXPORT void hp_execute_internal(zend_execute_data *execute_data, struct _zend_fcall_info *fci, int ret TSRMLS_DC) { -#endif zend_execute_data *current_data; char *func = NULL; int hp_profile_flag = 1; current_data = EG(current_execute_data); func = hp_get_function_name(current_data->op_array TSRMLS_CC); if (func) { BEGIN_PROFILING(&hp_globals.entries, func, hp_profile_flag); } if (!_zend_execute_internal) { /* no old override to begin with. so invoke the builtin's implementation */ - -#if ZEND_EXTENSION_API_NO >= 220121212 /* PHP 5.5. This is just inlining a copy of execute_internal(). */ if (fci != NULL) { ((zend_internal_function *) execute_data->function_state.function)->handler( fci->param_count, *fci->retval_ptr_ptr, fci->retval_ptr_ptr, fci->object_ptr, 1 TSRMLS_CC); } else { zval **return_value_ptr = &EX_TMP_VAR(execute_data, execute_data->opline->result.var)->var.ptr; ((zend_internal_function *) execute_data->function_state.function)->handler( execute_data->opline->extended_value, *return_value_ptr, (execute_data->function_state.function->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? return_value_ptr : NULL, execute_data->object, ret TSRMLS_CC); } -#elif ZEND_EXTENSION_API_NO >= 220100525 - zend_op *opline = EX(opline); - temp_variable *retvar = &EX_T(opline->result.var); - ((zend_internal_function *) EX(function_state).function)->handler( - opline->extended_value, - retvar->var.ptr, - (EX(function_state).function->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? - &retvar->var.ptr:NULL, - EX(object), ret TSRMLS_CC); -#else - zend_op *opline = EX(opline); - ((zend_internal_function *) EX(function_state).function)->handler( - opline->extended_value, - EX_T(opline->result.u.var).var.ptr, - EX(function_state).function->common.return_reference ? - &EX_T(opline->result.u.var).var.ptr:NULL, - EX(object), ret TSRMLS_CC); -#endif } else { /* call the old override */ -#if PHP_VERSION_ID < 50500 - _zend_execute_internal(execute_data, ret TSRMLS_CC); -#else _zend_execute_internal(execute_data, fci, ret TSRMLS_CC); -#endif } if (func) { if (hp_globals.entries) { END_PROFILING(&hp_globals.entries, hp_profile_flag); } efree(func); } } /** * Proxy for zend_compile_file(). Used to profile PHP compilation time. * * @author kannan, hzhao */ ZEND_DLEXPORT zend_op_array* hp_compile_file(zend_file_handle *file_handle, int type TSRMLS_DC) { const char *filename; char *func; int len; zend_op_array *ret; int hp_profile_flag = 1; filename = hp_get_base_filename(file_handle->filename); len = strlen("load") + strlen(filename) + 3; func = (char *)emalloc(len); snprintf(func, len, "load::%s", filename); BEGIN_PROFILING(&hp_globals.entries, func, hp_profile_flag); ret = _zend_compile_file(file_handle, type TSRMLS_CC); if (hp_globals.entries) { END_PROFILING(&hp_globals.entries, hp_profile_flag); } efree(func); return ret; } /** * Proxy for zend_compile_string(). Used to profile PHP eval compilation time. */ ZEND_DLEXPORT zend_op_array* hp_compile_string(zval *source_string, char *filename TSRMLS_DC) { char *func; int len; zend_op_array *ret; int hp_profile_flag = 1; len = strlen("eval") + strlen(filename) + 3; func = (char *)emalloc(len); snprintf(func, len, "eval::%s", filename); BEGIN_PROFILING(&hp_globals.entries, func, hp_profile_flag); ret = _zend_compile_string(source_string, filename TSRMLS_CC); if (hp_globals.entries) { END_PROFILING(&hp_globals.entries, hp_profile_flag); } efree(func); return ret; } /** * ************************** * MAIN XHPROF CALLBACKS * ************************** */ /** * This function gets called once when xhprof gets enabled. * It replaces all the functions like zend_execute, zend_execute_internal, * etc that needs to be instrumented with their corresponding proxies. */ static void hp_begin(long level, long xhprof_flags TSRMLS_DC) { if (!hp_globals.enabled) { int hp_profile_flag = 1; hp_globals.enabled = 1; hp_globals.xhprof_flags = (uint32)xhprof_flags; /* 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 */ -#if PHP_VERSION_ID < 50500 - _zend_execute = zend_execute; - zend_execute = hp_execute; -#else _zend_execute_ex = zend_execute_ex; zend_execute_ex = hp_execute_ex; -#endif /* Replace zend_execute_internal with our proxy */ _zend_execute_internal = zend_execute_internal; if (!(hp_globals.xhprof_flags & XHPROF_FLAGS_NO_BUILTINS)) { /* if NO_BUILTINS is not set (i.e. user wants to profile builtins), * then we intercept internal (builtin) function calls. */ zend_execute_internal = hp_execute_internal; } /* Initialize with the dummy mode first Having these dummy callbacks saves * us from checking if any of the callbacks are NULL everywhere. */ hp_globals.mode_cb.init_cb = hp_mode_dummy_init_cb; hp_globals.mode_cb.exit_cb = hp_mode_dummy_exit_cb; hp_globals.mode_cb.begin_fn_cb = hp_mode_dummy_beginfn_cb; hp_globals.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: hp_globals.mode_cb.begin_fn_cb = hp_mode_hier_beginfn_cb; hp_globals.mode_cb.end_fn_cb = hp_mode_hier_endfn_cb; break; case XHPROF_MODE_SAMPLED: hp_globals.mode_cb.init_cb = hp_mode_sampled_init_cb; hp_globals.mode_cb.begin_fn_cb = hp_mode_sampled_beginfn_cb; hp_globals.mode_cb.end_fn_cb = hp_mode_sampled_endfn_cb; break; } /* one time initializations */ hp_init_profiler_state(level TSRMLS_CC); /* start profiling from fictitious main() */ BEGIN_PROFILING(&hp_globals.entries, ROOT_SYMBOL, hp_profile_flag); } } /** * Called at request shutdown time. Cleans the profiler's global state. */ static void hp_end(TSRMLS_D) { /* Bail if not ever enabled */ if (!hp_globals.ever_enabled) { return; } /* Stop profiler if enabled */ if (hp_globals.enabled) { hp_stop(TSRMLS_C); } /* Clean up state */ hp_clean_profiler_state(TSRMLS_C); } /** * Called from xhprof_disable(). Removes all the proxies setup by * hp_begin() and restores the original values. */ static void hp_stop(TSRMLS_D) { int hp_profile_flag = 1; /* End any unfinished calls */ while (hp_globals.entries) { END_PROFILING(&hp_globals.entries, hp_profile_flag); } /* Remove proxies, restore the originals */ -#if PHP_VERSION_ID < 50500 - zend_execute = _zend_execute; -#else zend_execute_ex = _zend_execute_ex; -#endif zend_execute_internal = _zend_execute_internal; zend_compile_file = _zend_compile_file; zend_compile_string = _zend_compile_string; /* Resore cpu affinity. */ restore_cpu_affinity(&hp_globals.prev_mask); /* Stop profiling */ hp_globals.enabled = 0; }