当cpu飙升时,找出php中可能有问题的代码行

当你发现一个平时占用cpu比较少的进程突然间占用cpu接近100%时,你如何找到导致cpu飙升的原因?我的思路是,首先找到进程正在执行的代码行,从而确定可能有问题的代码段。然后,再仔细分析有问题的代码段,从而找出原因。

如果你的程序使用的是c、c++编写,那么你可以很容易的找到正在执行的代码行。但是,程序是php编写的,如何找到可能有问题的代码行呢?这个问题就是本文要解决的问题。

背景知识:

如果你对c语言不熟悉的话,可以略过,直接看 示例演示。

大家都知道php是一个解释性语言。用户编写的php代码会生成opcode,由解释器引擎去解释执行。在解释执行过程中,有一个全局变量包含了执 行过程中用到的各种数据。它就是executor_globals。在源码的Zend/zend_globals.h 文件中可以找到他的类型定义。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

struct _zend_executor_globals {

    zval **return_value_ptr_ptr;

    zval uninitialized_zval;

    zval *uninitialized_zval_ptr;

    zval error_zval;

    zval *error_zval_ptr;

    zend_ptr_stack arg_types_stack;

    /* symbol table cache */

    HashTable *symtable_cache[SYMTABLE_CACHE_SIZE];

    HashTable **symtable_cache_limit;

    HashTable **symtable_cache_ptr;

    zend_op **opline_ptr;

    HashTable *active_symbol_table;

    HashTable symbol_table;     /* main symbol table */

    HashTable included_files;   /* files already included */

    JMP_BUF *bailout;

    int error_reporting;

    int orig_error_reporting;

    int exit_status;

    zend_op_array *active_op_array;

    HashTable *function_table;  /* function symbol table */

    HashTable *class_table;     /* class table */

    HashTable *zend_constants;  /* constants table */

    zend_class_entry *scope;

    zend_class_entry *called_scope; /* Scope of the calling class */

    zval *This;

    long precision;

    int ticks_count;

    zend_bool in_execution;

    HashTable *in_autoload;

    zend_function *autoload_func;

    zend_bool full_tables_cleanup;

    /* for extended information support */

    zend_bool no_extensions;

#ifdef ZEND_WIN32

    zend_bool timed_out;

    OSVERSIONINFOEX windows_version_info;

#endif

    HashTable regular_list;

    HashTable persistent_list;

    zend_vm_stack argument_stack;

    int user_error_handler_error_reporting;

    zval *user_error_handler;

    zval *user_exception_handler;

    zend_stack user_error_handlers_error_reporting;

    zend_ptr_stack user_error_handlers;

    zend_ptr_stack user_exception_handlers;

    zend_error_handling_t  error_handling;

    zend_class_entry      *exception_class;

    /* timeout support */

    int timeout_seconds;

    int lambda_count;

    HashTable *ini_directives;

    HashTable *modified_ini_directives;

    zend_objects_store objects_store;

    zval *exception, *prev_exception;

    zend_op *opline_before_exception;

    zend_op exception_op[3];

    struct _zend_execute_data *current_execute_data;

    struct _zend_module_entry *current_module;

    zend_property_info std_property_info;

    zend_bool active;

    void *saved_fpu_cw;

    void *reserved[ZEND_MAX_RESERVED_RESOURCES];

};

这里我们只说两个对我们比较重要的变量,active_op_array 和 current_execute_data。
active_op_array变量中保存了引擎正在执行的op_array(想了解什么是op_array请点击查看)。在Zend/zend_compile.h中有关于op_array的数据类型的定义。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

struct _zend_op_array {

    /* Common elements */

    zend_uchar type;

    char *function_name;

    zend_class_entry *scope;

    zend_uint fn_flags;

    union _zend_function *prototype;

    zend_uint num_args;

    zend_uint required_num_args;

    zend_arg_info *arg_info;

    zend_bool pass_rest_by_reference;

    unsigned char return_reference;

    /* END of common elements */

    zend_bool done_pass_two;

    zend_uint *refcount;

    zend_op *opcodes;

    zend_uint last, size;

    zend_compiled_variable *vars;

    int last_var, size_var;

    zend_uint T;

    zend_brk_cont_element *brk_cont_array;

    int last_brk_cont;

    int current_brk_cont;

    zend_try_catch_element *try_catch_array;

    int last_try_catch;

    /* static variables support */

    HashTable *static_variables;

    zend_op *start_op;

    int backpatch_count;

    zend_uint this_var;

    char *filename;

    zend_uint line_start;

    zend_uint line_end;

    char *doc_comment;

    zend_uint doc_comment_len;

    zend_uint early_binding; /* the linked list of delayed declarations */

    void *reserved[ZEND_MAX_RESERVED_RESOURCES];

};

看完定义,就不用我多说了把。定义中,filename和 function_name分别保存了正在执行的文件名和方法名。

current_execute_data保存了正在执行的op_array的execute_data。execute_data保存了每个op_array执行过程中的一些数据。其定义在,Zend/zend_compile.h:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

struct _zend_execute_data {

    struct _zend_op *opline;

    zend_function_state function_state;

    zend_function *fbc; /* Function Being Called */

    zend_class_entry *called_scope;

    zend_op_array *op_array;

    zval *object;

    union _temp_variable *Ts;

    zval ***CVs;

    HashTable *symbol_table;

    struct _zend_execute_data *prev_execute_data;

    zval *old_error_reporting;

    zend_bool nested;

    zval **original_return_value;

    zend_class_entry *current_scope;

    zend_class_entry *current_called_scope;

    zval *current_this;

    zval *current_object;

    struct _zend_op *call_opline;

};

定义中的opline就是正在执行的opcode。opcode的结构定义如下:


1

2

3

4

5

6

7

8

9

struct _zend_op {

    opcode_handler_t handler;

    znode result;

    znode op1;

    znode op2;

    ulong extended_value;

    uint lineno;

    zend_uchar opcode;

};

其中lineno就是opcode所对应的行号。

示例说明:
看完上面的数据结构定义,你是否已经知道如何找php正在执行的文件名,方法名和行号呢?如果还有疑问的话,那就接着看下面的例子。创建一个文件test.php,代码如下:


1

2

3

4

5

6

7

8

<?php

function test1(){

        while(true){

              sleep(1);

        }

}

test1();

?>

cli方式执行php脚本,加入执行的进程号为14973。我们使用gdb命令来调试进程。


1

2

3

4

5

6

7

$sudo gdb -p 14973

(gdb) print (char *)executor_globals.active_op_array->filename

$1 = 0x9853a34 "/home/xinhailong/test/php/test.php"

(gdb) print (char *)executor_globals.active_op_array->function_name

$2 = 0x9854db8 "test1"

(gdb) print executor_globals->current_execute_data->opline->lineno

$3 = 4

很显然,他正在执行第四行的sleep方法。

如果上面的方法你感觉麻烦,那你可以使用.gdbinit文件。这个文件在php源码的根目录下。使用方法如下:


1

2

3

4

5

6

$sudo gdb -p 14973

(gdb) source /home/xinhailong/.gdbinit

(gdb) zbacktrace

[0xa453f34] sleep(1) /home/xinhailong/test/php/test.php:4

[0xa453ed0] test1() /home/xinhailong/test/php/test.php:7

(gdb)

题外话:
?从php5.6开始,php中集成了一个phpdbg的工具。可以像gdb调试c语言程序一样,调试php程序。感兴趣的话,可以打开下面的连接看看。

https://wiki.php.net/rfc/phpdbg

http://phpdbg.com/docs

当cpu飙升时,找出php中可能有问题的代码行

时间: 2024-10-11 20:58:12

当cpu飙升时,找出php中可能有问题的代码行的相关文章

找出矩阵中含有0最多的一行(find the longest row of zero)

对于一个n*n的矩阵,其中只包含有0,1两种元素且,所有的0都在1之前,请找出矩阵中0最多的一行.(Given an N-by-N matrix of 0s and 1s such that in each row no 0 comes before a 1, find the row with the most 0s in O(N) time.) 初看这题,想到的算法就是每一行都设置一个计数器,记录每行的0的个数,然后找出最大值即可(暴力解法). 算法实现: int* find_the_lon

找出数组中出现次数超过一半的元素

题目:找出数组中出现次数超过一半的元素 解法:每次删除数组中两个不同的元素,删除后,要查找的那个元素的个数仍然超过删除后的元素总数的一半 #include <stdio.h> int half_number(int a[], int n) { if( a == NULL || n <= 0 ) return -1; int i, candidate; int times = 0; for( i=0; i<n; i++ ) { if( times == 0 ) { candidate

算法之找出数组中出现次数大于n/m的元素

最经典的题目莫过于是: 在一个数组中找出出现次数超过n/2的元素?更进一步的,找出数组中出现次数大于n/3的所有元素? 注:这里有一个很重要的事实,那就是出现次数大于n/m的元素的个数至多为(m-1)个,比如出现次数大于n/3的至多只有两个. 关于这一类题目的解题思路,可以先讲一个游戏 称作 "俄罗斯方块".这里的规则是每一行的元素要完全不一样,一样的元素则总是在同一列,如果最下面的行已经被填满,那么消除最下面的行. 例如在数组 A = {7,3,3,7,4,3,4,7,3,4,3,4

找出数组中唯一重复的数(转)

题目: 数组a[N],1至N-1这N-1个数存放在a[N]中,其中某个数重复一次.写一个函数,找出被重复的数字. 方法一:异或法. 数组a[N]中的N个数异或结果与1至N-1异或的结果再做异或,得到的值即为所求. 设重复数为A,其余N-2个数异或结果为B. N个数异或结果为A^A^B 1至N-1异或结果为A^B 由于异或满足交换律和结合律,且X^X = 0  0^X = X; 则有 (A^B)^(A^A^B)=A^B^B=A 代码: #include <stdio.h> #include &l

Dijkstra 算法,用于对有权图进行搜索,找出图中两点的最短距离

Dijkstra 算法,用于对有权图进行搜索,找出图中两点的最短距离,既不是DFS搜索,也不是BFS搜索. 把Dijkstra 算法应用于无权图,或者所有边的权都相等的图,Dijkstra 算法等同于BFS搜索. http://www.cnblogs.com/biyeymyhjob/archive/2012/07/31/2615833.html 2.算法描述 1)算法思想:设G=(V,E)是一个带权有向图,把图中顶点集合V分成两组,第一组为已求出最短路径的顶点集合(用S表示,初始时S中只有一个源

找出数组中每个数右边第一个比它大的元素

题目 找出数组中每个数右边第一个比它大的元素. 思路 暴力解法 单调栈 使用栈结构.从前往后遍历数组每一位时,利用栈更新这一位之前每一位上的数的"右边第一个比它大的元素". 代码 public static int[] findMaxRightWithStack(int[] array) { if(array == null) return null; int n = array.length; int[] ret = new int[n]; Stack<Integer>

使用ps、top、ps_mem命令找出Linux中的最大内存消耗过程

使用ps.top.ps_mem命令找出Linux中的最大内存消耗过程 2020-02-08 16:06:59作者:自力稿源:云网牛站 您可能已经看到Linux系统多次消耗过多的内存,如果是这种情况,那么最好的办法是识别在Linux计算机上消耗过多内存的进程.使用top命令和ps命令可以很容易地识别出它,我曾经同时检查这两个命令,并且都得到了相同的结果. 使用ps命令在Linux中查找最大内存消耗过程 ps命令用于报告当前进程的快照,ps命令代表进程状态,这是一个标准的Linux应用程序,用于查找

经典算法学习——快速找出数组中两个数字,相加等于某特定值

这个算法题的描述如下:快速找出一个数组中的两个数字,让这两个数字之和等于一个给定的值.目前我假设数组中的都是各不相等的整数.这道题是我在一次面试中被问到的,由于各种原因,我没回答上来,十分尴尬.其实这道题十分简单,我们使用相对巧妙的方法来实现下.注意不使用两层循环的元素遍历.示例代码上传至:https://github.com/chenyufeng1991/SumTo100 . 算法描述如下: (0)首先对原数组进行排序,成为递增数组: (1)对排序后的数组头部i [0]和数组尾部j [n-1]

c语言代码编程题汇总:找出字符串中与输入的字母元素相同的个数以及其所对应数组的下标值

找出字符串中与输入的字母元素相同的个数以及其所对应数组的下标值 程序代码如下: 1 /* 2 2017年3月8日08:39:16 3 功能:找出字符串中与输入的字母元素相同的个数以及其所对应数组的下标值 4 */ 5 6 #include"stdio.h" 7 int main (void) 8 { 9 int i = 0, j = 0; 10 char a[100]; 11 char ch; 12 int num = 0; 13 14 printf ("please inp