微服务治理:APM-SkyWalking-PHP内核扩展源码分析

Posted by LB on Mon, Sep 7, 2020

SkyWalking APM作为服务遥测的关键技术点,为了能够更好地运用这项技术,我们需要拥有把握这项技术的底层能力。目前公司在PHP领域存活不少业务系统,针对PHP领域的APM技术,我们首先从分析这款PHP内核扩展程序下手。

一. 总体架构

PHP内核在php-fpm运行模式下是短生命周期,短生命周期的脚本运行如果直接连接SkyWalking的oap-server会造成大量的性能损耗,而且php也不擅长grpc通信,因此借助mesh架构思想为PHP-FPM进程池增加一个数据SideCar,主要的结构如下图所示:

从上图可以看出,PHP内核扩展程序拦截内核运行数据(主要是关键的外部IO调用)、数据被发送给SideCar,SideCar流转数据到SkyWalking-Server,数据流还可以被SkyWalking进行分析、从而通过WebHook流转报警时间到相关后续平台里。

二. PHP内核扩展源码分析

针对目前开源社区的SkyWalking-PHP内核源码进行分析,源码的分析主要包括以下几部分:

  1. 工程结构分析
  2. 关键生命周期分析
  3. 关键运行函数Hook分析

2.1 工程结构分析

SkyWalking PHP内核组件工程结构比较简单,主要是站在PHP内核基础上进行扩展设计与实现,主要包含的扩展文件有:

  1. b64.h:base64编码函数的头文件、主要包含内存分配、base64字符表、b64_encode及b64_decode、b64_decode_ex的函数声明。
  2. decode.c:base64序列化的函数具体实现。
  3. encode.c:base64反序列化的函数具体实现。
  4. components.h:针对skywalking协议中的component部分进行宏定义、这部分是apm协议的一部分,例如:tomcat、httpclient、dubbo、okhttp、grpc、jedis、更多查看附录1。
  5. php_skywalking.h:关键的内核扩展声明部分,主要包括:APM协议宏定义、Redis指令类别、memcache指令类别、ContextCarrier上下文结构体、apm拦截所需的关键函数定义(具体见附录二),apm关键函数hook定义(具体见附录三),全局变量定义(具体见附录四)。
  6. skywalking.c:具体内核扩展实现文件,里面包含了MI-MS、RI-RS、关键函数Hook等处理逻辑。

2.2 关键生命周期分析

这块将针对内核扩展关键生命周期进行分析。

2.2.1 关键生命期函数Hook定义

1static void (*ori_execute_ex)(zend_execute_data *execute_data); //PHP内核原始PHP层执行流程函数指针
2static void (*ori_execute_internal)(zend_execute_data *execute_data, zval *return_value);//PHP原始内核执行函数指针
3ZEND_API void sky_execute_ex(zend_execute_data *execute_data);//skywalking针对PHP层执行函数的替换指针
4ZEND_API void sky_execute_internal(zend_execute_data *execute_data, zval *return_value);//skywalking针对原始内核执行函数的替换指针

2.2.2 php.ini配置解析周期

 1PHP_INI_BEGIN()
 2#if SKY_DEBUG
 3	STD_PHP_INI_BOOLEAN("skywalking.enable",   	"1", PHP_INI_ALL, OnUpdateBool, enable, zend_skywalking_globals, skywalking_globals)  //读取skywalking.enable配置项
 4#else
 5	STD_PHP_INI_BOOLEAN("skywalking.enable",   	"0", PHP_INI_ALL, OnUpdateBool, enable, zend_skywalking_globals, skywalking_globals) //读取skywalking.enable配置项
 6#endif
 7	STD_PHP_INI_ENTRY("skywalking.version",   	"8", PHP_INI_ALL, OnUpdateLong, version, zend_skywalking_globals, skywalking_globals) //读取skywalking 版本配置项
 8    STD_PHP_INI_ENTRY("skywalking.app_code", UNKNOW_SERVICE_NAME, PHP_INI_ALL, OnUpdateString, app_code, zend_skywalking_globals, skywalking_globals) //读取微服务-服务名配置项
 9    STD_PHP_INI_ENTRY("skywalking.app_code_env_key", "APM_APP_CODE", PHP_INI_ALL, OnUpdateString, app_code_env_key, zend_skywalking_globals, skywalking_globals) //读取微服务-服务名环境变量 配置项
10    STD_PHP_INI_ENTRY("skywalking.sock_path", "/tmp/sky-agent.sock", PHP_INI_ALL, OnUpdateString, sock_path, zend_skywalking_globals, skywalking_globals) //读取微服务-agent通信unix路径配置项
11PHP_INI_END()

2.2.3 PHP内核模块初始化周期 - MI周期

MI周期是PHP进程的模块加载周期,这个周期内部会逐个加载内核扩展,并调用内核扩展MI周期的函数Hook,APM内核扩展对于这块的处理逻辑如下:

 1/* {{{ PHP_MINIT_FUNCTION
 2 */
 3PHP_MINIT_FUNCTION (skywalking) {
 4	ZEND_INIT_MODULE_GLOBALS(skywalking, php_skywalking_init_globals, NULL); //初始化模块变量
 5	//data_register_hashtable();
 6	REGISTER_INI_ENTRIES();
 7    /* If you have INI entries, uncomment these lines
 8	*/
 9    if (SKYWALKING_G(enable)) { //如果内核扩展功能开启了,则进行关键内核函数指针替换。
10        //屏蔽php的cli运行模式的APM监控
11        if (strcasecmp("cli", sapi_module.name) == 0 && cli_debug == 0) { 
12            return SUCCESS;
13        }
14
15        // 用户自定义函数执行器(php脚本定义的类、函数)
16        ori_execute_ex = zend_execute_ex; 
17        zend_execute_ex = sky_execute_ex;
18
19        // 内部函数执行器(c语言定义的类、函数)
20        ori_execute_internal = zend_execute_internal;
21        zend_execute_internal = sky_execute_internal;
22
23		// 托管curl内核函数:从zend函数表寻找内核函数、并使用内部的函数钩子进行拦截:自定函数钩子->exec、setopt、setopt_array、close
24		zend_function *old_function;
25		if ((old_function = zend_hash_str_find_ptr(CG(function_table), "curl_exec", sizeof("curl_exec") - 1)) != NULL) { //替换curl_exec函数
26			orig_curl_exec = old_function->internal_function.handler;
27			old_function->internal_function.handler = sky_curl_exec_handler;
28		}
29        if ((old_function = zend_hash_str_find_ptr(CG(function_table), "curl_setopt", sizeof("curl_setopt")-1)) != NULL) { //替换curl_setopt函数
30            orig_curl_setopt = old_function->internal_function.handler;
31            old_function->internal_function.handler = sky_curl_setopt_handler;
32        }
33        if ((old_function = zend_hash_str_find_ptr(CG(function_table), "curl_setopt_array", sizeof("curl_setopt_array")-1)) != NULL) { //替换curl_setopt_array函数
34            orig_curl_setopt_array = old_function->internal_function.handler;
35            old_function->internal_function.handler = sky_curl_setopt_array_handler;
36        }
37        if ((old_function = zend_hash_str_find_ptr(CG(function_table), "curl_close", sizeof("curl_close")-1)) != NULL) { //替换curl_close函数
38            orig_curl_close = old_function->internal_function.handler;
39            old_function->internal_function.handler = sky_curl_close_handler;
40        }
41	}
42
43	return SUCCESS;
44}
45/* }}} */

2.2.4 PHP内核请求初始化阶段 - RI周期

这个周期是PHP进程针对每次PHP请求入口进行的周期设计,RI周期会针对每次PHP请求均会执行,也是PHP-FPM模式的短生命周期的根源,针对这块源码的解读如下:

 1/* Remove if there's nothing to do at request start */
 2/* {{{ PHP_RINIT_FUNCTION7
 3 */
 4PHP_RINIT_FUNCTION(skywalking)
 5{
 6#if defined(COMPILE_DL_SKYWALKING) && defined(ZTS)
 7	ZEND_TSRMLS_CACHE_UPDATE();
 8#endif
 9    if (SKYWALKING_G(enable)) {
10        if (strcasecmp("cli", sapi_module.name) == 0 && cli_debug == 0) {
11            return SUCCESS;
12        }
13        sky_register(); //向APM-Agent进行服务注册逻辑,这个逻辑仅仅当application_instance == 0条件下执行,
14        if (application_instance == 0) { 
15            return SUCCESS;
16        }
17        sky_increment_id++; //因为PHP-FPM进程是线程安全的,所以此处可以单调的递加,变量主要用于构建Trace-Id
18        if (sky_increment_id >= 9999) {
19            sky_increment_id = 0;
20        }
21        request_init(); //初始化当前请求的APM数据,里面进行初始上下文、创建Trace、Span信息,及必要的父子Trace关系
22    }
23    return SUCCESS;
24}
25/* }}} */

2.2.5 PHP内核请求结束阶段 - RS周期

这个周期是PHP进程针对每次PHP请求结束进行的周期设计,RS周期会针对每次PHP请求的结束进行拦截,这个阶段一般进行内核资源回收操作:

 1/* Remove if there's nothing to do at request end */
 2/* {{{ PHP_RSHUTDOWN_FUNCTION
 3 */
 4PHP_RSHUTDOWN_FUNCTION(skywalking)
 5{
 6
 7	if(SKYWALKING_G(enable)){
 8        if (strcasecmp("cli", sapi_module.name) == 0 && cli_debug == 0) {
 9            return SUCCESS;
10        }
11        if (application_instance == 0) {
12            return SUCCESS;
13        }
14		    sky_flush_all();//发送APM数据给Agent程序
15        zval_dtor(&SKYWALKING_G(context)); //析构context hashtable、释放内存
16        zval_dtor(&SKYWALKING_G(curl_header));//析构curl_header hashtable、释放内存
17        zval_dtor(&SKYWALKING_G(curl_header_send));//析构curl_header_send hashtable、释放内存
18        zval_dtor(&SKYWALKING_G(UpstreamSegment));//析构UpstreamSegment hashtable、释放内存
19	}
20	return SUCCESS;
21}
22/* }}} */

三. 关键运行函数Hook分析

除了PHP生命期的关键环节,APM主要就是针对关键IO进行监控,因为这些IO是外部调用依赖的关键,接下来会分析不同的函数Hook的具体实现:

3.1 PHP用户态函数拦截

APM内核组件会对用户态函数进行执行拦截,通过拦截执行过程并构建APM需要的数据。

  1/* PHP 内核函数执行相关的核心内核数据结构
  2    struct _zend_execute_data {
  3    const zend_op       *opline;           executed opline                
  4    zend_execute_data *call;  current call                   
  5    zval *return_value;
  6    zend_function *func;  executed function             
  7    zval This;           this + call_info + num_args    
  8    zend_execute_data *prev_execute_data;
  9    zend_array *symbol_table;
 10    #if ZEND_EX_USE_RUN_TIME_CACHE
 11    void **run_time_cache;  cache op_array->run_time_cache 
 12    #endif
 13    #if ZEND_EX_USE_LITERALS
 14    zval *literals;  cache op_array->literals       
 15    #endif
 16    }
 17*/
 18// sky-apm 内核扩展注入的函数执行钩子 : 用户态函数拦截
 19ZEND_API void sky_execute_ex(zend_execute_data *execute_data) {
 20    if (application_instance == 0) { //如果当前请求周期不需要APM采集,则直接走原内核函数钩子
 21        ori_execute_ex(execute_data);
 22        return;
 23    }
 24
 25    zend_function *zf = execute_data->func; //拦截执行的函数
 26    const char *class_name = (zf->common.scope != NULL && zf->common.scope->name != NULL) ? ZSTR_VAL(
 27            zf->common.scope->name) : NULL; //获取class名称
 28    const char *function_name = zf->common.function_name == NULL ? NULL : ZSTR_VAL(zf->common.function_name); //获取function name
 29
 30    char *operationName = NULL; //定义操作名称
 31    char *peer = NULL;
 32    int componentId = 0; //组件Id
 33    if (class_name != NULL) {
 34        if (strcmp(class_name, "Predis\\Client") == 0 && strcmp(function_name, "executeCommand") == 0) {
 35            // 检测predis组件的executeCommand函数
 36            uint32_t arg_count = ZEND_CALL_NUM_ARGS(execute_data); 
 37            if (arg_count) {
 38                zval *p = ZEND_CALL_ARG(execute_data, 1);
 39
 40                zval *id = (zval *) emalloc(sizeof(zval));
 41                zend_call_method(p, Z_OBJCE_P(p), NULL, ZEND_STRL("getid"), id, 0, NULL, NULL);
 42
 43                if (Z_TYPE_P(id) == IS_STRING) { //构建predis函数类->函数方法的 操作名。
 44                    operationName = (char *) emalloc(strlen(class_name) + strlen(Z_STRVAL_P(id)) + 3);
 45                    componentId = COMPONENT_JEDIS;
 46                    strcpy(operationName, class_name);
 47                    strcat(operationName, "->");
 48                    strcat(operationName, Z_STRVAL_P(id));
 49                }
 50                efree(id);
 51            }
 52        } else if (strcmp(class_name, "Grpc\\BaseStub") == 0) { //拦截Grpc组件的关键方法。
 53            if (strcmp(function_name, "_simpleRequest") == 0
 54                || strcmp(function_name, "_clientStreamRequest") == 0
 55                || strcmp(function_name, "_serverStreamRequest") == 0
 56                || strcmp(function_name, "_bidiRequest") == 0
 57            ) {
 58                operationName = (char *) emalloc(strlen(class_name) + strlen(function_name) + 3);
 59                if (SKYWALKING_G(version) == 5) {
 60                    componentId = COMPONENT_GRPC;
 61                } else {
 62                    componentId = COMPONENT_RPC;
 63                }
 64                strcpy(operationName, class_name);
 65                strcat(operationName, "->");
 66                strcat(operationName, function_name);
 67            }
 68        }
 69    }
 70
 71    if (operationName != NULL) { //如果操作名称不为空,证明成功拦截到函数
 72        zval tags;
 73        array_init(&tags);
 74
 75      	//构建APM的tags属性,增加数据库类型、客户端组件类型
 76        if (strcmp(class_name, "Predis\\Client") == 0 && strcmp(function_name, "executeCommand") == 0) {
 77            add_assoc_string(&tags, "db.type", "redis");
 78            add_assoc_string(&tags, "component", "predis");
 79            zval *p = ZEND_CALL_ARG(execute_data, 1);
 80            zval *id = (zval *) emalloc(sizeof(zval));
 81            zval *arguments = (zval *) emalloc(sizeof(zval));
 82            zend_call_method(p, Z_OBJCE_P(p), NULL, ZEND_STRL("getid"), id, 0, NULL, NULL);
 83            zend_call_method(p, Z_OBJCE_P(p), NULL, ZEND_STRL("getarguments"), arguments, 0, NULL, NULL);
 84
 85            // 丰富APM的predis的连接peer信息
 86            zval *connection = sky_read_property(&(execute_data->This),"connection", 0);
 87            if (connection != NULL && Z_TYPE_P(connection) == IS_OBJECT && strcmp(sky_get_class_name(connection), "Predis\\Connection\\StreamConnection") == 0) {
 88                zval *parameters = sky_read_property(connection, "parameters", 0);
 89                if (parameters != NULL && Z_TYPE_P(parameters) == IS_OBJECT && strcmp(sky_get_class_name(parameters), "Predis\\Connection\\Parameters") == 0) {
 90                    zval *parameters_arr = sky_read_property(parameters, "parameters", 0);
 91                    if (Z_TYPE_P(parameters_arr) == IS_ARRAY) {
 92                        zval *predis_host = zend_hash_str_find(Z_ARRVAL_P(parameters_arr), "host", sizeof("host") - 1);
 93                        zval *predis_port = zend_hash_str_find(Z_ARRVAL_P(parameters_arr), "port", sizeof("port") - 1);
 94                        zval *port;
 95                        ZVAL_COPY(&port, predis_port);
 96                        if (Z_TYPE_P(port) != IS_LONG) {
 97                            convert_to_long(port);
 98                        }
 99
100                        if (Z_TYPE_P(predis_host) == IS_STRING && Z_TYPE_P(port) == IS_LONG) {
101                            const char *host = ZSTR_VAL(Z_STR_P(predis_host));
102                            peer = (char *) emalloc(strlen(host) + 10);
103                            bzero(peer, strlen(host) + 10);
104                            sprintf(peer, "%s:%" PRId3264, host, Z_LVAL_P(port));
105                        }
106                    }
107                }
108            }
109            // peer end
110
111            if (Z_TYPE_P(arguments) == IS_ARRAY) {
112                zend_ulong num_key;
113                zval *entry, str_entry;
114                smart_str command = {0};
115                smart_str_appends(&command, Z_STRVAL_P(id));
116                smart_str_appends(&command, " ");
117                ZEND_HASH_FOREACH_NUM_KEY_VAL(Z_ARRVAL_P(arguments), num_key, entry)
118                        {
119                            switch (Z_TYPE_P(entry)) {
120                                case IS_STRING:
121                                    smart_str_appends(&command, Z_STRVAL_P(entry));
122                                    smart_str_appends(&command, " ");
123                                    break;
124                                case IS_ARRAY:
125                                    break;
126                                default:
127                                    ZVAL_COPY(&str_entry, entry);
128                                    convert_to_string(&str_entry);
129                                    smart_str_appends(&command, Z_STRVAL_P(&str_entry));
130                                    smart_str_appends(&command, " ");
131                                    break;
132                            }
133                        }
134                ZEND_HASH_FOREACH_END();
135
136                // store command to tags
137                if (command.s) {
138                    smart_str_0(&command);
139                    add_assoc_string(&tags, "redis.command", ZSTR_VAL(command.s));
140                    smart_str_free(&command);
141                }
142            }
143            zval_ptr_dtor(id);
144            zval_ptr_dtor(arguments);
145            efree(id);
146            efree(arguments);
147        } else if (strcmp(class_name, "Grpc\\BaseStub") == 0) {
148            add_assoc_string(&tags, "rpc.type", "grpc");
149            add_assoc_string(&tags, "component", "php-grpc");
150            zval *p = ZEND_CALL_ARG(execute_data, 1);
151            if (Z_TYPE_P(p) == IS_STRING) {
152                add_assoc_string(&tags, "rpc.method", Z_STRVAL_P(p));
153            }
154
155            zval *hostname = sky_read_property(&(execute_data->This), "hostname", 1);
156            zval *hostname_override = sky_read_property(&(execute_data->This), "hostname_override", 1);
157
158            const char *host = NULL;
159            if (hostname_override != NULL && Z_TYPE_P(hostname_override) == IS_STRING) {
160                host = ZSTR_VAL(Z_STR_P(hostname_override));
161            } else if (hostname != NULL && Z_TYPE_P(hostname) == IS_STRING) {
162                host = ZSTR_VAL(Z_STR_P(hostname));
163            }
164
165            if (host != NULL) {
166                peer = (char *) emalloc(strlen(host) + 10);
167                bzero(peer, strlen(host) + 10);
168                sprintf(peer, "%s", host);
169            }
170        }
171
172        zval temp;
173        zval *spans = NULL;
174        zval *span_id = NULL;
175        zval *last_span = NULL;
176        char *l_millisecond;
177        long millisecond;
178        array_init(&temp);
179        spans = get_spans();
180        last_span = zend_hash_index_find(Z_ARRVAL_P(spans), zend_hash_num_elements(Z_ARRVAL_P(spans)) - 1);
181        span_id = zend_hash_str_find(Z_ARRVAL_P(last_span), "spanId", sizeof("spanId") - 1);
182
183        add_assoc_long(&temp, "spanId", Z_LVAL_P(span_id) + 1);
184        add_assoc_long(&temp, "parentSpanId", 0);
185        l_millisecond = get_millisecond();
186        millisecond = zend_atol(l_millisecond, strlen(l_millisecond));
187        efree(l_millisecond);
188        add_assoc_long(&temp, "startTime", millisecond);
189        add_assoc_long(&temp, "spanType", 1);
190        add_assoc_long(&temp, "spanLayer", 1);
191        add_assoc_long(&temp, "componentId", componentId);
192        add_assoc_string(&temp, "operationName", operationName);
193        add_assoc_string(&temp, "peer", peer == NULL ? "" : peer);
194        efree(operationName);
195        if (peer != NULL) {
196            efree(peer);
197        }
198
199        ori_execute_ex(execute_data); //前面已经注入了APM原始数据、正式执行原函数钩子
200
201        l_millisecond = get_millisecond();
202        millisecond = zend_atol(l_millisecond, strlen(l_millisecond));
203        efree(l_millisecond);
204
205        add_assoc_zval(&temp, "tags", &tags); //执行完后,注入请求监控的APM-tags
206        add_assoc_long(&temp, "endTime", millisecond); 
207        add_assoc_long(&temp, "isError", 0);
208
209        zend_hash_next_index_insert(Z_ARRVAL_P(spans), &temp);
210    } else {
211        ori_execute_ex(execute_data);
212    }
213}

3.2 PHP内核态函数拦截

APM内核组件会对PHP内核态函数进行执行拦截,通过拦截执行过程并构建APM需要的数据,相关源码解释如下:

  1//PHP非用户态函数拦截:例如PHP内核、PHP其他内核扩展
  2ZEND_API void sky_execute_internal(zend_execute_data *execute_data, zval *return_value) {
  3
  4    if (application_instance == 0) {
  5        if (ori_execute_internal) {
  6            ori_execute_internal(execute_data, return_value);
  7        } else {
  8            execute_internal(execute_data, return_value);
  9        }
 10        return;
 11    }
 12
 13    zend_function *zf = execute_data->func;
 14    const char *class_name = (zf->common.scope != NULL && zf->common.scope->name != NULL) ? ZSTR_VAL(
 15            zf->common.scope->name) : NULL; //获取函数所属的class名称
 16    const char *function_name = zf->common.function_name == NULL ? NULL : ZSTR_VAL(zf->common.function_name);//获取函数名称
 17    int is_procedural_mysqli = 0; // "Procedural style" or "Object oriented style" ?
 18    char *operationName = NULL;
 19    char *peer = NULL;
 20    char *component = NULL;
 21    int componentId = COMPONENT_MYSQL_JDBC_DRIVER;
 22    if (class_name != NULL) {
 23        if (strcmp(class_name, "PDO") == 0) { //拦截PDO类
 24            if (strcmp(function_name, "exec") == 0 
 25                || strcmp(function_name, "query") == 0
 26                || strcmp(function_name, "prepare") == 0
 27                || strcmp(function_name, "commit") == 0) {
 28                component = (char *) emalloc(strlen("PDO") + 1);
 29                strcpy(component, "PDO");
 30                operationName = (char *) emalloc(strlen(class_name) + strlen(function_name) + 3); //构建PDO操作名称
 31                strcpy(operationName, class_name);
 32                strcat(operationName, "->");
 33                strcat(operationName, function_name);
 34            }
 35        } else if (strcmp(class_name, "PDOStatement") == 0) { //拦截PDOStatement类操作
 36            if (strcmp(function_name, "execute") == 0) {
 37                component = (char *) emalloc(strlen("PDOStatement") + 1);
 38                strcpy(component, "PDOStatement");
 39                operationName = (char *) emalloc(strlen(class_name) + strlen(function_name) + 3); //构建PDOStatement操作名称
 40                strcpy(operationName, class_name);
 41                strcat(operationName, "->");
 42                strcat(operationName, function_name);
 43            }
 44        } else if (strcmp(class_name, "mysqli") == 0) {//拦截mysqli类操作
 45            if (strcmp(function_name, "query") == 0) {
 46                component = (char *) emalloc(strlen("mysqli") + 1);
 47                strcpy(component, "mysqli");
 48                operationName = (char *) emalloc(strlen(class_name) + strlen(function_name) + 3);
 49                strcpy(operationName, class_name);
 50                strcat(operationName, "->");
 51                strcat(operationName, function_name);
 52            }
 53        } else if (strcmp(class_name, "Yar_Client") == 0) { //拦截Yar_Client类操作
 54            if (strcmp(function_name, "__call") == 0) {
 55                if (SKYWALKING_G(version) == 5) {
 56                    componentId = COMPONENT_GRPC;
 57                } else {
 58                    componentId = COMPONENT_RPC;
 59                }
 60
 61                component = (char *) emalloc(strlen("Yar_Client") + 1);
 62                strcpy(component, "Yar_Client");
 63                uint32_t arg_count = ZEND_CALL_NUM_ARGS(execute_data);
 64                if (arg_count) {
 65                    zval *p = ZEND_CALL_ARG(execute_data, 1);
 66                    if (Z_TYPE_P(p) == IS_STRING) {
 67                        operationName = (char *) emalloc(strlen(class_name) + strlen(Z_STRVAL_P(p)) + 3);
 68                        strcpy(operationName, class_name);
 69                        strcat(operationName, "->");
 70                        strcat(operationName, Z_STRVAL_P(p));
 71                    }
 72                }
 73            }
 74        } else if (strcmp(class_name, "Redis") == 0 || strcmp(class_name, "RedisCluster") == 0) { //拦截phpredis内核组件
 75            char *fnamewall = sky_redis_fnamewall(function_name);
 76            if (sky_redis_opt_for_string_key(fnamewall) == 1) {
 77                componentId = COMPONENT_JEDIS;
 78                component = (char *) emalloc(strlen("Redis") + 1);
 79                strcpy(component, "Redis");
 80                operationName = (char *) emalloc(strlen(class_name) + strlen(function_name) + 3);
 81                strcpy(operationName, class_name);
 82                strcat(operationName, "->");
 83                strcat(operationName, function_name);
 84            }
 85            efree(fnamewall);
 86        } else if (strcmp(class_name, "Memcached") == 0) {//拦截Memcached内核组件
 87            char *fnamewall = sky_memcached_fnamewall(function_name);
 88            if (sky_memcached_opt_for_string_key(fnamewall) == 1) {
 89                componentId = COMPONENT_XMEMCACHED;
 90                component = (char *) emalloc(strlen("memcached") + 1);
 91                strcpy(component, "memcached");
 92                operationName = (char *) emalloc(strlen(class_name) + strlen(function_name) + 3);
 93                strcpy(operationName, class_name);
 94                strcat(operationName, "->");
 95                strcat(operationName, function_name);
 96            }
 97            efree(fnamewall);
 98        }
 99    } else if (function_name != NULL) {
100        if (strcmp(function_name, "mysqli_query") == 0) {//拦截mysqli_query函数
101            class_name = "mysqli";
102            function_name = "query";
103            component = (char *) emalloc(strlen(class_name) + 1);
104            strcpy(component, class_name);
105            operationName = (char *) emalloc(strlen(class_name) + strlen(function_name) + 3);
106            strcpy(operationName, class_name);
107            strcat(operationName, "->");
108            strcat(operationName, function_name);
109
110            is_procedural_mysqli = 1;
111        }
112    }
113
114    //如果在控制范围内的函数行为
115    if (operationName != NULL) {
116
117        zval tags;
118        array_init(&tags);
119
120        if (strcmp(class_name, "PDO") == 0) {//丰富PDO span tags
121            add_assoc_string(&tags, "component", "PHP-PDO");
122
123            // params
124            uint32_t arg_count = ZEND_CALL_NUM_ARGS(execute_data);
125            if (arg_count) {
126                zval *p = ZEND_CALL_ARG(execute_data, 1);
127                //db.statement
128                switch (Z_TYPE_P(p)) {
129                    case IS_STRING:
130                        add_assoc_string(&tags, "db.statement", Z_STRVAL_P(p));//丰富PDO 查询语句
131                        break;
132
133                }
134            }
135
136            char db_type[64] = {0};
137            pdo_dbh_t *dbh = Z_PDO_DBH_P(&(execute_data->This));
138            if (dbh != NULL) {
139                if (dbh->driver != NULL && dbh->driver->driver_name != NULL) {
140                    memcpy(db_type, (char *) dbh->driver->driver_name, dbh->driver->driver_name_len);
141                    add_assoc_string(&tags, "db.type", db_type);
142                }
143
144                if (dbh->data_source != NULL && db_type[0] != '\0') {
145                    add_assoc_string(&tags, "db.data_source", (char *) dbh->data_source);
146                    char *host = pcre_match("(host=([^;\\s]+))", sizeof("(host=([^;\\s]+))")-1, (char *) dbh->data_source);
147                    char *port = pcre_match("(port=([^;\\s]+))", sizeof("(port=([^;\\s]+))")-1, (char *) dbh->data_source);
148                    if (host != NULL && port != NULL) {
149                        peer = (char *) emalloc(strlen(host) + 10);
150                        bzero(peer, strlen(host) + 10);
151                        sprintf(peer, "%s:%s", host, port);
152                    }
153                    if (host != NULL) efree(host);
154                    if (port != NULL) efree(port);
155                }
156            }
157        } else if (strcmp(class_name, "PDOStatement") == 0) {
158            char db_type[64] = {0};
159            pdo_stmt_t *stmt = (pdo_stmt_t *) Z_PDO_STMT_P(&(execute_data->This));
160            if (stmt != NULL) {
161                add_assoc_string(&tags, "db.statement", stmt->query_string);
162                add_assoc_string(&tags, "component", "PHP-PDO");
163
164                if (stmt->dbh != NULL && stmt->dbh->driver->driver_name != NULL) {
165                    memcpy(db_type, (char *) stmt->dbh->driver->driver_name, stmt->dbh->driver->driver_name_len);
166                    add_assoc_string(&tags, "db.type", db_type);
167                }
168
169                if (db_type[0] != '\0' && stmt->dbh != NULL && stmt->dbh->data_source != NULL) {
170                    add_assoc_string(&tags, "db.data_source", (char *) stmt->dbh->data_source);
171                    char *host = pcre_match("(host=([^;\\s]+))", sizeof("(host=([^;\\s]+))")-1, (char *) stmt->dbh->data_source);
172                    char *port = pcre_match("(port=([^;\\s]+))", sizeof("(port=([^;\\s]+))")-1, (char *) stmt->dbh->data_source);
173                    if (host != NULL && port != NULL) {
174                        peer = (char *) emalloc(strlen(host) + 10);
175                        bzero(peer, strlen(host) + 10);
176                        sprintf(peer, "%s:%s", host, port);
177                    }
178                    if (host != NULL) efree(host);
179                    if (port != NULL) efree(port);
180                }
181            }
182        } else if (strcmp(class_name, "mysqli") == 0) {
183            if (strcmp(function_name, "query") == 0) {
184#ifdef MYSQLI_USE_MYSQLND
185                mysqli_object *mysqli = NULL;
186                if(is_procedural_mysqli) {
187                    mysqli = (mysqli_object *) Z_MYSQLI_P(ZEND_CALL_ARG(execute_data, 1));
188                } else {
189                    mysqli = (mysqli_object *) Z_MYSQLI_P(&(execute_data->This));
190                }
191
192                MYSQLI_RESOURCE *my_res = (MYSQLI_RESOURCE *) mysqli->ptr;
193                if (my_res && my_res->ptr) {
194                    MY_MYSQL *mysql = (MY_MYSQL *) my_res->ptr;
195                    if (mysql->mysql) {
196#if PHP_VERSION_ID >= 70100
197                        char *host = mysql->mysql->data->hostname.s;
198#else
199                        char *host = mysql->mysql->data->host;
200#endif
201                        char port[6];
202                        char nullHost[10] = "nullhost\0";
203
204                        sprintf(port, "%d", mysql->mysql->data->port);
205                        add_assoc_string(&tags, "db.port", port);
206                        if(host != NULL){
207                            add_assoc_string(&tags, "db.host", host);
208                            peer = (char *)emalloc(strlen(host) + 10);
209                            bzero(peer, strlen(host) + 10);
210                            sprintf(peer, "%s:%d", host, mysql->mysql->data->port);
211                        }else{
212                            add_assoc_string(&tags, "db.host", nullHost);
213                            peer = (char *)emalloc(strlen(nullHost) + 10);
214                            bzero(peer, strlen(nullHost) + 10);
215                            sprintf(peer, "%s:%d", nullHost, mysql->mysql->data->port);
216                        }
217                    }
218                }
219#endif
220                add_assoc_string(&tags, "db.type", "mysql");
221                add_assoc_string(&tags, "component", "PHP-mysqli");
222                // params
223                uint32_t arg_count = ZEND_CALL_NUM_ARGS(execute_data);
224                if (arg_count) {
225                    zval *p = is_procedural_mysqli ? ZEND_CALL_ARG(execute_data, 2) : ZEND_CALL_ARG(execute_data, 1);
226                    //db.statement
227                    switch (Z_TYPE_P(p)) {
228                        case IS_STRING:
229                            add_assoc_string(&tags, "db.statement", Z_STRVAL_P(p));
230                            break;
231
232                    }
233                }
234            }
235        } else if (strcmp(class_name, "Yar_Client") == 0) {
236            if (strcmp(function_name, "__call") == 0) {
237                zval rv, _uri;
238                ZVAL_STRING(&_uri, "_uri");
239                zval *yar_uri = Z_OBJ_HT(EX(This))->read_property(&EX(This), &_uri, BP_VAR_R, 0, &rv);
240                add_assoc_string(&tags, "component", "PHP-Yar");
241                add_assoc_string(&tags, "yar.uri", Z_STRVAL_P(yar_uri));
242                uint32_t arg_count = ZEND_CALL_NUM_ARGS(execute_data);
243                if (arg_count) {
244                    zval *p = ZEND_CALL_ARG(execute_data, 1);
245                    if (Z_TYPE_P(p) == IS_STRING) {
246                        add_assoc_string(&tags, "yar.method", Z_STRVAL_P(p));
247                    }
248                }
249            }
250        } else if (strcmp(class_name, "Redis") == 0 || strcmp(class_name, "RedisCluster") == 0) { //丰富phpredis 操作span数据
251            add_assoc_string(&tags, "db.type", "redis");
252            add_assoc_string(&tags, "component", "phpredis");
253            uint32_t arg_count = ZEND_CALL_NUM_ARGS(execute_data);
254
255            if (strcmp(class_name, "Redis") == 0) {
256                // find peer
257                zval *this = &(execute_data->This);
258                zval host;
259                zval port;
260                zend_call_method(this, Z_OBJCE_P(this), NULL, ZEND_STRL("gethost"), &host, 0, NULL, NULL);
261                zend_call_method(this, Z_OBJCE_P(this), NULL, ZEND_STRL("getport"), &port, 0, NULL, NULL);
262
263
264                if (!Z_ISUNDEF(host) && !Z_ISUNDEF(port) && Z_TYPE(host) == IS_STRING && Z_TYPE(port) == IS_LONG) {
265                    const char *h = ZSTR_VAL(Z_STR(host));
266                    peer = (char *) emalloc(strlen(h) + 10);
267                    bzero(peer, strlen(h) + 10);
268                    sprintf(peer, "%s:%" PRId3264, h, Z_LVAL(port));
269                }
270
271                if (!Z_ISUNDEF(host)) {
272                    zval_ptr_dtor(&host);
273                }
274
275                if (!Z_ISUNDEF(port)) {
276                    zval_ptr_dtor(&port);
277                }
278            }
279
280            smart_str command = {0};
281            char *fname = zend_str_tolower_dup((char *) function_name, strlen((char *) function_name));
282            smart_str_appends(&command, fname);
283            smart_str_appends(&command, " ");
284            efree(fname);
285
286            int is_string_command = 1;
287            int i;
288            for (i = 1; i < arg_count + 1; ++i) {
289                zval str_p;
290                zval *p = ZEND_CALL_ARG(execute_data, i);
291                if (Z_TYPE_P(p) == IS_ARRAY) {
292                    is_string_command = 0;
293                    break;
294                }
295
296                ZVAL_COPY(&str_p, p);
297                if (Z_TYPE_P(&str_p) != IS_STRING) {
298                    convert_to_string(&str_p);
299                }
300                if (i == 1) {
301                    add_assoc_string(&tags, "redis.key", Z_STRVAL_P(&str_p));//丰富phpredis key数据
302                }
303                char *tmp = zend_str_tolower_dup(Z_STRVAL_P(&str_p), Z_STRLEN_P(&str_p));
304                smart_str_appends(&command, tmp);
305                smart_str_appends(&command, " ");
306                efree(tmp);
307            }
308            // store command to tags
309            if (command.s) {
310                smart_str_0(&command);
311                if (is_string_command) {
312                    zend_string *trim_s = php_trim(command.s, NULL, 0, 3);
313                    add_assoc_string(&tags, "redis.command", ZSTR_VAL(trim_s));//丰富phpredis 指令数据
314                    zend_string_free(trim_s);
315                }
316                smart_str_free(&command);
317            }
318        } else if (strcmp(class_name, "Memcached") == 0) {
319
320            add_assoc_string(&tags, "db.type", "memcached");
321            add_assoc_string(&tags, "component", "PHP-Memcached");
322            uint32_t arg_count = ZEND_CALL_NUM_ARGS(execute_data);
323
324            smart_str command = {0};
325            smart_str_appends(&command, zend_str_tolower_dup((char *) function_name, strlen((char *) function_name)));
326            smart_str_appends(&command, " ");
327
328            int i;
329            for (i = 1; i < arg_count + 1; ++i) {
330                char *str = NULL;
331                zval str_p;
332                zval *p = ZEND_CALL_ARG(execute_data, i);
333                if (Z_TYPE_P(p) == IS_ARRAY) {
334                    str = sky_json_encode(p);
335                }
336
337                ZVAL_COPY(&str_p, p);
338                if (Z_TYPE_P(&str_p) != IS_ARRAY && Z_TYPE_P(&str_p) != IS_STRING) {
339                    convert_to_string(&str_p);
340                }
341
342                if (str == NULL) {
343                    str = Z_STRVAL_P(&str_p);
344                }
345
346                if (i == 1) {
347                    add_assoc_string(&tags, "memcached.key", str);
348                }
349                smart_str_appends(&command, zend_str_tolower_dup(str, strlen(str)));
350                smart_str_appends(&command, " ");
351            }
352            // store command to tags
353            if (command.s) {
354                smart_str_0(&command);
355                add_assoc_string(&tags, "memcached.command", ZSTR_VAL(php_trim(command.s, NULL, 0, 3)));
356                smart_str_free(&command);
357            }
358        }
359
360
361        zval temp;
362        zval *spans = NULL;
363        zval *span_id = NULL;
364        zval *last_span = NULL;
365        char *l_millisecond;
366        long millisecond;
367        array_init(&temp);
368        spans = get_spans();
369        last_span = zend_hash_index_find(Z_ARRVAL_P(spans), zend_hash_num_elements(Z_ARRVAL_P(spans)) - 1);
370        span_id = zend_hash_str_find(Z_ARRVAL_P(last_span), "spanId", sizeof("spanId") - 1);
371
372        add_assoc_long(&temp, "spanId", Z_LVAL_P(span_id) + 1);
373        add_assoc_long(&temp, "parentSpanId", 0);
374        l_millisecond = get_millisecond();
375        millisecond = zend_atol(l_millisecond, strlen(l_millisecond));
376        efree(l_millisecond);
377        add_assoc_long(&temp, "startTime", millisecond);
378        add_assoc_long(&temp, "spanType", 1);
379        add_assoc_long(&temp, "spanLayer", 1);
380//        add_assoc_string(&temp, "component", component);
381        add_assoc_long(&temp, "componentId", componentId);
382        add_assoc_string(&temp, "operationName", operationName);
383        add_assoc_string(&temp, "peer", peer == NULL ? "" : peer);
384        efree(component);
385        efree(operationName);
386        if (peer != NULL) {
387            efree(peer);
388        }
389
390      //执行PHP内核原函数指针
391        if (ori_execute_internal) {
392            ori_execute_internal(execute_data, return_value);
393        } else {
394            execute_internal(execute_data, return_value);
395        }
396
397        l_millisecond = get_millisecond();
398        millisecond = zend_atol(l_millisecond, strlen(l_millisecond));
399        efree(l_millisecond);
400
401
402        add_assoc_zval(&temp, "tags", &tags);
403        add_assoc_long(&temp, "endTime", millisecond);
404        add_assoc_long(&temp, "isError", 0); 
405
406        zend_hash_next_index_insert(Z_ARRVAL_P(spans), &temp);
407    } else {
408        if (ori_execute_internal) {
409            ori_execute_internal(execute_data, return_value);
410        } else {
411            execute_internal(execute_data, return_value);
412        }
413    }
414}

3.3 PHP-CURL 内核Hook拦截

  1//curl执行拦截
  2void sky_curl_exec_handler(INTERNAL_FUNCTION_PARAMETERS)
  3{
  4    if(application_instance == 0) {
  5        orig_curl_exec(INTERNAL_FUNCTION_PARAM_PASSTHRU);
  6        return;
  7    }
  8
  9	zval		*zid;
 10	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &zid) == FAILURE) {
 11		return;
 12	}
 13
 14	int is_send = 1;
 15
 16    zval function_name,curlInfo;
 17    zval params[1];
 18    ZVAL_COPY(&params[0], zid);
 19
 20    //执行curl_getinfo函数:
 21    ZVAL_STRING(&function_name,  "curl_getinfo");
 22    call_user_function(CG(function_table), NULL, &function_name, &curlInfo, 1, params); //从CG全局函数表、调用curl_getinfo函数
 23    zval_dtor(&function_name);
 24    zval_dtor(&params[0]);
 25
 26    //获取curl请求的url信息
 27    zval *z_url = zend_hash_str_find(Z_ARRVAL(curlInfo),  ZEND_STRL("url"));
 28    char *url_str = Z_STRVAL_P(z_url);
 29
 30    if(strlen(url_str) <= 0) {
 31        zval_dtor(&curlInfo);
 32        is_send = 0;
 33    }
 34
 35    //构建标准的HTTP URI结构
 36    php_url *url_info = NULL;
 37    if(is_send == 1) {
 38        url_info = php_url_parse(url_str); //解析
 39        if(url_info->scheme == NULL || url_info->host == NULL) {
 40            zval_dtor(&curlInfo);
 41            php_url_free(url_info); //释放内存
 42            is_send = 0;
 43        }
 44    }
 45
 46    char *sw = NULL;
 47    zval *spans = NULL;
 48    zval *last_span = NULL;
 49    zval *span_id = NULL;
 50    char *peer = NULL;
 51    char *operation_name = NULL;
 52    char *full_url = NULL;
 53
 54
 55    //确定要发送的请求、当前请求合理,才进行下面的信息丰富。
 56    if (is_send == 1) { 
 57
 58// for php7.3.0+
 59#if PHP_VERSION_ID >= 70300
 60        char *php_url_scheme = ZSTR_VAL(url_info->scheme);
 61        char *php_url_host = ZSTR_VAL(url_info->host);
 62        char *php_url_path = ZSTR_VAL(url_info->path);
 63        char *php_url_query = ZSTR_VAL(url_info->query);
 64#else
 65        char *php_url_scheme = url_info->scheme;
 66        char *php_url_host = url_info->host;
 67        char *php_url_path = url_info->path;
 68        char *php_url_query = url_info->query;
 69#endif
 70
 71        //处理url_info内部的被调用方端口值
 72        int peer_port = 0; 
 73        if (url_info->port) {
 74            peer_port = url_info->port;
 75        } else {
 76            if (strcasecmp("http", php_url_scheme) == 0) { //默认端口设置
 77                peer_port = 80;
 78            } else {
 79                peer_port = 443;
 80            }
 81        }
 82
 83        //处理url_info内部的query参数
 84        if (url_info->query){
 85            if (url_info->path == NULL) {
 86                spprintf(&operation_name, 0, "%s", "/");
 87                spprintf(&full_url, 0, "%s?%s", "/", php_url_query);
 88            } else {
 89                spprintf(&operation_name, 0, "%s", php_url_path);
 90                spprintf(&full_url, 0, "%s?%s", php_url_path, php_url_query);
 91            }
 92        }
 93        else
 94        {
 95            if (url_info->path == NULL) {
 96                spprintf(&operation_name, 0, "%s", "/");
 97                spprintf(&full_url, 0, "%s", "/");
 98            } else {
 99                spprintf(&operation_name, 0, "%s", php_url_path);
100                spprintf(&full_url, 0, "%s", php_url_path);
101            }
102        }
103
104        spans = get_spans(); //获取spans数组
105        last_span = zend_hash_index_find(Z_ARRVAL_P(spans), zend_hash_num_elements(Z_ARRVAL_P(spans)) - 1); //获取最后一个span
106        span_id = zend_hash_str_find(Z_ARRVAL_P(last_span), "spanId", sizeof("spanId") - 1);
107
108        //生成新的Trace-Span
109        if (SKYWALKING_G(version) == 5)
110        { // skywalking 5.x
111            spprintf(&peer, 0, "%s://%s:%d", php_url_scheme, php_url_host, peer_port);
112            sw = generate_sw3(Z_LVAL_P(span_id) + 1, peer, operation_name);
113        }
114        else if (SKYWALKING_G(version) == 6 || SKYWALKING_G(version) == 7)
115        { // skywalking 6.x
116            spprintf(&peer, 0, "%s:%d", php_url_host, peer_port);
117            sw = generate_sw6(Z_LVAL_P(span_id) + 1, peer);
118        }
119        else if (SKYWALKING_G(version) == 8)
120        {
121            spprintf(&peer, 0, "%s:%d", php_url_host, peer_port); //
122            sw = generate_sw8(Z_LVAL_P(span_id) + 1, peer); //生成version 8的span
123        }
124    }
125
126    /*
127     sw6_l = snprintf(NULL, 0, "sw8: 1-%s-%s-%" PRId3264 "-%s-%s-%s-%s",
128            Z_STRVAL(traceIdEncode),
129            Z_STRVAL(currentTraceIdEncode),
130            span_id,
131            Z_STRVAL(serviceEncode),
132            Z_STRVAL(serviceInstanceEncode),
133            Z_STRVAL(parentEndpointEncode),
134            Z_STRVAL(targetAddressEncode));
135
136    */
137    //如果span创建成功 : sw结构样例:
138    //sw8: 1-MS41NDY0LjE1OTg1MDU3ODEwMDAx-MS41NDY0LjE1OTg1MDU3ODEwMDAx-1-bG9jYWxfcGhw-YjFmN2QzNTctZTNkYi00ODkzLTk1NjktY2ZmOTczNWNlNDRh-L3NjX2N1cmxfcG9zdC5waHA=-MTI3LjAuMC4xOjkwOTA=
139    if (sw != NULL) {
140        zval *option = NULL;
141        int is_init = 0;
142        option = zend_hash_index_find(Z_ARRVAL_P(&SKYWALKING_G(curl_header)), Z_RES_HANDLE_P(zid));
143
144        if(option == NULL) {
145            option = emalloc(sizeof(zval));
146            bzero(option, sizeof(zval));
147            array_init(option);
148            is_init = 1;
149        }
150
151        add_next_index_string(option, sw); //sw字符串为sw8: 打头,所以天然就是HTTP header
152        add_index_bool(&SKYWALKING_G(curl_header_send), (zend_ulong)Z_RES_HANDLE_P(zid), IS_TRUE);
153
154        zval func;
155        zval argv[3];
156        zval ret;
157        ZVAL_STRING(&func, "curl_setopt");
158
159        ZVAL_COPY(&argv[0], zid);
160        ZVAL_LONG(&argv[1], CURLOPT_HTTPHEADER);
161        ZVAL_COPY(&argv[2], option);
162        call_user_function(CG(function_table), NULL, &func, &ret, 3, argv); //给curl header调用注入全链路Trace信息
163        zval_dtor(&ret);
164        zval_dtor(&func);
165        if(is_init == 1) {
166            zval_ptr_dtor(option);
167            efree(option);
168        }
169        zval_dtor(&argv[0]);
170        zval_dtor(&argv[1]);
171        zval_dtor(&argv[2]);
172        efree(sw);
173    }
174
175    zval temp;
176    char *l_millisecond;
177    long millisecond;
178    if(is_send == 1) {
179
180        array_init(&temp);
181
182        add_assoc_long(&temp, "spanId", Z_LVAL_P(span_id) + 1);
183        add_assoc_long(&temp, "parentSpanId", 0);
184        l_millisecond = get_millisecond();
185        millisecond = zend_atol(l_millisecond, strlen(l_millisecond));
186        efree(l_millisecond);
187        add_assoc_long(&temp, "startTime", millisecond);
188        add_assoc_long(&temp, "spanType", 1);
189        add_assoc_long(&temp, "spanLayer", 3);
190        add_assoc_long(&temp, "componentId", COMPONENT_HTTPCLIENT);
191    }
192
193
194	orig_curl_exec(INTERNAL_FUNCTION_PARAM_PASSTHRU); //执行原生的curl函数
195
196    if (is_send == 1) {
197        //结束时间获取
198        l_millisecond = get_millisecond();
199        millisecond = zend_atol(l_millisecond, strlen(l_millisecond));
200        efree(l_millisecond);
201
202        zval function_name_1, curlInfo_1;
203        zval params_1[1];
204        ZVAL_COPY(&params_1[0], zid);
205        ZVAL_STRING(&function_name_1, "curl_getinfo");
206        call_user_function(CG(function_table), NULL, &function_name_1, &curlInfo_1, 1, params_1);
207        zval_dtor(&params_1[0]);
208        zval_dtor(&function_name_1);
209
210        add_assoc_long(&temp, "endTime", millisecond);
211
212        add_assoc_string(&temp, "operationName", operation_name);
213        add_assoc_string(&temp, "peer", peer);
214
215        zval tags;
216        array_init(&tags);
217        add_assoc_string(&tags, "url", full_url);
218
219        add_assoc_string(&tags, "component", "PHP-CURL");
220        //HTTP 响应code
221        zval *z_http_code = zend_hash_str_find(Z_ARRVAL(curlInfo_1), ZEND_STRL("http_code"));
222      
223        if (Z_LVAL_P(z_http_code) != 200){
224            add_assoc_long(&temp, "isError", 1);
225        }
226        else
227        {
228            add_assoc_long(&temp, "isError", 0);
229        }
230
231        //针对http_code进行类型转化,便于增加tag
232        if (Z_TYPE_P(z_http_code) == IS_LONG)
233        {
234            convert_to_string(z_http_code);                                       //针对z_http_code进行类型转换
235            add_assoc_string(&tags, "http.status_code", Z_STRVAL_P(z_http_code)); //追加 reponse http code tag
236        }
237
238        add_assoc_zval(&temp, "tags", &tags);
239        efree(peer);
240        efree(operation_name);
241        efree(full_url);
242
243        php_url_free(url_info);
244
245        zval _refs;
246        array_init(&_refs);
247        add_assoc_zval(&temp, "refs", &_refs);
248        zend_hash_next_index_insert(Z_ARRVAL_P(spans), &temp);
249        zval_dtor(&curlInfo_1);
250        zval_dtor(&curlInfo);
251    }
252}

四. 关键附属函数分析

4.1 全局变量初始化函数

1//初始化内核扩展全局变量
2static void php_skywalking_init_globals(zend_skywalking_globals *skywalking_globals)
3{
4	skywalking_globals->app_code = NULL;
5	skywalking_globals->enable = 0;
6	skywalking_globals->version = 6;
7	skywalking_globals->sock_path = "/var/run/sky-agent.sock";
8    skywalking_globals->app_code_env_key = "APM_APP_CODE";
9}

4.2 json序列化函数

 1static char *sky_json_encode(zval *parameter){
 2
 3	smart_str buf = {0};
 4	zend_long options = 64;
 5#if PHP_VERSION_ID >= 70100
 6	if (php_json_encode(&buf, parameter, (int)options) != SUCCESS) {
 7		smart_str_free(&buf);
 8		return NULL;
 9	}
10#else
11	php_json_encode(&buf, parameter, (int)options);
12#endif
13	smart_str_0(&buf);
14	if(buf.s != NULL) {
15        char *bufs = emalloc(strlen(ZSTR_VAL(buf.s)) + 1);
16        strcpy(bufs, ZSTR_VAL(buf.s));
17        smart_str_free(&buf);
18        return bufs;
19	}
20    return NULL;
21}

4.3 发送APM数据函数

 1static void write_log(char *text) {
 2    if (application_instance != 0) {
 3        // to stream
 4        if(text == NULL || strlen(text) <= 0) {
 5            return;
 6        }
 7
 8        struct sockaddr_un un;
 9        un.sun_family = AF_UNIX;
10        strcpy(un.sun_path, SKYWALKING_G(sock_path));
11        int fd;
12        char *message = (char*) emalloc(strlen(text) + 10);
13        bzero(message, strlen(text) + 10);
14
15        fd = socket(AF_UNIX, SOCK_STREAM, 0);
16        if (fd >= 0) {
17            struct timeval tv;
18            tv.tv_sec = 0;
19            tv.tv_usec = 100000;
20            setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv);
21            int conn = connect(fd, (struct sockaddr *) &un, sizeof(un));
22
23            if (conn >= 0) {
24                sprintf(message, "1%s\n", text);
25                write(fd, message, strlen(message));
26            } else {
27                php_error_docref(NULL, E_WARNING, "[skywalking] failed to connect the sock.");
28            }
29            close(fd);
30        } else {
31            php_error_docref(NULL, E_WARNING, "[skywalking] failed to open the sock.");
32        }
33        efree(message);
34        efree(text);
35    }
36
37}

4.4 生成上下文函数

  1static void generate_context() {
  2    int sys_pid = getpid();
  3    long second = get_second();
  4    second = second * 10000 + sky_increment_id; //创建traceid的因子
  5    char *makeTraceId;
  6    makeTraceId = (char *) emalloc(sizeof(char) * 180); //分配traceId所需要的内存
  7
  8    bzero(makeTraceId, sizeof(char) * 180);
  9
 10    sprintf(makeTraceId, "%d.%d.%ld", application_instance, sys_pid, second);
 11
 12    add_assoc_string(&SKYWALKING_G(context), "currentTraceId", makeTraceId); //针对上下文context变量添加currentTraceId元素
 13    add_assoc_long(&SKYWALKING_G(context), "isChild", 0);
 14
 15    // parent
 16    zval *carrier = NULL;
 17    zval *sw;
 18
 19    zend_bool jit_initialization = PG(auto_globals_jit); //PHP全局变量
 20
 21    if (jit_initialization) {
 22        zend_string *server_str = zend_string_init("_SERVER", sizeof("_SERVER") - 1, 0);
 23        zend_is_auto_global(server_str);
 24        zend_string_release(server_str);
 25    }
 26    carrier = zend_hash_str_find(&EG(symbol_table), ZEND_STRL("_SERVER")); //获取查找$_SERVER超级全局变量
 27
 28    if(SKYWALKING_G(version) == 5) {
 29        sw = zend_hash_str_find(Z_ARRVAL_P(carrier), "HTTP_SW3", sizeof("HTTP_SW3") - 1);
 30
 31        if (sw != NULL && Z_TYPE_P(sw) == IS_STRING && Z_STRLEN_P(sw) > 10) {
 32            add_assoc_string(&SKYWALKING_G(context), "sw3", Z_STRVAL_P(sw));
 33
 34            zval temp;
 35            array_init(&temp);
 36
 37            php_explode(zend_string_init(ZEND_STRL("|"), 0), Z_STR_P(sw), &temp, 10);
 38
 39            if(zend_array_count(Z_ARRVAL_P(&temp)) >= 8) {
 40                zval *sw3_0 = zend_hash_index_find(Z_ARRVAL(temp), 0);
 41                zval *sw3_1 = zend_hash_index_find(Z_ARRVAL(temp), 1);
 42                zval *sw3_2 = zend_hash_index_find(Z_ARRVAL(temp), 2);
 43                zval *sw3_3 = zend_hash_index_find(Z_ARRVAL(temp), 3);
 44                zval *sw3_4 = zend_hash_index_find(Z_ARRVAL(temp), 4);
 45                zval *sw3_5 = zend_hash_index_find(Z_ARRVAL(temp), 5);
 46                zval *sw3_6 = zend_hash_index_find(Z_ARRVAL(temp), 6);
 47                zval *sw3_7 = zend_hash_index_find(Z_ARRVAL(temp), 7);
 48
 49                zval child;
 50                array_init(&child);
 51                ZVAL_LONG(&child, 1);
 52                zend_hash_str_update(Z_ARRVAL_P(&SKYWALKING_G(context)), "isChild", sizeof("isChild") - 1, &child);
 53
 54                add_assoc_string(&SKYWALKING_G(context), "parentTraceSegmentId", Z_STRVAL_P(sw3_0));
 55                add_assoc_long(&SKYWALKING_G(context), "parentSpanId", zend_atol(Z_STRVAL_P(sw3_1), sizeof(Z_STRVAL_P(sw3_1)) - 1));
 56                add_assoc_long(&SKYWALKING_G(context), "parentApplicationInstance", zend_atol(Z_STRVAL_P(sw3_2), sizeof(Z_STRVAL_P(sw3_2)) - 1));
 57                add_assoc_long(&SKYWALKING_G(context), "entryApplicationInstance", zend_atol(Z_STRVAL_P(sw3_3), sizeof(Z_STRVAL_P(sw3_3)) - 1));
 58                add_assoc_str(&SKYWALKING_G(context), "networkAddress", trim_sharp(sw3_4));
 59                add_assoc_str(&SKYWALKING_G(context), "entryOperationName", trim_sharp(sw3_5));
 60                add_assoc_str(&SKYWALKING_G(context), "parentOperationName", trim_sharp(sw3_6));
 61                add_assoc_string(&SKYWALKING_G(context), "distributedTraceId", Z_STRVAL_P(sw3_7));
 62            }
 63        } else {
 64            add_assoc_long(&SKYWALKING_G(context), "parentApplicationInstance", application_instance);
 65            add_assoc_long(&SKYWALKING_G(context), "entryApplicationInstance", application_instance);
 66            char *uri = get_page_request_uri();
 67            add_assoc_string(&SKYWALKING_G(context), "entryOperationName", (uri == NULL) ? "" : uri);
 68            add_assoc_string(&SKYWALKING_G(context), "distributedTraceId", makeTraceId);
 69            if(uri != NULL) {
 70                efree(uri);
 71            }
 72        }
 73    } else if (SKYWALKING_G(version) == 6 || SKYWALKING_G(version) == 7) {
 74        sw = zend_hash_str_find(Z_ARRVAL_P(carrier), "HTTP_SW6", sizeof("HTTP_SW6") - 1);
 75        if (sw != NULL && Z_TYPE_P(sw) == IS_STRING && Z_STRLEN_P(sw) > 10) {
 76            add_assoc_string(&SKYWALKING_G(context), "sw6", Z_STRVAL_P(sw));
 77
 78            zval temp;
 79            array_init(&temp);
 80
 81            php_explode(zend_string_init(ZEND_STRL("-"), 0), Z_STR_P(sw), &temp, 10);
 82
 83            if(zend_array_count(Z_ARRVAL_P(&temp)) >= 7) {
 84                zval *sw6_0 = zend_hash_index_find(Z_ARRVAL(temp), 0);
 85                zval *sw6_1 = zend_hash_index_find(Z_ARRVAL(temp), 1); // Trace Id
 86                zval *sw6_2 = zend_hash_index_find(Z_ARRVAL(temp), 2); // Parent trace segment Id
 87                zval *sw6_3 = zend_hash_index_find(Z_ARRVAL(temp), 3); // Parent span Id
 88                zval *sw6_4 = zend_hash_index_find(Z_ARRVAL(temp), 4); // Parent service instance Id
 89                zval *sw6_5 = zend_hash_index_find(Z_ARRVAL(temp), 5); // Entrance service instance Id
 90                zval *sw6_6 = zend_hash_index_find(Z_ARRVAL(temp), 6); // Target address of this request
 91
 92                zval *sw6_7 = NULL;
 93                zval *sw6_8 = NULL;
 94                if (zend_array_count(Z_ARRVAL_P(&temp)) >= 9) {
 95                    sw6_7 = zend_hash_index_find(Z_ARRVAL(temp), 7);
 96                    sw6_8 = zend_hash_index_find(Z_ARRVAL(temp), 8);
 97                }
 98
 99
100                zval child;
101                array_init(&child);
102                ZVAL_LONG(&child, 1)
103                zend_hash_str_update(Z_ARRVAL_P(&SKYWALKING_G(context)), "isChild", sizeof("isChild") - 1, &child);
104
105                zval sw6_1decode;
106                zval sw6_2decode;
107                zval sw6_6decode;
108                zval_b64_decode(&sw6_1decode, Z_STRVAL_P(sw6_1));
109                zval_b64_decode(&sw6_2decode, Z_STRVAL_P(sw6_2));
110                zval_b64_decode(&sw6_6decode, Z_STRVAL_P(sw6_6));
111
112                add_assoc_string(&SKYWALKING_G(context), "parentTraceSegmentId", Z_STRVAL(sw6_2decode));
113                add_assoc_long(&SKYWALKING_G(context), "parentSpanId", zend_atol(Z_STRVAL_P(sw6_3), sizeof(Z_STRVAL_P(sw6_3)) - 1));
114                add_assoc_long(&SKYWALKING_G(context), "parentApplicationInstance", zend_atol(Z_STRVAL_P(sw6_4), sizeof(Z_STRVAL_P(sw6_4)) - 1));
115                add_assoc_long(&SKYWALKING_G(context), "entryApplicationInstance", zend_atol(Z_STRVAL_P(sw6_5), sizeof(Z_STRVAL_P(sw6_5)) - 1));
116                add_assoc_string(&SKYWALKING_G(context), "networkAddress", Z_STRVAL(sw6_6decode));
117                if (sw6_7 != NULL && sw6_8 != NULL) {
118
119                    zval sw6_7decode;
120                    zval sw6_8decode;
121                    zval_b64_decode(&sw6_7decode, Z_STRVAL_P(sw6_7));
122                    zval_b64_decode(&sw6_8decode, Z_STRVAL_P(sw6_8));
123
124                    add_assoc_string(&SKYWALKING_G(context), "entryOperationName", Z_STRVAL(sw6_7decode));
125                    add_assoc_string(&SKYWALKING_G(context), "parentOperationName", Z_STRVAL(sw6_8decode));
126
127                    zval_dtor(&sw6_7decode);
128                    zval_dtor(&sw6_8decode);
129                }
130                add_assoc_string(&SKYWALKING_G(context), "distributedTraceId", Z_STRVAL(sw6_1decode));
131
132                zval_dtor(&sw6_1decode);
133                zval_dtor(&sw6_2decode);
134                zval_dtor(&sw6_6decode);
135            }
136        } else {
137            add_assoc_long(&SKYWALKING_G(context), "parentApplicationInstance", application_instance);
138            add_assoc_long(&SKYWALKING_G(context), "entryApplicationInstance", application_instance);
139            char *uri = get_page_request_uri();
140            char *path = NULL;
141            if (uri != NULL) {
142                path = (char *)emalloc(strlen(uri) + 5);
143                bzero(path, strlen(uri) + 5);
144
145                int i;
146                for(i = 0; i < strlen(uri); i++) {
147                    if (uri[i] == '?') {
148                        break;
149                    }
150                    path[i] = uri[i];
151                }
152                path[i] = '\0';
153            }
154
155            add_assoc_string(&SKYWALKING_G(context), "entryOperationName", (path == NULL) ? "" : path);
156            if (path != NULL) {
157                efree(path);
158            }
159
160            add_assoc_string(&SKYWALKING_G(context), "distributedTraceId", makeTraceId);
161            if(uri != NULL) {
162                efree(uri);
163            }
164        }
165    } else if (SKYWALKING_G(version) == 8) {
166        sw = zend_hash_str_find(Z_ARRVAL_P(carrier), "HTTP_SW8", sizeof("HTTP_SW8") - 1); //$SERVER['HTTP_SW8'];
167        if (sw != NULL && Z_TYPE_P(sw) == IS_STRING && Z_STRLEN_P(sw) > 10) { //从header头部拿到了trace信息
168            add_assoc_string(&SKYWALKING_G(context), "sw8", Z_STRVAL_P(sw));
169
170            zval temp;
171            array_init(&temp); //初始化数组元素
172
173            php_explode(zend_string_init(ZEND_STRL("-"), 0), Z_STR_P(sw), &temp, 10);
174
175            if(zend_array_count(Z_ARRVAL_P(&temp)) >= 7) {
176                zval *sw8_0 = zend_hash_index_find(Z_ARRVAL(temp), 0);
177                zval *sw8_1 = zend_hash_index_find(Z_ARRVAL(temp), 1); // Trace Id base64
178                zval *sw8_2 = zend_hash_index_find(Z_ARRVAL(temp), 2); // Parent trace segment Id
179                zval *sw8_3 = zend_hash_index_find(Z_ARRVAL(temp), 3); // Parent span Id
180                zval *sw8_4 = zend_hash_index_find(Z_ARRVAL(temp), 4); // Parent service
181                zval *sw8_5 = zend_hash_index_find(Z_ARRVAL(temp), 5); // Parent service instance
182                zval *sw8_6 = zend_hash_index_find(Z_ARRVAL(temp), 6); // Parent endpoint
183                zval *sw8_7 = zend_hash_index_find(Z_ARRVAL(temp), 7); // Target address used at client side of this request
184
185                zval child;
186                array_init(&child);
187                ZVAL_LONG(&child, 1)
188                zend_hash_str_update(Z_ARRVAL_P(&SKYWALKING_G(context)), "isChild", sizeof("isChild") - 1, &child); //因为当前HTTP SERVER有TraceId,所以链路存在父对象、因此切换当前上下文状态为child状态。
189
190                zval sw8_1decode;
191                zval sw8_2decode;
192                zval sw8_4decode;
193                zval sw8_5decode;
194                zval sw8_6decode;
195                zval sw8_7decode;
196                zval_b64_decode(&sw8_1decode, Z_STRVAL_P(sw8_1));
197                zval_b64_decode(&sw8_2decode, Z_STRVAL_P(sw8_2));
198                zval_b64_decode(&sw8_4decode, Z_STRVAL_P(sw8_4));
199                zval_b64_decode(&sw8_5decode, Z_STRVAL_P(sw8_5));
200                zval_b64_decode(&sw8_6decode, Z_STRVAL_P(sw8_6));
201                zval_b64_decode(&sw8_7decode, Z_STRVAL_P(sw8_7));
202
203                //针对当前请求的上下文丰富相关链路数据。
204                add_assoc_string(&SKYWALKING_G(context), "traceId", Z_STRVAL(sw8_1decode));
205                add_assoc_string(&SKYWALKING_G(context), "parentTraceSegmentId", Z_STRVAL(sw8_2decode));
206                add_assoc_long(&SKYWALKING_G(context), "parentSpanId", zend_atol(Z_STRVAL_P(sw8_3), sizeof(Z_STRVAL_P(sw8_3)) - 1));
207                add_assoc_string(&SKYWALKING_G(context), "parentService", Z_STRVAL(sw8_4decode));
208                add_assoc_string(&SKYWALKING_G(context), "parentServiceInstance", Z_STRVAL(sw8_5decode));
209                add_assoc_string(&SKYWALKING_G(context), "parentEndpoint", Z_STRVAL(sw8_6decode));
210                add_assoc_string(&SKYWALKING_G(context), "targetAddress", Z_STRVAL(sw8_7decode));
211
212                //释放转码相关内存
213                zval_dtor(&sw8_1decode);
214                zval_dtor(&sw8_2decode);
215                zval_dtor(&sw8_4decode);
216                zval_dtor(&sw8_5decode);
217                zval_dtor(&sw8_6decode);
218                zval_dtor(&sw8_7decode);
219            }
220        } else {
221            //$SERVER没有全链路ID,证明当前为ROOT根
222            //此处可以注入采样率
223            add_assoc_string(&SKYWALKING_G(context), "parentService", service);
224            add_assoc_string(&SKYWALKING_G(context), "parentServiceInstance", service_instance);
225            char *uri = get_page_request_uri(); //获取当前请求的URI PATH
226            char *path = NULL;
227            if (uri != NULL) {
228                path = (char *)emalloc(strlen(uri) + 5);
229                bzero(path, strlen(uri) + 5);
230
231                int i;
232                for(i = 0; i < strlen(uri); i++) {
233                    if (uri[i] == '?') {
234                        break;
235                    }
236                    path[i] = uri[i];
237                }
238                path[i] = '\0';
239            }
240
241            add_assoc_string(&SKYWALKING_G(context), "parentEndpoint", (path == NULL) ? "" : path); //把自己当前的PATH当做父级EndPoint
242            if (path != NULL) {
243                efree(path);
244            }
245
246            add_assoc_string(&SKYWALKING_G(context), "traceId", makeTraceId); //丰富traceId信息
247            if(uri != NULL) {
248                efree(uri);
249            }
250        }
251    }
252
253    efree(makeTraceId);
254}

4.5 解析请求URI信息函数

 1//获取当前请求的URI地址
 2static char *get_page_request_uri() {
 3    zval *carrier = NULL;
 4    zval *request_uri;
 5
 6    smart_str uri = {0}; //创建smart_str对象
 7
 8    if (strcasecmp("cli", sapi_module.name) == 0) {
 9        smart_str_appendl(&uri, "cli", strlen("cli"));
10    } else {
11        zend_bool jit_initialization = PG(auto_globals_jit);
12
13        if (jit_initialization) {
14            zend_string *server_str = zend_string_init("_SERVER", sizeof("_SERVER") - 1, 0);
15            zend_is_auto_global(server_str);
16            zend_string_release(server_str);
17        }
18        carrier = zend_hash_str_find(&EG(symbol_table), ZEND_STRL("_SERVER"));
19
20        request_uri = zend_hash_str_find(Z_ARRVAL_P(carrier), "REQUEST_URI", sizeof("REQUEST_URI") - 1);
21        smart_str_appendl(&uri, Z_STRVAL_P(request_uri), strlen(Z_STRVAL_P(request_uri)));
22    }
23
24    smart_str_0(&uri); //smart_str追加\0结尾
25    if (uri.s != NULL) { //转化为char*类型
26        char *uris = emalloc(strlen(ZSTR_VAL(uri.s)) + 1);
27        strcpy(uris, ZSTR_VAL(uri.s));
28        smart_str_free(&uri);
29        return uris;
30    }
31    return NULL;
32}

4.6 初始化请求函数

  1//请求初始化操作:用来丰富trace、spans等信息: 审计:2020.08.27 00:01
  2static void request_init() {
  3
  4    array_init(&SKYWALKING_G(curl_header));
  5    array_init(&SKYWALKING_G(curl_header_send));
  6    array_init(&SKYWALKING_G(context)); //初始化上下文
  7	array_init(&SKYWALKING_G(UpstreamSegment)); //初始化to agent的数据格式
  8
  9    generate_context(); //生成上下文
 10
 11    add_assoc_long(&SKYWALKING_G(UpstreamSegment), "application_instance", application_instance); //from agent
 12    add_assoc_stringl(&SKYWALKING_G(UpstreamSegment), "uuid", application_uuid, strlen(application_uuid)); //from agent
 13    add_assoc_long(&SKYWALKING_G(UpstreamSegment), "pid", getppid());
 14    add_assoc_long(&SKYWALKING_G(UpstreamSegment), "application_id", application_id);
 15    add_assoc_long(&SKYWALKING_G(UpstreamSegment), "version", SKYWALKING_G(version));
 16    SKY_ADD_ASSOC_ZVAL(&SKYWALKING_G(UpstreamSegment), "segment"); //创建子数组对象,用于构建 $UpstreamSegment['segment'] = array();
 17    SKY_ADD_ASSOC_ZVAL(&SKYWALKING_G(UpstreamSegment), "globalTraceIds");
 18
 19    add_assoc_stringl(&SKYWALKING_G(UpstreamSegment), "service", service, strlen(service)); //like app_code: from agent
 20    add_assoc_stringl(&SKYWALKING_G(UpstreamSegment), "serviceInstance", service_instance, strlen(service_instance)); //uuid : from agent
 21	zval *traceId = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "currentTraceId", sizeof("currentTraceId") - 1);
 22
 23	zval traceSegmentObject;
 24	zval spans;
 25	array_init(&spans);
 26	array_init(&traceSegmentObject);
 27	add_assoc_string(&traceSegmentObject, "traceSegmentId", Z_STRVAL_P(traceId));
 28	add_assoc_long(&traceSegmentObject, "isSizeLimited", 0);
 29
 30	zval temp;
 31    char *peer = NULL;
 32    char *uri = get_page_request_uri();
 33    char *path = (char*)emalloc(strlen(uri) + 5);
 34    bzero(path, strlen(uri) + 5);
 35
 36
 37    int i;
 38    for(i = 0; i < strlen(uri); i++) {
 39        if (uri[i] == '?') {
 40            break;
 41        }
 42        path[i] = uri[i];
 43    }
 44    path[i] = '\0';
 45
 46	array_init(&temp);
 47    peer = get_page_request_peer(); // 从PHP $_SERVER变量中读取peer数据
 48    zval tags;
 49    array_init(&tags); //创建tags对象
 50    add_assoc_string(&tags, "url", (uri == NULL) ? "" : uri);
 51    add_assoc_string(&tags, "server_type", "PHP");      //注入PHP Server类型
 52    add_assoc_string(&tags, "php_version",PHP_VERSION); //注入PHP版本号
 53
 54    add_assoc_zval(&temp, "tags", &tags);
 55
 56    add_assoc_long(&temp, "spanId", 0);
 57    add_assoc_long(&temp, "parentSpanId", -1);
 58    char *l_millisecond = get_millisecond();
 59    long millisecond = zend_atol(l_millisecond, strlen(l_millisecond));
 60    efree(l_millisecond);
 61    add_assoc_long(&temp, "startTime", millisecond);
 62    add_assoc_string(&temp, "operationName", path);
 63    add_assoc_string(&temp, "peer", (peer == NULL) ? "" : peer);
 64    add_assoc_long(&temp, "spanType", 0);
 65    add_assoc_long(&temp, "spanLayer", 3);
 66    if (SKYWALKING_G(version) == 8) {
 67        add_assoc_long(&temp, "componentId", 8001);
 68    } else {
 69        add_assoc_long(&temp, "componentId", COMPONENT_UNDERTOW);
 70    }
 71
 72
 73    // sw8 or sw6 for parent endpoint name
 74    add_assoc_string(&SKYWALKING_G(context), "currentEndpoint", path);
 75
 76    efree(path);
 77    if (peer != NULL) {
 78        efree(peer);
 79    }
 80    if (uri != NULL) {
 81        efree(uri);
 82    }
 83
 84    zval *isChild = zend_hash_str_find(Z_ARRVAL_P(&SKYWALKING_G(context)), "isChild", sizeof("isChild") - 1);
 85    // refs
 86    zval refs;
 87    array_init(&refs);
 88
 89    zval globalTraceIds;
 90    array_init(&globalTraceIds);
 91    zval tmpGlobalTraceIds;
 92
 93    add_assoc_string(&SKYWALKING_G(UpstreamSegment), "traceId", Z_STRVAL_P(traceId));
 94
 95    if(Z_LVAL_P(isChild) == 1) {
 96        zval ref;
 97        array_init(&ref);
 98        zval *parentTraceSegmentId = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "parentTraceSegmentId", sizeof("parentTraceSegmentId") - 1);
 99        zval *parentSpanId = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "parentSpanId", sizeof("parentSpanId") - 1);
100        zval *parentApplicationInstance = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "parentApplicationInstance", sizeof("parentApplicationInstance") - 1);
101        zval *entryApplicationInstance = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "entryApplicationInstance", sizeof("entryApplicationInstance") - 1);
102        zval *entryOperationName = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "entryOperationName", sizeof("entryOperationName") - 1);
103        zval *networkAddress = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "networkAddress", sizeof("networkAddress") - 1);
104        zval *parentOperationName = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "parentOperationName", sizeof("parentOperationName") - 1);
105        zval *distributedTraceId = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "distributedTraceId", sizeof("distributedTraceId") - 1);
106        add_assoc_long(&ref, "type", 0);
107        add_assoc_string(&ref, "parentTraceSegmentId", Z_STRVAL_P(parentTraceSegmentId));
108        add_assoc_long(&ref, "parentSpanId", Z_LVAL_P(parentSpanId));
109
110        if (SKYWALKING_G(version) == 8) {
111            zval *traceId = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "traceId", sizeof("traceId") - 1);
112            zval *parentService = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "parentService", sizeof("parentService") - 1);
113            zval *parentServiceInstance = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "parentServiceInstance", sizeof("parentServiceInstance") - 1);
114            zval *parentEndpoint = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "parentEndpoint", sizeof("parentEndpoint") - 1);
115            zval *targetAddress = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "targetAddress", sizeof("targetAddress") - 1);
116
117            add_assoc_string(&ref, "traceId", Z_STRVAL_P(traceId));
118            add_assoc_string(&ref, "parentService", Z_STRVAL_P(parentService));
119            add_assoc_string(&ref, "parentServiceInstance", Z_STRVAL_P(parentServiceInstance));
120            add_assoc_string(&ref, "parentEndpoint", Z_STRVAL_P(parentEndpoint));
121            add_assoc_string(&ref, "targetAddress", Z_STRVAL_P(targetAddress));
122            zend_hash_str_update(Z_ARRVAL(SKYWALKING_G(UpstreamSegment)), "traceId", sizeof("traceId") - 1, traceId);
123        } else {
124            add_assoc_long(&ref, "parentApplicationInstanceId", Z_LVAL_P(parentApplicationInstance));
125            add_assoc_long(&ref, "entryApplicationInstanceId", Z_LVAL_P(entryApplicationInstance));
126            add_assoc_string(&ref, "networkAddress", Z_STRVAL_P(networkAddress));
127            add_assoc_string(&ref, "entryServiceName", Z_STRVAL_P(entryOperationName));
128            add_assoc_string(&ref, "parentServiceName", Z_STRVAL_P(parentOperationName));
129            ZVAL_STRING(&tmpGlobalTraceIds, Z_STRVAL_P(distributedTraceId));
130        }
131
132        zend_hash_next_index_insert(Z_ARRVAL(refs), &ref);
133    } else {
134        ZVAL_STRING(&tmpGlobalTraceIds, Z_STRVAL_P(traceId));
135    }
136
137    zend_hash_str_add(Z_ARRVAL(temp), "refs", sizeof("refs") - 1, &refs); 
138    zend_hash_next_index_insert(Z_ARRVAL(spans), &temp);
139
140    add_assoc_zval(&traceSegmentObject, "spans", &spans);
141
142    if (SKYWALKING_G(version) != 8)
143    {                                                                              //谨慎调整
144        zend_hash_next_index_insert(Z_ARRVAL(globalTraceIds), &tmpGlobalTraceIds); //问题症结 :skywalking 8.0版本之后移除这个参数支持
145    }
146    zend_hash_str_update(Z_ARRVAL(SKYWALKING_G(UpstreamSegment)), "segment", sizeof("segment") - 1, &traceSegmentObject);
147    zend_hash_str_update(Z_ARRVAL(SKYWALKING_G(UpstreamSegment)), "globalTraceIds", sizeof("globalTraceIds") - 1, &globalTraceIds);
148}

4.7 APM数据flush函数

 1static void sky_flush_all() {
 2	char *l_millisecond = get_millisecond();
 3	long millisecond = zend_atol(l_millisecond, strlen(l_millisecond));
 4	efree(l_millisecond);
 5
 6	zval *span = get_first_span();
 7
 8	add_assoc_long(span, "endTime", millisecond); //标志请求结束时间
 9	if ((SG(sapi_headers).http_response_code >= 500)) { //标志是否出错
10		add_assoc_long(span, "isError", 1);
11	} else {
12		add_assoc_long(span, "isError", 0);
13	}
14	write_log(sky_json_encode(&SKYWALKING_G(UpstreamSegment))); //发送APM数据 - UNIX domain socket途径
15}

4.8 APM注册函数

 1//sky-agent注册 :2020.08.26 22:34审计结束
 2static int sky_register() {
 3    if (application_instance == 0) {
 4        struct sockaddr_un un;
 5        un.sun_family = AF_UNIX; //UNIX通信类型
 6        strcpy(un.sun_path, SKYWALKING_G(sock_path)); //UNIX通信地址
 7        int fd;
 8        char message[1024]; //内存空间优化
 9        char return_message[1024]; //内存空间优化
10
11        fd = socket(AF_UNIX, SOCK_STREAM, 0); //创建client fd
12        if (fd >= 0) { 
13            struct timeval tv;
14            tv.tv_sec = 0;
15            tv.tv_usec = 50000; //单位微秒:50ms
16            setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char *) &tv, sizeof tv);
17            int conn = connect(fd, (struct sockaddr *) &un, sizeof(un));
18
19            if (conn >= 0) { 
20                //进行ServiceName构建:如果环境变量不存在KEY对应的值,则进行环境变量获取
21                char *env_app_code = getenv(SKYWALKING_G(app_code_env_key));
22                if (env_app_code != NULL)
23                {
24                    SKYWALKING_G(app_code) = env_app_code;
25                }
26
27                bzero(message, sizeof(message));
28
29                sprintf(message, "0{\"app_code\":\"%s\",\"pid\":%d,\"version\":%d}\n", SKYWALKING_G(app_code),
30                        getppid(), SKYWALKING_G(version));
31                write(fd, message, strlen(message));
32
33                bzero(return_message, sizeof(return_message));
34                read(fd, return_message, sizeof(return_message));
35
36                char *ids[10] = {0};
37                int i = 0;
38                char *p = strtok(return_message, ","); //分割握手协议
39                while (p != NULL) {
40                    ids[i++] = p;
41                    p = strtok(NULL, ",");
42                }
43
44                if (ids[0] != NULL && ids[1] != NULL && ids[2] != NULL) {
45                    if (SKYWALKING_G(version) == 6 || SKYWALKING_G(version) == 7) {
46                        application_id = atoi(ids[0]);
47                        application_instance = atoi(ids[1]);
48                        strncpy(application_uuid, ids[2], sizeof application_uuid - 1);
49                    } else if (SKYWALKING_G(version) == 8) {
50                        application_id = 1;
51                        application_instance = 1;
52                        strncpy(service, ids[0], sizeof service - 1); //拷贝service app_code ,此处和sidecar进行交互,可以定制化 : app_code
53                        strncpy(service_instance, ids[1], sizeof service_instance - 1);
54                        strncpy(application_uuid, ids[2], sizeof application_uuid - 1);
55                    }
56                }
57
58            } else {
59                php_error_docref(NULL, E_WARNING, "[skywalking] failed to connect the socket.");
60            }
61
62            close(fd);
63        } else {
64            php_error_docref(NULL, E_WARNING, "[skywalking] failed to open the socket.");
65        }
66    }
67    return 0;
68}

五.附录

5.1 附录一 : SkyWalking-PHP内核组件的Component列表

 1#ifndef SKYWALKING_COMPONENTS_H
 2#define SKYWALKING_COMPONENTS_H
 3
 4#define COMPONENT_TOMCAT 1
 5#define COMPONENT_HTTPCLIENT 2
 6#define COMPONENT_DUBBO 3
 7#define COMPONENT_MOTAN 8
 8#define COMPONENT_RESIN 10
 9#define COMPONENT_FEIGN 11
10#define COMPONENT_OKHTTP 12
11#define COMPONENT_SPRING_REST_TEMPLATE 13
12#define COMPONENT_SPRING_MVC_ANNOTATION 14
13#define COMPONENT_STRUTS2 15
14#define COMPONENT_NUTZ_MVC_ANNOTATION 16
15#define COMPONENT_NUTZ_HTTP 17
16#define COMPONENT_JETTY_CLIENT 18
17#define COMPONENT_JETTY_SERVER 19
18#define COMPONENT_SHARDING_JDBC 21
19#define COMPONENT_GRPC 23
20#define COMPONENT_ELASTIC_JOB 24
21#define COMPONENT_HTTP_ASYNC_CLIENT 26
22#define COMPONENT_SERVICECOMB 28
23#define COMPONENT_HYSTRIX 29
24#define COMPONENT_JEDIS 30
25#define COMPONENT_H2_JDBC_DRIVER 32
26#define COMPONENT_MYSQL_JDBC_DRIVER 33
27#define COMPONENT_OJDBC 34
28#define COMPONENT_SPYMEMCACHED 35
29#define COMPONENT_XMEMCACHED 36
30#define COMPONENT_POSTGRESQL_DRIVER 37
31#define COMPONENT_ROCKET_MQ_PRODUCER 38
32#define COMPONENT_ROCKET_MQ_CONSUMER 39
33#define COMPONENT_KAFKA_PRODUCER 40
34#define COMPONENT_KAFKA_CONSUMER 41
35#define COMPONENT_MONGO_DRIVER 42
36#define COMPONENT_SOFARPC 43
37#define COMPONENT_ACTIVEMQ_PRODUCER 45
38#define COMPONENT_ACTIVEMQ_CONSUMER 46
39#define COMPONENT_TRANSPORT_CLIENT 48
40#define COMPONENT_UNDERTOW 49
41#define COMPONENT_RPC 50
42
43#endif //SKYWALKING_COMPONENTS_H

5.2 附录二:APM拦截所需的关键函数定义

 1static char *sky_json_encode(zval *parameter); //json序列化
 2static long get_second(); //获取秒级时间戳
 3static char *get_millisecond(); //获取毫秒级时间戳
 4static char *generate_sw3(zend_long span_id, char *peer_host, char *operation_name); //生成sw3协议数据
 5static char *generate_sw6(zend_long span_id, char *peer_host); //生成sw6协议数据
 6static char *generate_sw8(zend_long span_id, char *peer_host); //生成sw8协议数据
 7static void generate_context(); //生成上下文
 8static char *get_page_request_uri(); //获取request的uri
 9static char *get_page_request_peer(); //获取request的主机信息
10static void write_log( char *text); //发送APM日志
11static void request_init();//初始化一次请求
12static void zval_b64_encode(zval *out, char *in); //base64序列化
13static void zval_b64_decode(zval *out, char *in); //base64反序列化
14static char *sky_get_class_name(zval *obj); //获取zval数据结构的class名称
15static zval *sky_read_property(zval *obj, const char *property, int parent); //读取apm监控数据的某个属性
16static char *sky_redis_fnamewall(const char *function_name); //针对redis操作增加函数边界符号 |%s|
17static int sky_redis_opt_for_string_key(char *fnamewall); //查询redis指令是否在APM监控范围内
18
19static void sky_flush_all(); //发送skywalking APM数据包
20static zval *get_first_span(); //获取收个全链路span数据
21static zval *get_spans(); //获取全部的spans数据
22static char* _get_current_machine_ip(); //获取当前机器的IP
23
24static char *sky_memcached_fnamewall(const char *function_name); //针对memcache操作增加函数边界服务 |%s|
25static int sky_memcached_opt_for_string_key(char *fnamewall); //查询memcache指令是否在APM监控范围内

5.3 附录三:APM关键函数hook定义

 1//curl函数Hook函数
 2void sky_curl_exec_handler(INTERNAL_FUNCTION_PARAMETERS); 
 3void sky_curl_setopt_handler(INTERNAL_FUNCTION_PARAMETERS);
 4void sky_curl_setopt_array_handler(INTERNAL_FUNCTION_PARAMETERS);
 5void sky_curl_close_handler(INTERNAL_FUNCTION_PARAMETERS);
 6
 7static void (*orig_curl_exec)(INTERNAL_FUNCTION_PARAMETERS) = NULL;
 8static void (*orig_curl_setopt)(INTERNAL_FUNCTION_PARAMETERS) = NULL;
 9static void (*orig_curl_setopt_array)(INTERNAL_FUNCTION_PARAMETERS) = NULL;
10static void (*orig_curl_close)(INTERNAL_FUNCTION_PARAMETERS) = NULL;

5.4 附录三:全局变量定义

 1/*
 2  	Declare any global variables you may need between the BEGIN
 3	and END macros here:
 4*/
 5ZEND_BEGIN_MODULE_GLOBALS(skywalking)
 6    char *sock_path; //和sidecar通信的unix地址
 7    char *app_code; //服务名称,app_name eg:skywalking.app_code = MyProjectName
 8    char *app_code_env_key; //app_name 环境变量地址:环境变量->默认KEY:APM_APP_CODE
 9    zend_bool enable; //是否开启内核扩展
10    zval UpstreamSegment; //全局上报数据段
11    zval context; //APM上下文
12    zval curl_header; //curl header数据
13    zval curl_header_send; //记录当前R周期 是否已经send过curl_header
14    int  version; //APM 版本号
15ZEND_END_MODULE_GLOBALS(skywalking)
16
17extern ZEND_DECLARE_MODULE_GLOBALS(skywalking);