当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

转载于:https://www.cnblogs.com/breg/p/3830145.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值