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

@李彪  September 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定义

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

2.2.2 php.ini配置解析周期

PHP_INI_BEGIN()
#if SKY_DEBUG
    STD_PHP_INI_BOOLEAN("skywalking.enable",       "1", PHP_INI_ALL, OnUpdateBool, enable, zend_skywalking_globals, skywalking_globals)  //读取skywalking.enable配置项
#else
    STD_PHP_INI_BOOLEAN("skywalking.enable",       "0", PHP_INI_ALL, OnUpdateBool, enable, zend_skywalking_globals, skywalking_globals) //读取skywalking.enable配置项
#endif
    STD_PHP_INI_ENTRY("skywalking.version",       "8", PHP_INI_ALL, OnUpdateLong, version, zend_skywalking_globals, skywalking_globals) //读取skywalking 版本配置项
    STD_PHP_INI_ENTRY("skywalking.app_code", UNKNOW_SERVICE_NAME, PHP_INI_ALL, OnUpdateString, app_code, zend_skywalking_globals, skywalking_globals) //读取微服务-服务名配置项
    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) //读取微服务-服务名环境变量 配置项
    STD_PHP_INI_ENTRY("skywalking.sock_path", "/tmp/sky-agent.sock", PHP_INI_ALL, OnUpdateString, sock_path, zend_skywalking_globals, skywalking_globals) //读取微服务-agent通信unix路径配置项
PHP_INI_END()

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

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

/* {{{ PHP_MINIT_FUNCTION
 */
PHP_MINIT_FUNCTION (skywalking) {
    ZEND_INIT_MODULE_GLOBALS(skywalking, php_skywalking_init_globals, NULL); //初始化模块变量
    //data_register_hashtable();
    REGISTER_INI_ENTRIES();
    /* If you have INI entries, uncomment these lines
    */
    if (SKYWALKING_G(enable)) { //如果内核扩展功能开启了,则进行关键内核函数指针替换。
        //屏蔽php的cli运行模式的APM监控
        if (strcasecmp("cli", sapi_module.name) == 0 && cli_debug == 0) { 
            return SUCCESS;
        }

        // 用户自定义函数执行器(php脚本定义的类、函数)
        ori_execute_ex = zend_execute_ex; 
        zend_execute_ex = sky_execute_ex;

        // 内部函数执行器(c语言定义的类、函数)
        ori_execute_internal = zend_execute_internal;
        zend_execute_internal = sky_execute_internal;

        // 托管curl内核函数:从zend函数表寻找内核函数、并使用内部的函数钩子进行拦截:自定函数钩子->exec、setopt、setopt_array、close
        zend_function *old_function;
        if ((old_function = zend_hash_str_find_ptr(CG(function_table), "curl_exec", sizeof("curl_exec") - 1)) != NULL) { //替换curl_exec函数
            orig_curl_exec = old_function->internal_function.handler;
            old_function->internal_function.handler = sky_curl_exec_handler;
        }
        if ((old_function = zend_hash_str_find_ptr(CG(function_table), "curl_setopt", sizeof("curl_setopt")-1)) != NULL) { //替换curl_setopt函数
            orig_curl_setopt = old_function->internal_function.handler;
            old_function->internal_function.handler = sky_curl_setopt_handler;
        }
        if ((old_function = zend_hash_str_find_ptr(CG(function_table), "curl_setopt_array", sizeof("curl_setopt_array")-1)) != NULL) { //替换curl_setopt_array函数
            orig_curl_setopt_array = old_function->internal_function.handler;
            old_function->internal_function.handler = sky_curl_setopt_array_handler;
        }
        if ((old_function = zend_hash_str_find_ptr(CG(function_table), "curl_close", sizeof("curl_close")-1)) != NULL) { //替换curl_close函数
            orig_curl_close = old_function->internal_function.handler;
            old_function->internal_function.handler = sky_curl_close_handler;
        }
    }

    return SUCCESS;
}
/* }}} */

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

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

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

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

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

/* Remove if there's nothing to do at request end */
/* {{{ PHP_RSHUTDOWN_FUNCTION
 */
PHP_RSHUTDOWN_FUNCTION(skywalking)
{

    if(SKYWALKING_G(enable)){
        if (strcasecmp("cli", sapi_module.name) == 0 && cli_debug == 0) {
            return SUCCESS;
        }
        if (application_instance == 0) {
            return SUCCESS;
        }
            sky_flush_all();//发送APM数据给Agent程序
        zval_dtor(&SKYWALKING_G(context)); //析构context hashtable、释放内存
        zval_dtor(&SKYWALKING_G(curl_header));//析构curl_header hashtable、释放内存
        zval_dtor(&SKYWALKING_G(curl_header_send));//析构curl_header_send hashtable、释放内存
        zval_dtor(&SKYWALKING_G(UpstreamSegment));//析构UpstreamSegment hashtable、释放内存
    }
    return SUCCESS;
}
/* }}} */

三. 关键运行函数Hook分析

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

3.1 PHP用户态函数拦截

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

/* PHP 内核函数执行相关的核心内核数据结构
    struct _zend_execute_data {
    const zend_op       *opline;           executed opline                
    zend_execute_data *call;  current call                   
    zval *return_value;
    zend_function *func;  executed function             
    zval This;           this + call_info + num_args    
    zend_execute_data *prev_execute_data;
    zend_array *symbol_table;
    #if ZEND_EX_USE_RUN_TIME_CACHE
    void **run_time_cache;  cache op_array->run_time_cache 
    #endif
    #if ZEND_EX_USE_LITERALS
    zval *literals;  cache op_array->literals       
    #endif
    }
*/
// sky-apm 内核扩展注入的函数执行钩子 : 用户态函数拦截
ZEND_API void sky_execute_ex(zend_execute_data *execute_data) {
    if (application_instance == 0) { //如果当前请求周期不需要APM采集,则直接走原内核函数钩子
        ori_execute_ex(execute_data);
        return;
    }

    zend_function *zf = execute_data->func; //拦截执行的函数
    const char *class_name = (zf->common.scope != NULL && zf->common.scope->name != NULL) ? ZSTR_VAL(
            zf->common.scope->name) : NULL; //获取class名称
    const char *function_name = zf->common.function_name == NULL ? NULL : ZSTR_VAL(zf->common.function_name); //获取function name

    char *operationName = NULL; //定义操作名称
    char *peer = NULL;
    int componentId = 0; //组件Id
    if (class_name != NULL) {
        if (strcmp(class_name, "Predis\\Client") == 0 && strcmp(function_name, "executeCommand") == 0) {
            // 检测predis组件的executeCommand函数
            uint32_t arg_count = ZEND_CALL_NUM_ARGS(execute_data); 
            if (arg_count) {
                zval *p = ZEND_CALL_ARG(execute_data, 1);

                zval *id = (zval *) emalloc(sizeof(zval));
                zend_call_method(p, Z_OBJCE_P(p), NULL, ZEND_STRL("getid"), id, 0, NULL, NULL);

                if (Z_TYPE_P(id) == IS_STRING) { //构建predis函数类->函数方法的 操作名。
                    operationName = (char *) emalloc(strlen(class_name) + strlen(Z_STRVAL_P(id)) + 3);
                    componentId = COMPONENT_JEDIS;
                    strcpy(operationName, class_name);
                    strcat(operationName, "->");
                    strcat(operationName, Z_STRVAL_P(id));
                }
                efree(id);
            }
        } else if (strcmp(class_name, "Grpc\\BaseStub") == 0) { //拦截Grpc组件的关键方法。
            if (strcmp(function_name, "_simpleRequest") == 0
                || strcmp(function_name, "_clientStreamRequest") == 0
                || strcmp(function_name, "_serverStreamRequest") == 0
                || strcmp(function_name, "_bidiRequest") == 0
            ) {
                operationName = (char *) emalloc(strlen(class_name) + strlen(function_name) + 3);
                if (SKYWALKING_G(version) == 5) {
                    componentId = COMPONENT_GRPC;
                } else {
                    componentId = COMPONENT_RPC;
                }
                strcpy(operationName, class_name);
                strcat(operationName, "->");
                strcat(operationName, function_name);
            }
        }
    }

    if (operationName != NULL) { //如果操作名称不为空,证明成功拦截到函数
        zval tags;
        array_init(&tags);

          //构建APM的tags属性,增加数据库类型、客户端组件类型
        if (strcmp(class_name, "Predis\\Client") == 0 && strcmp(function_name, "executeCommand") == 0) {
            add_assoc_string(&tags, "db.type", "redis");
            add_assoc_string(&tags, "component", "predis");
            zval *p = ZEND_CALL_ARG(execute_data, 1);
            zval *id = (zval *) emalloc(sizeof(zval));
            zval *arguments = (zval *) emalloc(sizeof(zval));
            zend_call_method(p, Z_OBJCE_P(p), NULL, ZEND_STRL("getid"), id, 0, NULL, NULL);
            zend_call_method(p, Z_OBJCE_P(p), NULL, ZEND_STRL("getarguments"), arguments, 0, NULL, NULL);

            // 丰富APM的predis的连接peer信息
            zval *connection = sky_read_property(&(execute_data->This),"connection", 0);
            if (connection != NULL && Z_TYPE_P(connection) == IS_OBJECT && strcmp(sky_get_class_name(connection), "Predis\\Connection\\StreamConnection") == 0) {
                zval *parameters = sky_read_property(connection, "parameters", 0);
                if (parameters != NULL && Z_TYPE_P(parameters) == IS_OBJECT && strcmp(sky_get_class_name(parameters), "Predis\\Connection\\Parameters") == 0) {
                    zval *parameters_arr = sky_read_property(parameters, "parameters", 0);
                    if (Z_TYPE_P(parameters_arr) == IS_ARRAY) {
                        zval *predis_host = zend_hash_str_find(Z_ARRVAL_P(parameters_arr), "host", sizeof("host") - 1);
                        zval *predis_port = zend_hash_str_find(Z_ARRVAL_P(parameters_arr), "port", sizeof("port") - 1);
                        zval *port;
                        ZVAL_COPY(&port, predis_port);
                        if (Z_TYPE_P(port) != IS_LONG) {
                            convert_to_long(port);
                        }

                        if (Z_TYPE_P(predis_host) == IS_STRING && Z_TYPE_P(port) == IS_LONG) {
                            const char *host = ZSTR_VAL(Z_STR_P(predis_host));
                            peer = (char *) emalloc(strlen(host) + 10);
                            bzero(peer, strlen(host) + 10);
                            sprintf(peer, "%s:%" PRId3264, host, Z_LVAL_P(port));
                        }
                    }
                }
            }
            // peer end

            if (Z_TYPE_P(arguments) == IS_ARRAY) {
                zend_ulong num_key;
                zval *entry, str_entry;
                smart_str command = {0};
                smart_str_appends(&command, Z_STRVAL_P(id));
                smart_str_appends(&command, " ");
                ZEND_HASH_FOREACH_NUM_KEY_VAL(Z_ARRVAL_P(arguments), num_key, entry)
                        {
                            switch (Z_TYPE_P(entry)) {
                                case IS_STRING:
                                    smart_str_appends(&command, Z_STRVAL_P(entry));
                                    smart_str_appends(&command, " ");
                                    break;
                                case IS_ARRAY:
                                    break;
                                default:
                                    ZVAL_COPY(&str_entry, entry);
                                    convert_to_string(&str_entry);
                                    smart_str_appends(&command, Z_STRVAL_P(&str_entry));
                                    smart_str_appends(&command, " ");
                                    break;
                            }
                        }
                ZEND_HASH_FOREACH_END();

                // store command to tags
                if (command.s) {
                    smart_str_0(&command);
                    add_assoc_string(&tags, "redis.command", ZSTR_VAL(command.s));
                    smart_str_free(&command);
                }
            }
            zval_ptr_dtor(id);
            zval_ptr_dtor(arguments);
            efree(id);
            efree(arguments);
        } else if (strcmp(class_name, "Grpc\\BaseStub") == 0) {
            add_assoc_string(&tags, "rpc.type", "grpc");
            add_assoc_string(&tags, "component", "php-grpc");
            zval *p = ZEND_CALL_ARG(execute_data, 1);
            if (Z_TYPE_P(p) == IS_STRING) {
                add_assoc_string(&tags, "rpc.method", Z_STRVAL_P(p));
            }

            zval *hostname = sky_read_property(&(execute_data->This), "hostname", 1);
            zval *hostname_override = sky_read_property(&(execute_data->This), "hostname_override", 1);

            const char *host = NULL;
            if (hostname_override != NULL && Z_TYPE_P(hostname_override) == IS_STRING) {
                host = ZSTR_VAL(Z_STR_P(hostname_override));
            } else if (hostname != NULL && Z_TYPE_P(hostname) == IS_STRING) {
                host = ZSTR_VAL(Z_STR_P(hostname));
            }

            if (host != NULL) {
                peer = (char *) emalloc(strlen(host) + 10);
                bzero(peer, strlen(host) + 10);
                sprintf(peer, "%s", host);
            }
        }

        zval temp;
        zval *spans = NULL;
        zval *span_id = NULL;
        zval *last_span = NULL;
        char *l_millisecond;
        long millisecond;
        array_init(&temp);
        spans = get_spans();
        last_span = zend_hash_index_find(Z_ARRVAL_P(spans), zend_hash_num_elements(Z_ARRVAL_P(spans)) - 1);
        span_id = zend_hash_str_find(Z_ARRVAL_P(last_span), "spanId", sizeof("spanId") - 1);

        add_assoc_long(&temp, "spanId", Z_LVAL_P(span_id) + 1);
        add_assoc_long(&temp, "parentSpanId", 0);
        l_millisecond = get_millisecond();
        millisecond = zend_atol(l_millisecond, strlen(l_millisecond));
        efree(l_millisecond);
        add_assoc_long(&temp, "startTime", millisecond);
        add_assoc_long(&temp, "spanType", 1);
        add_assoc_long(&temp, "spanLayer", 1);
        add_assoc_long(&temp, "componentId", componentId);
        add_assoc_string(&temp, "operationName", operationName);
        add_assoc_string(&temp, "peer", peer == NULL ? "" : peer);
        efree(operationName);
        if (peer != NULL) {
            efree(peer);
        }

        ori_execute_ex(execute_data); //前面已经注入了APM原始数据、正式执行原函数钩子

        l_millisecond = get_millisecond();
        millisecond = zend_atol(l_millisecond, strlen(l_millisecond));
        efree(l_millisecond);

        add_assoc_zval(&temp, "tags", &tags); //执行完后,注入请求监控的APM-tags
        add_assoc_long(&temp, "endTime", millisecond); 
        add_assoc_long(&temp, "isError", 0);

        zend_hash_next_index_insert(Z_ARRVAL_P(spans), &temp);
    } else {
        ori_execute_ex(execute_data);
    }
}

3.2 PHP内核态函数拦截

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

//PHP非用户态函数拦截:例如PHP内核、PHP其他内核扩展
ZEND_API void sky_execute_internal(zend_execute_data *execute_data, zval *return_value) {

    if (application_instance == 0) {
        if (ori_execute_internal) {
            ori_execute_internal(execute_data, return_value);
        } else {
            execute_internal(execute_data, return_value);
        }
        return;
    }

    zend_function *zf = execute_data->func;
    const char *class_name = (zf->common.scope != NULL && zf->common.scope->name != NULL) ? ZSTR_VAL(
            zf->common.scope->name) : NULL; //获取函数所属的class名称
    const char *function_name = zf->common.function_name == NULL ? NULL : ZSTR_VAL(zf->common.function_name);//获取函数名称
    int is_procedural_mysqli = 0; // "Procedural style" or "Object oriented style" ?
    char *operationName = NULL;
    char *peer = NULL;
    char *component = NULL;
    int componentId = COMPONENT_MYSQL_JDBC_DRIVER;
    if (class_name != NULL) {
        if (strcmp(class_name, "PDO") == 0) { //拦截PDO类
            if (strcmp(function_name, "exec") == 0 
                || strcmp(function_name, "query") == 0
                || strcmp(function_name, "prepare") == 0
                || strcmp(function_name, "commit") == 0) {
                component = (char *) emalloc(strlen("PDO") + 1);
                strcpy(component, "PDO");
                operationName = (char *) emalloc(strlen(class_name) + strlen(function_name) + 3); //构建PDO操作名称
                strcpy(operationName, class_name);
                strcat(operationName, "->");
                strcat(operationName, function_name);
            }
        } else if (strcmp(class_name, "PDOStatement") == 0) { //拦截PDOStatement类操作
            if (strcmp(function_name, "execute") == 0) {
                component = (char *) emalloc(strlen("PDOStatement") + 1);
                strcpy(component, "PDOStatement");
                operationName = (char *) emalloc(strlen(class_name) + strlen(function_name) + 3); //构建PDOStatement操作名称
                strcpy(operationName, class_name);
                strcat(operationName, "->");
                strcat(operationName, function_name);
            }
        } else if (strcmp(class_name, "mysqli") == 0) {//拦截mysqli类操作
            if (strcmp(function_name, "query") == 0) {
                component = (char *) emalloc(strlen("mysqli") + 1);
                strcpy(component, "mysqli");
                operationName = (char *) emalloc(strlen(class_name) + strlen(function_name) + 3);
                strcpy(operationName, class_name);
                strcat(operationName, "->");
                strcat(operationName, function_name);
            }
        } else if (strcmp(class_name, "Yar_Client") == 0) { //拦截Yar_Client类操作
            if (strcmp(function_name, "__call") == 0) {
                if (SKYWALKING_G(version) == 5) {
                    componentId = COMPONENT_GRPC;
                } else {
                    componentId = COMPONENT_RPC;
                }

                component = (char *) emalloc(strlen("Yar_Client") + 1);
                strcpy(component, "Yar_Client");
                uint32_t arg_count = ZEND_CALL_NUM_ARGS(execute_data);
                if (arg_count) {
                    zval *p = ZEND_CALL_ARG(execute_data, 1);
                    if (Z_TYPE_P(p) == IS_STRING) {
                        operationName = (char *) emalloc(strlen(class_name) + strlen(Z_STRVAL_P(p)) + 3);
                        strcpy(operationName, class_name);
                        strcat(operationName, "->");
                        strcat(operationName, Z_STRVAL_P(p));
                    }
                }
            }
        } else if (strcmp(class_name, "Redis") == 0 || strcmp(class_name, "RedisCluster") == 0) { //拦截phpredis内核组件
            char *fnamewall = sky_redis_fnamewall(function_name);
            if (sky_redis_opt_for_string_key(fnamewall) == 1) {
                componentId = COMPONENT_JEDIS;
                component = (char *) emalloc(strlen("Redis") + 1);
                strcpy(component, "Redis");
                operationName = (char *) emalloc(strlen(class_name) + strlen(function_name) + 3);
                strcpy(operationName, class_name);
                strcat(operationName, "->");
                strcat(operationName, function_name);
            }
            efree(fnamewall);
        } else if (strcmp(class_name, "Memcached") == 0) {//拦截Memcached内核组件
            char *fnamewall = sky_memcached_fnamewall(function_name);
            if (sky_memcached_opt_for_string_key(fnamewall) == 1) {
                componentId = COMPONENT_XMEMCACHED;
                component = (char *) emalloc(strlen("memcached") + 1);
                strcpy(component, "memcached");
                operationName = (char *) emalloc(strlen(class_name) + strlen(function_name) + 3);
                strcpy(operationName, class_name);
                strcat(operationName, "->");
                strcat(operationName, function_name);
            }
            efree(fnamewall);
        }
    } else if (function_name != NULL) {
        if (strcmp(function_name, "mysqli_query") == 0) {//拦截mysqli_query函数
            class_name = "mysqli";
            function_name = "query";
            component = (char *) emalloc(strlen(class_name) + 1);
            strcpy(component, class_name);
            operationName = (char *) emalloc(strlen(class_name) + strlen(function_name) + 3);
            strcpy(operationName, class_name);
            strcat(operationName, "->");
            strcat(operationName, function_name);

            is_procedural_mysqli = 1;
        }
    }

    //如果在控制范围内的函数行为
    if (operationName != NULL) {

        zval tags;
        array_init(&tags);

        if (strcmp(class_name, "PDO") == 0) {//丰富PDO span tags
            add_assoc_string(&tags, "component", "PHP-PDO");

            // params
            uint32_t arg_count = ZEND_CALL_NUM_ARGS(execute_data);
            if (arg_count) {
                zval *p = ZEND_CALL_ARG(execute_data, 1);
                //db.statement
                switch (Z_TYPE_P(p)) {
                    case IS_STRING:
                        add_assoc_string(&tags, "db.statement", Z_STRVAL_P(p));//丰富PDO 查询语句
                        break;

                }
            }

            char db_type[64] = {0};
            pdo_dbh_t *dbh = Z_PDO_DBH_P(&(execute_data->This));
            if (dbh != NULL) {
                if (dbh->driver != NULL && dbh->driver->driver_name != NULL) {
                    memcpy(db_type, (char *) dbh->driver->driver_name, dbh->driver->driver_name_len);
                    add_assoc_string(&tags, "db.type", db_type);
                }

                if (dbh->data_source != NULL && db_type[0] != '\0') {
                    add_assoc_string(&tags, "db.data_source", (char *) dbh->data_source);
                    char *host = pcre_match("(host=([^;\\s]+))", sizeof("(host=([^;\\s]+))")-1, (char *) dbh->data_source);
                    char *port = pcre_match("(port=([^;\\s]+))", sizeof("(port=([^;\\s]+))")-1, (char *) dbh->data_source);
                    if (host != NULL && port != NULL) {
                        peer = (char *) emalloc(strlen(host) + 10);
                        bzero(peer, strlen(host) + 10);
                        sprintf(peer, "%s:%s", host, port);
                    }
                    if (host != NULL) efree(host);
                    if (port != NULL) efree(port);
                }
            }
        } else if (strcmp(class_name, "PDOStatement") == 0) {
            char db_type[64] = {0};
            pdo_stmt_t *stmt = (pdo_stmt_t *) Z_PDO_STMT_P(&(execute_data->This));
            if (stmt != NULL) {
                add_assoc_string(&tags, "db.statement", stmt->query_string);
                add_assoc_string(&tags, "component", "PHP-PDO");

                if (stmt->dbh != NULL && stmt->dbh->driver->driver_name != NULL) {
                    memcpy(db_type, (char *) stmt->dbh->driver->driver_name, stmt->dbh->driver->driver_name_len);
                    add_assoc_string(&tags, "db.type", db_type);
                }

                if (db_type[0] != '\0' && stmt->dbh != NULL && stmt->dbh->data_source != NULL) {
                    add_assoc_string(&tags, "db.data_source", (char *) stmt->dbh->data_source);
                    char *host = pcre_match("(host=([^;\\s]+))", sizeof("(host=([^;\\s]+))")-1, (char *) stmt->dbh->data_source);
                    char *port = pcre_match("(port=([^;\\s]+))", sizeof("(port=([^;\\s]+))")-1, (char *) stmt->dbh->data_source);
                    if (host != NULL && port != NULL) {
                        peer = (char *) emalloc(strlen(host) + 10);
                        bzero(peer, strlen(host) + 10);
                        sprintf(peer, "%s:%s", host, port);
                    }
                    if (host != NULL) efree(host);
                    if (port != NULL) efree(port);
                }
            }
        } else if (strcmp(class_name, "mysqli") == 0) {
            if (strcmp(function_name, "query") == 0) {
#ifdef MYSQLI_USE_MYSQLND
                mysqli_object *mysqli = NULL;
                if(is_procedural_mysqli) {
                    mysqli = (mysqli_object *) Z_MYSQLI_P(ZEND_CALL_ARG(execute_data, 1));
                } else {
                    mysqli = (mysqli_object *) Z_MYSQLI_P(&(execute_data->This));
                }

                MYSQLI_RESOURCE *my_res = (MYSQLI_RESOURCE *) mysqli->ptr;
                if (my_res && my_res->ptr) {
                    MY_MYSQL *mysql = (MY_MYSQL *) my_res->ptr;
                    if (mysql->mysql) {
#if PHP_VERSION_ID >= 70100
                        char *host = mysql->mysql->data->hostname.s;
#else
                        char *host = mysql->mysql->data->host;
#endif
                        char port[6];
                        char nullHost[10] = "nullhost\0";

                        sprintf(port, "%d", mysql->mysql->data->port);
                        add_assoc_string(&tags, "db.port", port);
                        if(host != NULL){
                            add_assoc_string(&tags, "db.host", host);
                            peer = (char *)emalloc(strlen(host) + 10);
                            bzero(peer, strlen(host) + 10);
                            sprintf(peer, "%s:%d", host, mysql->mysql->data->port);
                        }else{
                            add_assoc_string(&tags, "db.host", nullHost);
                            peer = (char *)emalloc(strlen(nullHost) + 10);
                            bzero(peer, strlen(nullHost) + 10);
                            sprintf(peer, "%s:%d", nullHost, mysql->mysql->data->port);
                        }
                    }
                }
#endif
                add_assoc_string(&tags, "db.type", "mysql");
                add_assoc_string(&tags, "component", "PHP-mysqli");
                // params
                uint32_t arg_count = ZEND_CALL_NUM_ARGS(execute_data);
                if (arg_count) {
                    zval *p = is_procedural_mysqli ? ZEND_CALL_ARG(execute_data, 2) : ZEND_CALL_ARG(execute_data, 1);
                    //db.statement
                    switch (Z_TYPE_P(p)) {
                        case IS_STRING:
                            add_assoc_string(&tags, "db.statement", Z_STRVAL_P(p));
                            break;

                    }
                }
            }
        } else if (strcmp(class_name, "Yar_Client") == 0) {
            if (strcmp(function_name, "__call") == 0) {
                zval rv, _uri;
                ZVAL_STRING(&_uri, "_uri");
                zval *yar_uri = Z_OBJ_HT(EX(This))->read_property(&EX(This), &_uri, BP_VAR_R, 0, &rv);
                add_assoc_string(&tags, "component", "PHP-Yar");
                add_assoc_string(&tags, "yar.uri", Z_STRVAL_P(yar_uri));
                uint32_t arg_count = ZEND_CALL_NUM_ARGS(execute_data);
                if (arg_count) {
                    zval *p = ZEND_CALL_ARG(execute_data, 1);
                    if (Z_TYPE_P(p) == IS_STRING) {
                        add_assoc_string(&tags, "yar.method", Z_STRVAL_P(p));
                    }
                }
            }
        } else if (strcmp(class_name, "Redis") == 0 || strcmp(class_name, "RedisCluster") == 0) { //丰富phpredis 操作span数据
            add_assoc_string(&tags, "db.type", "redis");
            add_assoc_string(&tags, "component", "phpredis");
            uint32_t arg_count = ZEND_CALL_NUM_ARGS(execute_data);

            if (strcmp(class_name, "Redis") == 0) {
                // find peer
                zval *this = &(execute_data->This);
                zval host;
                zval port;
                zend_call_method(this, Z_OBJCE_P(this), NULL, ZEND_STRL("gethost"), &host, 0, NULL, NULL);
                zend_call_method(this, Z_OBJCE_P(this), NULL, ZEND_STRL("getport"), &port, 0, NULL, NULL);


                if (!Z_ISUNDEF(host) && !Z_ISUNDEF(port) && Z_TYPE(host) == IS_STRING && Z_TYPE(port) == IS_LONG) {
                    const char *h = ZSTR_VAL(Z_STR(host));
                    peer = (char *) emalloc(strlen(h) + 10);
                    bzero(peer, strlen(h) + 10);
                    sprintf(peer, "%s:%" PRId3264, h, Z_LVAL(port));
                }

                if (!Z_ISUNDEF(host)) {
                    zval_ptr_dtor(&host);
                }

                if (!Z_ISUNDEF(port)) {
                    zval_ptr_dtor(&port);
                }
            }

            smart_str command = {0};
            char *fname = zend_str_tolower_dup((char *) function_name, strlen((char *) function_name));
            smart_str_appends(&command, fname);
            smart_str_appends(&command, " ");
            efree(fname);

            int is_string_command = 1;
            int i;
            for (i = 1; i < arg_count + 1; ++i) {
                zval str_p;
                zval *p = ZEND_CALL_ARG(execute_data, i);
                if (Z_TYPE_P(p) == IS_ARRAY) {
                    is_string_command = 0;
                    break;
                }

                ZVAL_COPY(&str_p, p);
                if (Z_TYPE_P(&str_p) != IS_STRING) {
                    convert_to_string(&str_p);
                }
                if (i == 1) {
                    add_assoc_string(&tags, "redis.key", Z_STRVAL_P(&str_p));//丰富phpredis key数据
                }
                char *tmp = zend_str_tolower_dup(Z_STRVAL_P(&str_p), Z_STRLEN_P(&str_p));
                smart_str_appends(&command, tmp);
                smart_str_appends(&command, " ");
                efree(tmp);
            }
            // store command to tags
            if (command.s) {
                smart_str_0(&command);
                if (is_string_command) {
                    zend_string *trim_s = php_trim(command.s, NULL, 0, 3);
                    add_assoc_string(&tags, "redis.command", ZSTR_VAL(trim_s));//丰富phpredis 指令数据
                    zend_string_free(trim_s);
                }
                smart_str_free(&command);
            }
        } else if (strcmp(class_name, "Memcached") == 0) {

            add_assoc_string(&tags, "db.type", "memcached");
            add_assoc_string(&tags, "component", "PHP-Memcached");
            uint32_t arg_count = ZEND_CALL_NUM_ARGS(execute_data);

            smart_str command = {0};
            smart_str_appends(&command, zend_str_tolower_dup((char *) function_name, strlen((char *) function_name)));
            smart_str_appends(&command, " ");

            int i;
            for (i = 1; i < arg_count + 1; ++i) {
                char *str = NULL;
                zval str_p;
                zval *p = ZEND_CALL_ARG(execute_data, i);
                if (Z_TYPE_P(p) == IS_ARRAY) {
                    str = sky_json_encode(p);
                }

                ZVAL_COPY(&str_p, p);
                if (Z_TYPE_P(&str_p) != IS_ARRAY && Z_TYPE_P(&str_p) != IS_STRING) {
                    convert_to_string(&str_p);
                }

                if (str == NULL) {
                    str = Z_STRVAL_P(&str_p);
                }

                if (i == 1) {
                    add_assoc_string(&tags, "memcached.key", str);
                }
                smart_str_appends(&command, zend_str_tolower_dup(str, strlen(str)));
                smart_str_appends(&command, " ");
            }
            // store command to tags
            if (command.s) {
                smart_str_0(&command);
                add_assoc_string(&tags, "memcached.command", ZSTR_VAL(php_trim(command.s, NULL, 0, 3)));
                smart_str_free(&command);
            }
        }


        zval temp;
        zval *spans = NULL;
        zval *span_id = NULL;
        zval *last_span = NULL;
        char *l_millisecond;
        long millisecond;
        array_init(&temp);
        spans = get_spans();
        last_span = zend_hash_index_find(Z_ARRVAL_P(spans), zend_hash_num_elements(Z_ARRVAL_P(spans)) - 1);
        span_id = zend_hash_str_find(Z_ARRVAL_P(last_span), "spanId", sizeof("spanId") - 1);

        add_assoc_long(&temp, "spanId", Z_LVAL_P(span_id) + 1);
        add_assoc_long(&temp, "parentSpanId", 0);
        l_millisecond = get_millisecond();
        millisecond = zend_atol(l_millisecond, strlen(l_millisecond));
        efree(l_millisecond);
        add_assoc_long(&temp, "startTime", millisecond);
        add_assoc_long(&temp, "spanType", 1);
        add_assoc_long(&temp, "spanLayer", 1);
//        add_assoc_string(&temp, "component", component);
        add_assoc_long(&temp, "componentId", componentId);
        add_assoc_string(&temp, "operationName", operationName);
        add_assoc_string(&temp, "peer", peer == NULL ? "" : peer);
        efree(component);
        efree(operationName);
        if (peer != NULL) {
            efree(peer);
        }

      //执行PHP内核原函数指针
        if (ori_execute_internal) {
            ori_execute_internal(execute_data, return_value);
        } else {
            execute_internal(execute_data, return_value);
        }

        l_millisecond = get_millisecond();
        millisecond = zend_atol(l_millisecond, strlen(l_millisecond));
        efree(l_millisecond);


        add_assoc_zval(&temp, "tags", &tags);
        add_assoc_long(&temp, "endTime", millisecond);
        add_assoc_long(&temp, "isError", 0); 

        zend_hash_next_index_insert(Z_ARRVAL_P(spans), &temp);
    } else {
        if (ori_execute_internal) {
            ori_execute_internal(execute_data, return_value);
        } else {
            execute_internal(execute_data, return_value);
        }
    }
}

3.3 PHP-CURL 内核Hook拦截

//curl执行拦截
void sky_curl_exec_handler(INTERNAL_FUNCTION_PARAMETERS)
{
    if(application_instance == 0) {
        orig_curl_exec(INTERNAL_FUNCTION_PARAM_PASSTHRU);
        return;
    }

    zval        *zid;
    if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &zid) == FAILURE) {
        return;
    }

    int is_send = 1;

    zval function_name,curlInfo;
    zval params[1];
    ZVAL_COPY(&params[0], zid);

    //执行curl_getinfo函数:
    ZVAL_STRING(&function_name,  "curl_getinfo");
    call_user_function(CG(function_table), NULL, &function_name, &curlInfo, 1, params); //从CG全局函数表、调用curl_getinfo函数
    zval_dtor(&function_name);
    zval_dtor(&params[0]);

    //获取curl请求的url信息
    zval *z_url = zend_hash_str_find(Z_ARRVAL(curlInfo),  ZEND_STRL("url"));
    char *url_str = Z_STRVAL_P(z_url);

    if(strlen(url_str) <= 0) {
        zval_dtor(&curlInfo);
        is_send = 0;
    }

    //构建标准的HTTP URI结构
    php_url *url_info = NULL;
    if(is_send == 1) {
        url_info = php_url_parse(url_str); //解析
        if(url_info->scheme == NULL || url_info->host == NULL) {
            zval_dtor(&curlInfo);
            php_url_free(url_info); //释放内存
            is_send = 0;
        }
    }

    char *sw = NULL;
    zval *spans = NULL;
    zval *last_span = NULL;
    zval *span_id = NULL;
    char *peer = NULL;
    char *operation_name = NULL;
    char *full_url = NULL;


    //确定要发送的请求、当前请求合理,才进行下面的信息丰富。
    if (is_send == 1) { 

// for php7.3.0+
#if PHP_VERSION_ID >= 70300
        char *php_url_scheme = ZSTR_VAL(url_info->scheme);
        char *php_url_host = ZSTR_VAL(url_info->host);
        char *php_url_path = ZSTR_VAL(url_info->path);
        char *php_url_query = ZSTR_VAL(url_info->query);
#else
        char *php_url_scheme = url_info->scheme;
        char *php_url_host = url_info->host;
        char *php_url_path = url_info->path;
        char *php_url_query = url_info->query;
#endif

        //处理url_info内部的被调用方端口值
        int peer_port = 0; 
        if (url_info->port) {
            peer_port = url_info->port;
        } else {
            if (strcasecmp("http", php_url_scheme) == 0) { //默认端口设置
                peer_port = 80;
            } else {
                peer_port = 443;
            }
        }

        //处理url_info内部的query参数
        if (url_info->query){
            if (url_info->path == NULL) {
                spprintf(&operation_name, 0, "%s", "/");
                spprintf(&full_url, 0, "%s?%s", "/", php_url_query);
            } else {
                spprintf(&operation_name, 0, "%s", php_url_path);
                spprintf(&full_url, 0, "%s?%s", php_url_path, php_url_query);
            }
        }
        else
        {
            if (url_info->path == NULL) {
                spprintf(&operation_name, 0, "%s", "/");
                spprintf(&full_url, 0, "%s", "/");
            } else {
                spprintf(&operation_name, 0, "%s", php_url_path);
                spprintf(&full_url, 0, "%s", php_url_path);
            }
        }

        spans = get_spans(); //获取spans数组
        last_span = zend_hash_index_find(Z_ARRVAL_P(spans), zend_hash_num_elements(Z_ARRVAL_P(spans)) - 1); //获取最后一个span
        span_id = zend_hash_str_find(Z_ARRVAL_P(last_span), "spanId", sizeof("spanId") - 1);

        //生成新的Trace-Span
        if (SKYWALKING_G(version) == 5)
        { // skywalking 5.x
            spprintf(&peer, 0, "%s://%s:%d", php_url_scheme, php_url_host, peer_port);
            sw = generate_sw3(Z_LVAL_P(span_id) + 1, peer, operation_name);
        }
        else if (SKYWALKING_G(version) == 6 || SKYWALKING_G(version) == 7)
        { // skywalking 6.x
            spprintf(&peer, 0, "%s:%d", php_url_host, peer_port);
            sw = generate_sw6(Z_LVAL_P(span_id) + 1, peer);
        }
        else if (SKYWALKING_G(version) == 8)
        {
            spprintf(&peer, 0, "%s:%d", php_url_host, peer_port); //
            sw = generate_sw8(Z_LVAL_P(span_id) + 1, peer); //生成version 8的span
        }
    }

    /*
     sw6_l = snprintf(NULL, 0, "sw8: 1-%s-%s-%" PRId3264 "-%s-%s-%s-%s",
            Z_STRVAL(traceIdEncode),
            Z_STRVAL(currentTraceIdEncode),
            span_id,
            Z_STRVAL(serviceEncode),
            Z_STRVAL(serviceInstanceEncode),
            Z_STRVAL(parentEndpointEncode),
            Z_STRVAL(targetAddressEncode));

    */
    //如果span创建成功 : sw结构样例:
    //sw8: 1-MS41NDY0LjE1OTg1MDU3ODEwMDAx-MS41NDY0LjE1OTg1MDU3ODEwMDAx-1-bG9jYWxfcGhw-YjFmN2QzNTctZTNkYi00ODkzLTk1NjktY2ZmOTczNWNlNDRh-L3NjX2N1cmxfcG9zdC5waHA=-MTI3LjAuMC4xOjkwOTA=
    if (sw != NULL) {
        zval *option = NULL;
        int is_init = 0;
        option = zend_hash_index_find(Z_ARRVAL_P(&SKYWALKING_G(curl_header)), Z_RES_HANDLE_P(zid));

        if(option == NULL) {
            option = emalloc(sizeof(zval));
            bzero(option, sizeof(zval));
            array_init(option);
            is_init = 1;
        }

        add_next_index_string(option, sw); //sw字符串为sw8: 打头,所以天然就是HTTP header
        add_index_bool(&SKYWALKING_G(curl_header_send), (zend_ulong)Z_RES_HANDLE_P(zid), IS_TRUE);

        zval func;
        zval argv[3];
        zval ret;
        ZVAL_STRING(&func, "curl_setopt");

        ZVAL_COPY(&argv[0], zid);
        ZVAL_LONG(&argv[1], CURLOPT_HTTPHEADER);
        ZVAL_COPY(&argv[2], option);
        call_user_function(CG(function_table), NULL, &func, &ret, 3, argv); //给curl header调用注入全链路Trace信息
        zval_dtor(&ret);
        zval_dtor(&func);
        if(is_init == 1) {
            zval_ptr_dtor(option);
            efree(option);
        }
        zval_dtor(&argv[0]);
        zval_dtor(&argv[1]);
        zval_dtor(&argv[2]);
        efree(sw);
    }

    zval temp;
    char *l_millisecond;
    long millisecond;
    if(is_send == 1) {

        array_init(&temp);

        add_assoc_long(&temp, "spanId", Z_LVAL_P(span_id) + 1);
        add_assoc_long(&temp, "parentSpanId", 0);
        l_millisecond = get_millisecond();
        millisecond = zend_atol(l_millisecond, strlen(l_millisecond));
        efree(l_millisecond);
        add_assoc_long(&temp, "startTime", millisecond);
        add_assoc_long(&temp, "spanType", 1);
        add_assoc_long(&temp, "spanLayer", 3);
        add_assoc_long(&temp, "componentId", COMPONENT_HTTPCLIENT);
    }


    orig_curl_exec(INTERNAL_FUNCTION_PARAM_PASSTHRU); //执行原生的curl函数

    if (is_send == 1) {
        //结束时间获取
        l_millisecond = get_millisecond();
        millisecond = zend_atol(l_millisecond, strlen(l_millisecond));
        efree(l_millisecond);

        zval function_name_1, curlInfo_1;
        zval params_1[1];
        ZVAL_COPY(&params_1[0], zid);
        ZVAL_STRING(&function_name_1, "curl_getinfo");
        call_user_function(CG(function_table), NULL, &function_name_1, &curlInfo_1, 1, params_1);
        zval_dtor(&params_1[0]);
        zval_dtor(&function_name_1);

        add_assoc_long(&temp, "endTime", millisecond);

        add_assoc_string(&temp, "operationName", operation_name);
        add_assoc_string(&temp, "peer", peer);

        zval tags;
        array_init(&tags);
        add_assoc_string(&tags, "url", full_url);

        add_assoc_string(&tags, "component", "PHP-CURL");
        //HTTP 响应code
        zval *z_http_code = zend_hash_str_find(Z_ARRVAL(curlInfo_1), ZEND_STRL("http_code"));
      
        if (Z_LVAL_P(z_http_code) != 200){
            add_assoc_long(&temp, "isError", 1);
        }
        else
        {
            add_assoc_long(&temp, "isError", 0);
        }

        //针对http_code进行类型转化,便于增加tag
        if (Z_TYPE_P(z_http_code) == IS_LONG)
        {
            convert_to_string(z_http_code);                                       //针对z_http_code进行类型转换
            add_assoc_string(&tags, "http.status_code", Z_STRVAL_P(z_http_code)); //追加 reponse http code tag
        }

        add_assoc_zval(&temp, "tags", &tags);
        efree(peer);
        efree(operation_name);
        efree(full_url);

        php_url_free(url_info);

        zval _refs;
        array_init(&_refs);
        add_assoc_zval(&temp, "refs", &_refs);
        zend_hash_next_index_insert(Z_ARRVAL_P(spans), &temp);
        zval_dtor(&curlInfo_1);
        zval_dtor(&curlInfo);
    }
}

四. 关键附属函数分析

4.1 全局变量初始化函数

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

4.2 json序列化函数

static char *sky_json_encode(zval *parameter){

    smart_str buf = {0};
    zend_long options = 64;
#if PHP_VERSION_ID >= 70100
    if (php_json_encode(&buf, parameter, (int)options) != SUCCESS) {
        smart_str_free(&buf);
        return NULL;
    }
#else
    php_json_encode(&buf, parameter, (int)options);
#endif
    smart_str_0(&buf);
    if(buf.s != NULL) {
        char *bufs = emalloc(strlen(ZSTR_VAL(buf.s)) + 1);
        strcpy(bufs, ZSTR_VAL(buf.s));
        smart_str_free(&buf);
        return bufs;
    }
    return NULL;
}

4.3 发送APM数据函数

static void write_log(char *text) {
    if (application_instance != 0) {
        // to stream
        if(text == NULL || strlen(text) <= 0) {
            return;
        }

        struct sockaddr_un un;
        un.sun_family = AF_UNIX;
        strcpy(un.sun_path, SKYWALKING_G(sock_path));
        int fd;
        char *message = (char*) emalloc(strlen(text) + 10);
        bzero(message, strlen(text) + 10);

        fd = socket(AF_UNIX, SOCK_STREAM, 0);
        if (fd >= 0) {
            struct timeval tv;
            tv.tv_sec = 0;
            tv.tv_usec = 100000;
            setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv);
            int conn = connect(fd, (struct sockaddr *) &un, sizeof(un));

            if (conn >= 0) {
                sprintf(message, "1%s\n", text);
                write(fd, message, strlen(message));
            } else {
                php_error_docref(NULL, E_WARNING, "[skywalking] failed to connect the sock.");
            }
            close(fd);
        } else {
            php_error_docref(NULL, E_WARNING, "[skywalking] failed to open the sock.");
        }
        efree(message);
        efree(text);
    }

}

4.4 生成上下文函数

static void generate_context() {
    int sys_pid = getpid();
    long second = get_second();
    second = second * 10000 + sky_increment_id; //创建traceid的因子
    char *makeTraceId;
    makeTraceId = (char *) emalloc(sizeof(char) * 180); //分配traceId所需要的内存

    bzero(makeTraceId, sizeof(char) * 180);

    sprintf(makeTraceId, "%d.%d.%ld", application_instance, sys_pid, second);

    add_assoc_string(&SKYWALKING_G(context), "currentTraceId", makeTraceId); //针对上下文context变量添加currentTraceId元素
    add_assoc_long(&SKYWALKING_G(context), "isChild", 0);

    // parent
    zval *carrier = NULL;
    zval *sw;

    zend_bool jit_initialization = PG(auto_globals_jit); //PHP全局变量

    if (jit_initialization) {
        zend_string *server_str = zend_string_init("_SERVER", sizeof("_SERVER") - 1, 0);
        zend_is_auto_global(server_str);
        zend_string_release(server_str);
    }
    carrier = zend_hash_str_find(&EG(symbol_table), ZEND_STRL("_SERVER")); //获取查找$_SERVER超级全局变量

    if(SKYWALKING_G(version) == 5) {
        sw = zend_hash_str_find(Z_ARRVAL_P(carrier), "HTTP_SW3", sizeof("HTTP_SW3") - 1);

        if (sw != NULL && Z_TYPE_P(sw) == IS_STRING && Z_STRLEN_P(sw) > 10) {
            add_assoc_string(&SKYWALKING_G(context), "sw3", Z_STRVAL_P(sw));

            zval temp;
            array_init(&temp);

            php_explode(zend_string_init(ZEND_STRL("|"), 0), Z_STR_P(sw), &temp, 10);

            if(zend_array_count(Z_ARRVAL_P(&temp)) >= 8) {
                zval *sw3_0 = zend_hash_index_find(Z_ARRVAL(temp), 0);
                zval *sw3_1 = zend_hash_index_find(Z_ARRVAL(temp), 1);
                zval *sw3_2 = zend_hash_index_find(Z_ARRVAL(temp), 2);
                zval *sw3_3 = zend_hash_index_find(Z_ARRVAL(temp), 3);
                zval *sw3_4 = zend_hash_index_find(Z_ARRVAL(temp), 4);
                zval *sw3_5 = zend_hash_index_find(Z_ARRVAL(temp), 5);
                zval *sw3_6 = zend_hash_index_find(Z_ARRVAL(temp), 6);
                zval *sw3_7 = zend_hash_index_find(Z_ARRVAL(temp), 7);

                zval child;
                array_init(&child);
                ZVAL_LONG(&child, 1);
                zend_hash_str_update(Z_ARRVAL_P(&SKYWALKING_G(context)), "isChild", sizeof("isChild") - 1, &child);

                add_assoc_string(&SKYWALKING_G(context), "parentTraceSegmentId", Z_STRVAL_P(sw3_0));
                add_assoc_long(&SKYWALKING_G(context), "parentSpanId", zend_atol(Z_STRVAL_P(sw3_1), sizeof(Z_STRVAL_P(sw3_1)) - 1));
                add_assoc_long(&SKYWALKING_G(context), "parentApplicationInstance", zend_atol(Z_STRVAL_P(sw3_2), sizeof(Z_STRVAL_P(sw3_2)) - 1));
                add_assoc_long(&SKYWALKING_G(context), "entryApplicationInstance", zend_atol(Z_STRVAL_P(sw3_3), sizeof(Z_STRVAL_P(sw3_3)) - 1));
                add_assoc_str(&SKYWALKING_G(context), "networkAddress", trim_sharp(sw3_4));
                add_assoc_str(&SKYWALKING_G(context), "entryOperationName", trim_sharp(sw3_5));
                add_assoc_str(&SKYWALKING_G(context), "parentOperationName", trim_sharp(sw3_6));
                add_assoc_string(&SKYWALKING_G(context), "distributedTraceId", Z_STRVAL_P(sw3_7));
            }
        } else {
            add_assoc_long(&SKYWALKING_G(context), "parentApplicationInstance", application_instance);
            add_assoc_long(&SKYWALKING_G(context), "entryApplicationInstance", application_instance);
            char *uri = get_page_request_uri();
            add_assoc_string(&SKYWALKING_G(context), "entryOperationName", (uri == NULL) ? "" : uri);
            add_assoc_string(&SKYWALKING_G(context), "distributedTraceId", makeTraceId);
            if(uri != NULL) {
                efree(uri);
            }
        }
    } else if (SKYWALKING_G(version) == 6 || SKYWALKING_G(version) == 7) {
        sw = zend_hash_str_find(Z_ARRVAL_P(carrier), "HTTP_SW6", sizeof("HTTP_SW6") - 1);
        if (sw != NULL && Z_TYPE_P(sw) == IS_STRING && Z_STRLEN_P(sw) > 10) {
            add_assoc_string(&SKYWALKING_G(context), "sw6", Z_STRVAL_P(sw));

            zval temp;
            array_init(&temp);

            php_explode(zend_string_init(ZEND_STRL("-"), 0), Z_STR_P(sw), &temp, 10);

            if(zend_array_count(Z_ARRVAL_P(&temp)) >= 7) {
                zval *sw6_0 = zend_hash_index_find(Z_ARRVAL(temp), 0);
                zval *sw6_1 = zend_hash_index_find(Z_ARRVAL(temp), 1); // Trace Id
                zval *sw6_2 = zend_hash_index_find(Z_ARRVAL(temp), 2); // Parent trace segment Id
                zval *sw6_3 = zend_hash_index_find(Z_ARRVAL(temp), 3); // Parent span Id
                zval *sw6_4 = zend_hash_index_find(Z_ARRVAL(temp), 4); // Parent service instance Id
                zval *sw6_5 = zend_hash_index_find(Z_ARRVAL(temp), 5); // Entrance service instance Id
                zval *sw6_6 = zend_hash_index_find(Z_ARRVAL(temp), 6); // Target address of this request

                zval *sw6_7 = NULL;
                zval *sw6_8 = NULL;
                if (zend_array_count(Z_ARRVAL_P(&temp)) >= 9) {
                    sw6_7 = zend_hash_index_find(Z_ARRVAL(temp), 7);
                    sw6_8 = zend_hash_index_find(Z_ARRVAL(temp), 8);
                }


                zval child;
                array_init(&child);
                ZVAL_LONG(&child, 1)
                zend_hash_str_update(Z_ARRVAL_P(&SKYWALKING_G(context)), "isChild", sizeof("isChild") - 1, &child);

                zval sw6_1decode;
                zval sw6_2decode;
                zval sw6_6decode;
                zval_b64_decode(&sw6_1decode, Z_STRVAL_P(sw6_1));
                zval_b64_decode(&sw6_2decode, Z_STRVAL_P(sw6_2));
                zval_b64_decode(&sw6_6decode, Z_STRVAL_P(sw6_6));

                add_assoc_string(&SKYWALKING_G(context), "parentTraceSegmentId", Z_STRVAL(sw6_2decode));
                add_assoc_long(&SKYWALKING_G(context), "parentSpanId", zend_atol(Z_STRVAL_P(sw6_3), sizeof(Z_STRVAL_P(sw6_3)) - 1));
                add_assoc_long(&SKYWALKING_G(context), "parentApplicationInstance", zend_atol(Z_STRVAL_P(sw6_4), sizeof(Z_STRVAL_P(sw6_4)) - 1));
                add_assoc_long(&SKYWALKING_G(context), "entryApplicationInstance", zend_atol(Z_STRVAL_P(sw6_5), sizeof(Z_STRVAL_P(sw6_5)) - 1));
                add_assoc_string(&SKYWALKING_G(context), "networkAddress", Z_STRVAL(sw6_6decode));
                if (sw6_7 != NULL && sw6_8 != NULL) {

                    zval sw6_7decode;
                    zval sw6_8decode;
                    zval_b64_decode(&sw6_7decode, Z_STRVAL_P(sw6_7));
                    zval_b64_decode(&sw6_8decode, Z_STRVAL_P(sw6_8));

                    add_assoc_string(&SKYWALKING_G(context), "entryOperationName", Z_STRVAL(sw6_7decode));
                    add_assoc_string(&SKYWALKING_G(context), "parentOperationName", Z_STRVAL(sw6_8decode));

                    zval_dtor(&sw6_7decode);
                    zval_dtor(&sw6_8decode);
                }
                add_assoc_string(&SKYWALKING_G(context), "distributedTraceId", Z_STRVAL(sw6_1decode));

                zval_dtor(&sw6_1decode);
                zval_dtor(&sw6_2decode);
                zval_dtor(&sw6_6decode);
            }
        } else {
            add_assoc_long(&SKYWALKING_G(context), "parentApplicationInstance", application_instance);
            add_assoc_long(&SKYWALKING_G(context), "entryApplicationInstance", application_instance);
            char *uri = get_page_request_uri();
            char *path = NULL;
            if (uri != NULL) {
                path = (char *)emalloc(strlen(uri) + 5);
                bzero(path, strlen(uri) + 5);

                int i;
                for(i = 0; i < strlen(uri); i++) {
                    if (uri[i] == '?') {
                        break;
                    }
                    path[i] = uri[i];
                }
                path[i] = '\0';
            }

            add_assoc_string(&SKYWALKING_G(context), "entryOperationName", (path == NULL) ? "" : path);
            if (path != NULL) {
                efree(path);
            }

            add_assoc_string(&SKYWALKING_G(context), "distributedTraceId", makeTraceId);
            if(uri != NULL) {
                efree(uri);
            }
        }
    } else if (SKYWALKING_G(version) == 8) {
        sw = zend_hash_str_find(Z_ARRVAL_P(carrier), "HTTP_SW8", sizeof("HTTP_SW8") - 1); //$SERVER['HTTP_SW8'];
        if (sw != NULL && Z_TYPE_P(sw) == IS_STRING && Z_STRLEN_P(sw) > 10) { //从header头部拿到了trace信息
            add_assoc_string(&SKYWALKING_G(context), "sw8", Z_STRVAL_P(sw));

            zval temp;
            array_init(&temp); //初始化数组元素

            php_explode(zend_string_init(ZEND_STRL("-"), 0), Z_STR_P(sw), &temp, 10);

            if(zend_array_count(Z_ARRVAL_P(&temp)) >= 7) {
                zval *sw8_0 = zend_hash_index_find(Z_ARRVAL(temp), 0);
                zval *sw8_1 = zend_hash_index_find(Z_ARRVAL(temp), 1); // Trace Id base64
                zval *sw8_2 = zend_hash_index_find(Z_ARRVAL(temp), 2); // Parent trace segment Id
                zval *sw8_3 = zend_hash_index_find(Z_ARRVAL(temp), 3); // Parent span Id
                zval *sw8_4 = zend_hash_index_find(Z_ARRVAL(temp), 4); // Parent service
                zval *sw8_5 = zend_hash_index_find(Z_ARRVAL(temp), 5); // Parent service instance
                zval *sw8_6 = zend_hash_index_find(Z_ARRVAL(temp), 6); // Parent endpoint
                zval *sw8_7 = zend_hash_index_find(Z_ARRVAL(temp), 7); // Target address used at client side of this request

                zval child;
                array_init(&child);
                ZVAL_LONG(&child, 1)
                zend_hash_str_update(Z_ARRVAL_P(&SKYWALKING_G(context)), "isChild", sizeof("isChild") - 1, &child); //因为当前HTTP SERVER有TraceId,所以链路存在父对象、因此切换当前上下文状态为child状态。

                zval sw8_1decode;
                zval sw8_2decode;
                zval sw8_4decode;
                zval sw8_5decode;
                zval sw8_6decode;
                zval sw8_7decode;
                zval_b64_decode(&sw8_1decode, Z_STRVAL_P(sw8_1));
                zval_b64_decode(&sw8_2decode, Z_STRVAL_P(sw8_2));
                zval_b64_decode(&sw8_4decode, Z_STRVAL_P(sw8_4));
                zval_b64_decode(&sw8_5decode, Z_STRVAL_P(sw8_5));
                zval_b64_decode(&sw8_6decode, Z_STRVAL_P(sw8_6));
                zval_b64_decode(&sw8_7decode, Z_STRVAL_P(sw8_7));

                //针对当前请求的上下文丰富相关链路数据。
                add_assoc_string(&SKYWALKING_G(context), "traceId", Z_STRVAL(sw8_1decode));
                add_assoc_string(&SKYWALKING_G(context), "parentTraceSegmentId", Z_STRVAL(sw8_2decode));
                add_assoc_long(&SKYWALKING_G(context), "parentSpanId", zend_atol(Z_STRVAL_P(sw8_3), sizeof(Z_STRVAL_P(sw8_3)) - 1));
                add_assoc_string(&SKYWALKING_G(context), "parentService", Z_STRVAL(sw8_4decode));
                add_assoc_string(&SKYWALKING_G(context), "parentServiceInstance", Z_STRVAL(sw8_5decode));
                add_assoc_string(&SKYWALKING_G(context), "parentEndpoint", Z_STRVAL(sw8_6decode));
                add_assoc_string(&SKYWALKING_G(context), "targetAddress", Z_STRVAL(sw8_7decode));

                //释放转码相关内存
                zval_dtor(&sw8_1decode);
                zval_dtor(&sw8_2decode);
                zval_dtor(&sw8_4decode);
                zval_dtor(&sw8_5decode);
                zval_dtor(&sw8_6decode);
                zval_dtor(&sw8_7decode);
            }
        } else {
            //$SERVER没有全链路ID,证明当前为ROOT根
            //此处可以注入采样率
            add_assoc_string(&SKYWALKING_G(context), "parentService", service);
            add_assoc_string(&SKYWALKING_G(context), "parentServiceInstance", service_instance);
            char *uri = get_page_request_uri(); //获取当前请求的URI PATH
            char *path = NULL;
            if (uri != NULL) {
                path = (char *)emalloc(strlen(uri) + 5);
                bzero(path, strlen(uri) + 5);

                int i;
                for(i = 0; i < strlen(uri); i++) {
                    if (uri[i] == '?') {
                        break;
                    }
                    path[i] = uri[i];
                }
                path[i] = '\0';
            }

            add_assoc_string(&SKYWALKING_G(context), "parentEndpoint", (path == NULL) ? "" : path); //把自己当前的PATH当做父级EndPoint
            if (path != NULL) {
                efree(path);
            }

            add_assoc_string(&SKYWALKING_G(context), "traceId", makeTraceId); //丰富traceId信息
            if(uri != NULL) {
                efree(uri);
            }
        }
    }

    efree(makeTraceId);
}

4.5 解析请求URI信息函数

//获取当前请求的URI地址
static char *get_page_request_uri() {
    zval *carrier = NULL;
    zval *request_uri;

    smart_str uri = {0}; //创建smart_str对象

    if (strcasecmp("cli", sapi_module.name) == 0) {
        smart_str_appendl(&uri, "cli", strlen("cli"));
    } else {
        zend_bool jit_initialization = PG(auto_globals_jit);

        if (jit_initialization) {
            zend_string *server_str = zend_string_init("_SERVER", sizeof("_SERVER") - 1, 0);
            zend_is_auto_global(server_str);
            zend_string_release(server_str);
        }
        carrier = zend_hash_str_find(&EG(symbol_table), ZEND_STRL("_SERVER"));

        request_uri = zend_hash_str_find(Z_ARRVAL_P(carrier), "REQUEST_URI", sizeof("REQUEST_URI") - 1);
        smart_str_appendl(&uri, Z_STRVAL_P(request_uri), strlen(Z_STRVAL_P(request_uri)));
    }

    smart_str_0(&uri); //smart_str追加\0结尾
    if (uri.s != NULL) { //转化为char*类型
        char *uris = emalloc(strlen(ZSTR_VAL(uri.s)) + 1);
        strcpy(uris, ZSTR_VAL(uri.s));
        smart_str_free(&uri);
        return uris;
    }
    return NULL;
}

4.6 初始化请求函数

//请求初始化操作:用来丰富trace、spans等信息: 审计:2020.08.27 00:01
static void request_init() {

    array_init(&SKYWALKING_G(curl_header));
    array_init(&SKYWALKING_G(curl_header_send));
    array_init(&SKYWALKING_G(context)); //初始化上下文
    array_init(&SKYWALKING_G(UpstreamSegment)); //初始化to agent的数据格式

    generate_context(); //生成上下文

    add_assoc_long(&SKYWALKING_G(UpstreamSegment), "application_instance", application_instance); //from agent
    add_assoc_stringl(&SKYWALKING_G(UpstreamSegment), "uuid", application_uuid, strlen(application_uuid)); //from agent
    add_assoc_long(&SKYWALKING_G(UpstreamSegment), "pid", getppid());
    add_assoc_long(&SKYWALKING_G(UpstreamSegment), "application_id", application_id);
    add_assoc_long(&SKYWALKING_G(UpstreamSegment), "version", SKYWALKING_G(version));
    SKY_ADD_ASSOC_ZVAL(&SKYWALKING_G(UpstreamSegment), "segment"); //创建子数组对象,用于构建 $UpstreamSegment['segment'] = array();
    SKY_ADD_ASSOC_ZVAL(&SKYWALKING_G(UpstreamSegment), "globalTraceIds");

    add_assoc_stringl(&SKYWALKING_G(UpstreamSegment), "service", service, strlen(service)); //like app_code: from agent
    add_assoc_stringl(&SKYWALKING_G(UpstreamSegment), "serviceInstance", service_instance, strlen(service_instance)); //uuid : from agent
    zval *traceId = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "currentTraceId", sizeof("currentTraceId") - 1);

    zval traceSegmentObject;
    zval spans;
    array_init(&spans);
    array_init(&traceSegmentObject);
    add_assoc_string(&traceSegmentObject, "traceSegmentId", Z_STRVAL_P(traceId));
    add_assoc_long(&traceSegmentObject, "isSizeLimited", 0);

    zval temp;
    char *peer = NULL;
    char *uri = get_page_request_uri();
    char *path = (char*)emalloc(strlen(uri) + 5);
    bzero(path, strlen(uri) + 5);


    int i;
    for(i = 0; i < strlen(uri); i++) {
        if (uri[i] == '?') {
            break;
        }
        path[i] = uri[i];
    }
    path[i] = '\0';

    array_init(&temp);
    peer = get_page_request_peer(); // 从PHP $_SERVER变量中读取peer数据
    zval tags;
    array_init(&tags); //创建tags对象
    add_assoc_string(&tags, "url", (uri == NULL) ? "" : uri);
    add_assoc_string(&tags, "server_type", "PHP");      //注入PHP Server类型
    add_assoc_string(&tags, "php_version",PHP_VERSION); //注入PHP版本号

    add_assoc_zval(&temp, "tags", &tags);

    add_assoc_long(&temp, "spanId", 0);
    add_assoc_long(&temp, "parentSpanId", -1);
    char *l_millisecond = get_millisecond();
    long millisecond = zend_atol(l_millisecond, strlen(l_millisecond));
    efree(l_millisecond);
    add_assoc_long(&temp, "startTime", millisecond);
    add_assoc_string(&temp, "operationName", path);
    add_assoc_string(&temp, "peer", (peer == NULL) ? "" : peer);
    add_assoc_long(&temp, "spanType", 0);
    add_assoc_long(&temp, "spanLayer", 3);
    if (SKYWALKING_G(version) == 8) {
        add_assoc_long(&temp, "componentId", 8001);
    } else {
        add_assoc_long(&temp, "componentId", COMPONENT_UNDERTOW);
    }


    // sw8 or sw6 for parent endpoint name
    add_assoc_string(&SKYWALKING_G(context), "currentEndpoint", path);

    efree(path);
    if (peer != NULL) {
        efree(peer);
    }
    if (uri != NULL) {
        efree(uri);
    }

    zval *isChild = zend_hash_str_find(Z_ARRVAL_P(&SKYWALKING_G(context)), "isChild", sizeof("isChild") - 1);
    // refs
    zval refs;
    array_init(&refs);

    zval globalTraceIds;
    array_init(&globalTraceIds);
    zval tmpGlobalTraceIds;

    add_assoc_string(&SKYWALKING_G(UpstreamSegment), "traceId", Z_STRVAL_P(traceId));

    if(Z_LVAL_P(isChild) == 1) {
        zval ref;
        array_init(&ref);
        zval *parentTraceSegmentId = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "parentTraceSegmentId", sizeof("parentTraceSegmentId") - 1);
        zval *parentSpanId = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "parentSpanId", sizeof("parentSpanId") - 1);
        zval *parentApplicationInstance = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "parentApplicationInstance", sizeof("parentApplicationInstance") - 1);
        zval *entryApplicationInstance = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "entryApplicationInstance", sizeof("entryApplicationInstance") - 1);
        zval *entryOperationName = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "entryOperationName", sizeof("entryOperationName") - 1);
        zval *networkAddress = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "networkAddress", sizeof("networkAddress") - 1);
        zval *parentOperationName = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "parentOperationName", sizeof("parentOperationName") - 1);
        zval *distributedTraceId = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "distributedTraceId", sizeof("distributedTraceId") - 1);
        add_assoc_long(&ref, "type", 0);
        add_assoc_string(&ref, "parentTraceSegmentId", Z_STRVAL_P(parentTraceSegmentId));
        add_assoc_long(&ref, "parentSpanId", Z_LVAL_P(parentSpanId));

        if (SKYWALKING_G(version) == 8) {
            zval *traceId = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "traceId", sizeof("traceId") - 1);
            zval *parentService = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "parentService", sizeof("parentService") - 1);
            zval *parentServiceInstance = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "parentServiceInstance", sizeof("parentServiceInstance") - 1);
            zval *parentEndpoint = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "parentEndpoint", sizeof("parentEndpoint") - 1);
            zval *targetAddress = zend_hash_str_find(Z_ARRVAL(SKYWALKING_G(context)), "targetAddress", sizeof("targetAddress") - 1);

            add_assoc_string(&ref, "traceId", Z_STRVAL_P(traceId));
            add_assoc_string(&ref, "parentService", Z_STRVAL_P(parentService));
            add_assoc_string(&ref, "parentServiceInstance", Z_STRVAL_P(parentServiceInstance));
            add_assoc_string(&ref, "parentEndpoint", Z_STRVAL_P(parentEndpoint));
            add_assoc_string(&ref, "targetAddress", Z_STRVAL_P(targetAddress));
            zend_hash_str_update(Z_ARRVAL(SKYWALKING_G(UpstreamSegment)), "traceId", sizeof("traceId") - 1, traceId);
        } else {
            add_assoc_long(&ref, "parentApplicationInstanceId", Z_LVAL_P(parentApplicationInstance));
            add_assoc_long(&ref, "entryApplicationInstanceId", Z_LVAL_P(entryApplicationInstance));
            add_assoc_string(&ref, "networkAddress", Z_STRVAL_P(networkAddress));
            add_assoc_string(&ref, "entryServiceName", Z_STRVAL_P(entryOperationName));
            add_assoc_string(&ref, "parentServiceName", Z_STRVAL_P(parentOperationName));
            ZVAL_STRING(&tmpGlobalTraceIds, Z_STRVAL_P(distributedTraceId));
        }

        zend_hash_next_index_insert(Z_ARRVAL(refs), &ref);
    } else {
        ZVAL_STRING(&tmpGlobalTraceIds, Z_STRVAL_P(traceId));
    }

    zend_hash_str_add(Z_ARRVAL(temp), "refs", sizeof("refs") - 1, &refs); 
    zend_hash_next_index_insert(Z_ARRVAL(spans), &temp);

    add_assoc_zval(&traceSegmentObject, "spans", &spans);

    if (SKYWALKING_G(version) != 8)
    {                                                                              //谨慎调整
        zend_hash_next_index_insert(Z_ARRVAL(globalTraceIds), &tmpGlobalTraceIds); //问题症结 :skywalking 8.0版本之后移除这个参数支持
    }
    zend_hash_str_update(Z_ARRVAL(SKYWALKING_G(UpstreamSegment)), "segment", sizeof("segment") - 1, &traceSegmentObject);
    zend_hash_str_update(Z_ARRVAL(SKYWALKING_G(UpstreamSegment)), "globalTraceIds", sizeof("globalTraceIds") - 1, &globalTraceIds);
}

4.7 APM数据flush函数

static void sky_flush_all() {
    char *l_millisecond = get_millisecond();
    long millisecond = zend_atol(l_millisecond, strlen(l_millisecond));
    efree(l_millisecond);

    zval *span = get_first_span();

    add_assoc_long(span, "endTime", millisecond); //标志请求结束时间
    if ((SG(sapi_headers).http_response_code >= 500)) { //标志是否出错
        add_assoc_long(span, "isError", 1);
    } else {
        add_assoc_long(span, "isError", 0);
    }
    write_log(sky_json_encode(&SKYWALKING_G(UpstreamSegment))); //发送APM数据 - UNIX domain socket途径
}

4.8 APM注册函数

//sky-agent注册 :2020.08.26 22:34审计结束
static int sky_register() {
    if (application_instance == 0) {
        struct sockaddr_un un;
        un.sun_family = AF_UNIX; //UNIX通信类型
        strcpy(un.sun_path, SKYWALKING_G(sock_path)); //UNIX通信地址
        int fd;
        char message[1024]; //内存空间优化
        char return_message[1024]; //内存空间优化

        fd = socket(AF_UNIX, SOCK_STREAM, 0); //创建client fd
        if (fd >= 0) { 
            struct timeval tv;
            tv.tv_sec = 0;
            tv.tv_usec = 50000; //单位微秒:50ms
            setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char *) &tv, sizeof tv);
            int conn = connect(fd, (struct sockaddr *) &un, sizeof(un));

            if (conn >= 0) { 
                //进行ServiceName构建:如果环境变量不存在KEY对应的值,则进行环境变量获取
                char *env_app_code = getenv(SKYWALKING_G(app_code_env_key));
                if (env_app_code != NULL)
                {
                    SKYWALKING_G(app_code) = env_app_code;
                }

                bzero(message, sizeof(message));

                sprintf(message, "0{\"app_code\":\"%s\",\"pid\":%d,\"version\":%d}\n", SKYWALKING_G(app_code),
                        getppid(), SKYWALKING_G(version));
                write(fd, message, strlen(message));

                bzero(return_message, sizeof(return_message));
                read(fd, return_message, sizeof(return_message));

                char *ids[10] = {0};
                int i = 0;
                char *p = strtok(return_message, ","); //分割握手协议
                while (p != NULL) {
                    ids[i++] = p;
                    p = strtok(NULL, ",");
                }

                if (ids[0] != NULL && ids[1] != NULL && ids[2] != NULL) {
                    if (SKYWALKING_G(version) == 6 || SKYWALKING_G(version) == 7) {
                        application_id = atoi(ids[0]);
                        application_instance = atoi(ids[1]);
                        strncpy(application_uuid, ids[2], sizeof application_uuid - 1);
                    } else if (SKYWALKING_G(version) == 8) {
                        application_id = 1;
                        application_instance = 1;
                        strncpy(service, ids[0], sizeof service - 1); //拷贝service app_code ,此处和sidecar进行交互,可以定制化 : app_code
                        strncpy(service_instance, ids[1], sizeof service_instance - 1);
                        strncpy(application_uuid, ids[2], sizeof application_uuid - 1);
                    }
                }

            } else {
                php_error_docref(NULL, E_WARNING, "[skywalking] failed to connect the socket.");
            }

            close(fd);
        } else {
            php_error_docref(NULL, E_WARNING, "[skywalking] failed to open the socket.");
        }
    }
    return 0;
}

五.附录

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

#ifndef SKYWALKING_COMPONENTS_H
#define SKYWALKING_COMPONENTS_H

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

#endif //SKYWALKING_COMPONENTS_H

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

static char *sky_json_encode(zval *parameter); //json序列化
static long get_second(); //获取秒级时间戳
static char *get_millisecond(); //获取毫秒级时间戳
static char *generate_sw3(zend_long span_id, char *peer_host, char *operation_name); //生成sw3协议数据
static char *generate_sw6(zend_long span_id, char *peer_host); //生成sw6协议数据
static char *generate_sw8(zend_long span_id, char *peer_host); //生成sw8协议数据
static void generate_context(); //生成上下文
static char *get_page_request_uri(); //获取request的uri
static char *get_page_request_peer(); //获取request的主机信息
static void write_log( char *text); //发送APM日志
static void request_init();//初始化一次请求
static void zval_b64_encode(zval *out, char *in); //base64序列化
static void zval_b64_decode(zval *out, char *in); //base64反序列化
static char *sky_get_class_name(zval *obj); //获取zval数据结构的class名称
static zval *sky_read_property(zval *obj, const char *property, int parent); //读取apm监控数据的某个属性
static char *sky_redis_fnamewall(const char *function_name); //针对redis操作增加函数边界符号 |%s|
static int sky_redis_opt_for_string_key(char *fnamewall); //查询redis指令是否在APM监控范围内

static void sky_flush_all(); //发送skywalking APM数据包
static zval *get_first_span(); //获取收个全链路span数据
static zval *get_spans(); //获取全部的spans数据
static char* _get_current_machine_ip(); //获取当前机器的IP

static char *sky_memcached_fnamewall(const char *function_name); //针对memcache操作增加函数边界服务 |%s|
static int sky_memcached_opt_for_string_key(char *fnamewall); //查询memcache指令是否在APM监控范围内

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

//curl函数Hook函数
void sky_curl_exec_handler(INTERNAL_FUNCTION_PARAMETERS); 
void sky_curl_setopt_handler(INTERNAL_FUNCTION_PARAMETERS);
void sky_curl_setopt_array_handler(INTERNAL_FUNCTION_PARAMETERS);
void sky_curl_close_handler(INTERNAL_FUNCTION_PARAMETERS);

static void (*orig_curl_exec)(INTERNAL_FUNCTION_PARAMETERS) = NULL;
static void (*orig_curl_setopt)(INTERNAL_FUNCTION_PARAMETERS) = NULL;
static void (*orig_curl_setopt_array)(INTERNAL_FUNCTION_PARAMETERS) = NULL;
static void (*orig_curl_close)(INTERNAL_FUNCTION_PARAMETERS) = NULL;

5.4 附录三:全局变量定义

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

extern ZEND_DECLARE_MODULE_GLOBALS(skywalking);

评论已关闭