diff --git a/apm.c b/apm.c index 624369c..00de375 100644 --- a/apm.c +++ b/apm.c @@ -44,7 +44,7 @@ #ifdef APM_DRIVER_SQLITE3 # include "driver_sqlite3.h" #endif -#ifdef APM_DRIVER_MYSQL +#if defined(APM_DRIVER_MYSQL) || defined(APM_DRIVER_MYSQLND) # include "driver_mysql.h" #endif #ifdef APM_DRIVER_STATSD @@ -122,8 +122,20 @@ struct timeval begin_tp; struct rusage begin_usg; #endif +static const zend_module_dep apm_deps[] = { +#ifdef APM_DRIVER_SOCKET + ZEND_MOD_REQUIRED("json") +#endif +#ifdef APM_DRIVER_MYSQLND + ZEND_MOD_REQUIRED("mysqlnd") +#endif + ZEND_MOD_END +}; + zend_module_entry apm_module_entry = { - STANDARD_MODULE_HEADER, + STANDARD_MODULE_HEADER_EX, + NULL, + apm_deps, "apm", NULL, PHP_MINIT(apm), @@ -186,7 +198,7 @@ PHP_INI_BEGIN() STD_PHP_INI_BOOLEAN("apm.sqlite_process_silenced_events", "1", PHP_INI_PERDIR, OnUpdateBool, sqlite3_process_silenced_events, zend_apm_globals, apm_globals) #endif -#ifdef APM_DRIVER_MYSQL +#if defined(APM_DRIVER_MYSQL) || defined(APM_DRIVER_MYSQLND) /* Boolean controlling whether the driver is active or not */ STD_PHP_INI_BOOLEAN("apm.mysql_enabled", "1", PHP_INI_PERDIR, OnUpdateBool, mysql_enabled, zend_apm_globals, apm_globals) /* Boolean controlling the collection of stats */ @@ -260,7 +272,7 @@ static PHP_GINIT_FUNCTION(apm) *next = apm_driver_sqlite3_create(); next = &(*next)->next; #endif -#ifdef APM_DRIVER_MYSQL +#if defined(APM_DRIVER_MYSQL) || defined(APM_DRIVER_MYSQLND) *next = apm_driver_mysql_create(); next = &(*next)->next; #endif @@ -457,6 +469,21 @@ PHP_MINFO_FUNCTION(apm) php_info_print_table_start(); php_info_print_table_row(2, "APM support", "enabled"); php_info_print_table_row(2, "Version", PHP_APM_VERSION); +#ifdef APM_DRIVER_SQLITE3 + php_info_print_table_row(2, "Sqlite3 driver", "enabled"); +#endif +#ifdef APM_DRIVER_MYSQL + php_info_print_table_row(2, "MySQL driver (mysqlnd)", "enabled"); +#endif +#ifdef APM_DRIVER_MYSQLND + php_info_print_table_row(2, "MySQL driver (libmysqlclient)", "enabled"); +#endif +#ifdef APM_DRIVER_STATSD + php_info_print_table_row(2, "Statsd driver", "enabled"); +#endif +#ifdef APM_DRIVER_SOCKET + php_info_print_table_row(2, "Socket driver", "enabled"); +#endif php_info_print_table_end(); DISPLAY_INI_ENTRIES(); diff --git a/config.m4 b/config.m4 index 647bcaa..aff8205 100644 --- a/config.m4 +++ b/config.m4 @@ -29,7 +29,9 @@ PHP_ARG_ENABLE(apm, whether to enable apm support, [ --enable-apm Enable apm support], yes) PHP_ARG_WITH(sqlite3, enable support for sqlite3, [ --with-sqlite3=DIR Location of sqlite3 library], yes, no) -PHP_ARG_WITH(mysql, enable support for MySQL, +PHP_ARG_WITH(mysqlnd, enable support for MySQL through mysqlnd, +[ --with-mysqlnd Enable mysqlnd support], no, no) +PHP_ARG_WITH(mysql, enable support for MySQL through libmysqlclient, [ --with-mysql=DIR Location of MySQL base directory], yes, no) PHP_ARG_ENABLE(statsd, enable support for statsd, [ --enable-statsd Enable statsd support], yes, no) @@ -103,7 +105,18 @@ if test "$PHP_APM" != "no"; then fi fi - if test "$PHP_MYSQL" != "no"; then + if test "$PHP_MYSQLND" != "no"; then + mysql_driver="driver_mysqlnd.c" + AC_MSG_CHECKING([mysqlnd headers]) + if test -f $phpincludedir/ext/mysqlnd/mysqlnd.h; then + AC_MSG_RESULT(found) + AC_DEFINE(APM_DRIVER_MYSQLND, 1, [activate MySQL storage driver]) + else + AC_MSG_RESULT(not found) + AC_MSG_ERROR([Please check PHP is build with mysqlnd]) + fi + + elif test "$PHP_MYSQL" != "no"; then mysql_driver="driver_mysql.c" AC_DEFINE(APM_DRIVER_MYSQL, 1, [activate MySQL storage driver]) diff --git a/driver_mysql.c b/driver_mysql.c index 1f2390b..f167fa1 100644 --- a/driver_mysql.c +++ b/driver_mysql.c @@ -61,52 +61,9 @@ MYSQL * mysql_get_instance(TSRMLS_D) { mysql_set_character_set(APM_G(mysql_event_db), "utf8"); - mysql_query( - APM_G(mysql_event_db), - "\ -CREATE TABLE IF NOT EXISTS request (\ - id INTEGER UNSIGNED PRIMARY KEY auto_increment,\ - application VARCHAR(255) NOT NULL,\ - ts TIMESTAMP NOT NULL,\ - script TEXT NOT NULL,\ - uri TEXT NOT NULL,\ - host TEXT NOT NULL,\ - ip INTEGER UNSIGNED NOT NULL,\ - cookies TEXT NOT NULL,\ - post_vars TEXT NOT NULL,\ - referer TEXT NOT NULL,\ - method TEXT NOT NULL\ -)" - ); - mysql_query( - APM_G(mysql_event_db), - "\ -CREATE TABLE IF NOT EXISTS event (\ - id INTEGER UNSIGNED PRIMARY KEY auto_increment,\ - request_id INTEGER UNSIGNED,\ - ts TIMESTAMP NOT NULL,\ - type SMALLINT UNSIGNED NOT NULL,\ - file TEXT NOT NULL,\ - line MEDIUMINT UNSIGNED NOT NULL,\ - message TEXT NOT NULL,\ - backtrace BLOB NOT NULL,\ - KEY request (request_id)\ -)" - ); - - mysql_query( - APM_G(mysql_event_db), - "\ -CREATE TABLE IF NOT EXISTS stats (\ - id INTEGER UNSIGNED PRIMARY KEY auto_increment,\ - request_id INTEGER UNSIGNED,\ - duration FLOAT UNSIGNED NOT NULL,\ - user_cpu FLOAT UNSIGNED NOT NULL,\ - sys_cpu FLOAT UNSIGNED NOT NULL,\ - mem_peak_usage INTEGER UNSIGNED NOT NULL,\ - KEY request (request_id)\ -)" - ); + mysql_query(APM_G(mysql_event_db), TABLE_REQUEST); + mysql_query(APM_G(mysql_event_db), TABLE_EVENT); + mysql_query(APM_G(mysql_event_db), TABLE_STATS); } return APM_G(mysql_event_db); @@ -185,7 +142,7 @@ static void apm_driver_mysql_insert_request(TSRMLS_D) if (mysql_query(connection, sql) != 0) APM_DEBUG("[MySQL driver] Error: %s\n", mysql_error(APM_G(mysql_event_db))); - mysql_query(connection, "SET @request_id = LAST_INSERT_ID()"); + mysql_query(connection, QUERY_REQUEST_ID); efree(sql); if (application_esc) diff --git a/driver_mysql.h b/driver_mysql.h index f10673e..878107b 100644 --- a/driver_mysql.h +++ b/driver_mysql.h @@ -36,4 +36,45 @@ apm_driver_entry * apm_driver_mysql_create(); PHP_INI_MH(OnUpdateAPMmysqlErrorReporting); +#define TABLE_REQUEST "\ + CREATE TABLE IF NOT EXISTS request (\ + id INTEGER UNSIGNED PRIMARY KEY auto_increment,\ + application VARCHAR(255) NOT NULL,\ + ts TIMESTAMP NOT NULL,\ + script TEXT NOT NULL,\ + uri TEXT NOT NULL,\ + host TEXT NOT NULL,\ + ip INTEGER UNSIGNED NOT NULL,\ + cookies TEXT NOT NULL,\ + post_vars TEXT NOT NULL,\ + referer TEXT NOT NULL,\ + method TEXT NOT NULL\ +)" + +#define TABLE_EVENT "\ + CREATE TABLE IF NOT EXISTS event (\ + id INTEGER UNSIGNED PRIMARY KEY auto_increment,\ + request_id INTEGER UNSIGNED,\ + ts TIMESTAMP NOT NULL,\ + type SMALLINT UNSIGNED NOT NULL,\ + file TEXT NOT NULL,\ + line MEDIUMINT UNSIGNED NOT NULL,\ + message TEXT NOT NULL,\ + backtrace BLOB NOT NULL,\ + KEY request (request_id)\ +)" + +#define TABLE_STATS "\ + CREATE TABLE IF NOT EXISTS stats (\ + id INTEGER UNSIGNED PRIMARY KEY auto_increment,\ + request_id INTEGER UNSIGNED,\ + duration FLOAT UNSIGNED NOT NULL,\ + user_cpu FLOAT UNSIGNED NOT NULL,\ + sys_cpu FLOAT UNSIGNED NOT NULL,\ + mem_peak_usage INTEGER UNSIGNED NOT NULL,\ + KEY request (request_id)\ +)" + +#define QUERY_REQUEST_ID "SET @request_id = LAST_INSERT_ID()" + #endif diff --git a/driver_mysqlnd.c b/driver_mysqlnd.c new file mode 100644 index 0000000..d6d27fe --- /dev/null +++ b/driver_mysqlnd.c @@ -0,0 +1,261 @@ +/* + +----------------------------------------------------------------------+ + | APM stands for Alternative PHP Monitor | + +----------------------------------------------------------------------+ + | Copyright (c) 2008-2014 Davide Mendolia, Patrick Allaert | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Patrick Allaert | + +----------------------------------------------------------------------+ +*/ + +#include "php_apm.h" +#include "php_ini.h" +#include "driver_mysql.h" +#include + +#ifdef NETWARE +#include +#endif +#if HAVE_ARPA_INET_H +#include +#endif + +ZEND_EXTERN_MODULE_GLOBALS(apm); + +APM_DRIVER_CREATE(mysql) + + +static void mysql_destroy(TSRMLS_D) { + APM_DEBUG("[MySQL driver] Closing connection\n"); + mysqlnd_close(APM_G(mysql_event_db), 0); + APM_G(mysql_event_db) = NULL; +} + +/* Returns the MYSQL instance (singleton) */ +MYSQLND * mysql_get_instance(TSRMLS_D) { + + if (APM_G(mysql_event_db) == NULL) { + APM_G(mysql_event_db) = mysqlnd_init(0, 1); + + APM_DEBUG("[MySQL driver] Connecting to server..."); + if (mysqlnd_connect(APM_G(mysql_event_db), APM_G(mysql_db_host), APM_G(mysql_db_user), + APM_G(mysql_db_pass), strlen(APM_G(mysql_db_pass)), + APM_G(mysql_db_name), strlen(APM_G(mysql_db_name)), + APM_G(mysql_db_port), NULL, 0, 0) == NULL) { + APM_DEBUG("FAILED! Message: %s\n", mysqlnd_error(APM_G(mysql_event_db))); + + mysql_destroy(TSRMLS_C); + return NULL; + } + APM_DEBUG("OK\n"); + + mysqlnd_set_character_set(APM_G(mysql_event_db), "utf8"); + + mysqlnd_query(APM_G(mysql_event_db), TABLE_REQUEST, sizeof(TABLE_REQUEST)-1); + mysqlnd_query(APM_G(mysql_event_db), TABLE_EVENT, sizeof(TABLE_EVENT)-1); + mysqlnd_query(APM_G(mysql_event_db), TABLE_STATS, sizeof(TABLE_STATS)-1); + } + + return APM_G(mysql_event_db); +} + +/* Escape string request data */ +#define APM_MYSQL_ESCAPE_STR(data) \ +{ \ + if (APM_RD(data##_found)) { \ + data##_len = strlen(APM_RD_STRVAL(data)); \ + data##_esc = emalloc(data##_len * 2 + 1); \ + data##_len = mysqlnd_real_escape_string(connection, data##_esc, APM_RD_STRVAL(data), data##_len); \ + } \ +} + +/* Escape smart_str request data */ +#define APM_MYSQL_ESCAPE_SMART_STR(data) \ +{ \ + if (APM_RD(data##_found)) { \ + data##_len = strlen(APM_RD_SMART_STRVAL(data)); \ + data##_esc = emalloc(data##_len * 2 + 1); \ + data##_len = mysqlnd_real_escape_string(connection, data##_esc, APM_RD_SMART_STRVAL(data), data##_len); \ + } \ +} + +/* Insert a request in the backend */ +static void apm_driver_mysql_insert_request(TSRMLS_D) +{ + char *application_esc = NULL, *script_esc = NULL, *uri_esc = NULL, *host_esc = NULL, *cookies_esc = NULL, *post_vars_esc = NULL, *referer_esc = NULL, *method_esc = NULL, *sql = NULL; + unsigned int application_len = 0, script_len = 0, uri_len = 0, host_len = 0, ip_int = 0, cookies_len = 0, post_vars_len = 0, referer_len = 0, method_len = 0; + struct in_addr ip_addr; + MYSQLND *connection; + + extract_data(TSRMLS_C); + + APM_DEBUG("[MySQL driver] Begin insert request\n"); + if (APM_G(mysql_is_request_created)) { + APM_DEBUG("[MySQL driver] SKIPPED, request already created.\n"); + return; + } + + MYSQL_INSTANCE_INIT + + if (APM_G(application_id)) { + application_len = strlen(APM_G(application_id)); + application_esc = emalloc(application_len * 2 + 1); + application_len = mysqlnd_real_escape_string(connection, application_esc, APM_G(application_id), application_len); + } + + APM_MYSQL_ESCAPE_STR(script); + APM_MYSQL_ESCAPE_STR(uri); + APM_MYSQL_ESCAPE_STR(host); + APM_MYSQL_ESCAPE_STR(referer); + APM_MYSQL_ESCAPE_STR(method); + APM_MYSQL_ESCAPE_SMART_STR(cookies); + APM_MYSQL_ESCAPE_SMART_STR(post_vars); + + if (APM_RD(ip_found) && (inet_pton(AF_INET, APM_RD_STRVAL(ip), &ip_addr) == 1)) { + ip_int = ntohl(ip_addr.s_addr); + } + + sql = emalloc(166 + application_len + script_len + uri_len + host_len + cookies_len + post_vars_len + referer_len + method_len); + sprintf( + sql, + "INSERT INTO request (application, script, uri, host, ip, cookies, post_vars, referer, method) VALUES ('%s', '%s', '%s', '%s', %u, '%s', '%s', '%s', '%s')", + application_esc ? application_esc : "", + APM_RD(script_found) ? script_esc : "", + APM_RD(uri_found) ? uri_esc : "", + APM_RD(host_found) ? host_esc : "", + ip_int, APM_RD(cookies_found) ? cookies_esc : "", + APM_RD(post_vars_found) ? post_vars_esc : "", + APM_RD(referer_found) ? referer_esc : "", + APM_RD(method_found) ? method_esc : ""); + + APM_DEBUG("[MySQL driver] Sending: %s\n", sql); + if (mysqlnd_query(connection, sql, strlen(sql)) != 0) + APM_DEBUG("[MySQL driver] Error: %s\n", mysqlnd_error(APM_G(mysql_event_db))); + + mysqlnd_query(connection, QUERY_REQUEST_ID, sizeof(QUERY_REQUEST_ID)-1); + + efree(sql); + if (application_esc) + efree(application_esc); + if (script_esc) + efree(script_esc); + if (uri_esc) + efree(uri_esc); + if (host_esc) + efree(host_esc); + if (cookies_esc) + efree(cookies_esc); + if (post_vars_esc) + efree(post_vars_esc); + if (referer_esc) + efree(referer_esc); + if (method_esc) + efree(method_esc); + + APM_G(mysql_is_request_created) = 1; + APM_DEBUG("[MySQL driver] End insert request\n"); +} + +/* Insert an event in the backend */ +void apm_driver_mysql_process_event(PROCESS_EVENT_ARGS) +{ + char *filename_esc = NULL, *msg_esc = NULL, *trace_esc = NULL, *sql = NULL; + int filename_len = 0, msg_len = 0, trace_len = 0; + MYSQLND *connection; + + apm_driver_mysql_insert_request(TSRMLS_C); + + MYSQL_INSTANCE_INIT + + if (error_filename) { + filename_len = strlen(error_filename); + filename_esc = emalloc(filename_len * 2 + 1); + filename_len = mysqlnd_real_escape_string(connection, filename_esc, error_filename, filename_len); + } + + if (msg) { + msg_len = strlen(msg); + msg_esc = emalloc(msg_len * 2 + 1); + msg_len = mysqlnd_real_escape_string(connection, msg_esc, msg, msg_len); + } + + if (trace) { + trace_len = strlen(trace); + trace_esc = emalloc(trace_len * 2 + 1); + trace_len = mysqlnd_real_escape_string(connection, trace_esc, trace, trace_len); + } + + sql = emalloc(135 + filename_len + msg_len + trace_len); + sprintf( + sql, + "INSERT INTO event (request_id, type, file, line, message, backtrace) VALUES (@request_id, %d, '%s', %u, '%s', '%s')", + type, error_filename ? filename_esc : "", error_lineno, msg ? msg_esc : "", trace ? trace_esc : ""); + + APM_DEBUG("[MySQL driver] Sending: %s\n", sql); + if (mysqlnd_query(connection, sql, strlen(sql)) != 0) + APM_DEBUG("[MySQL driver] Error: %s\n", mysqlnd_error(APM_G(mysql_event_db))); + + efree(sql); + efree(filename_esc); + efree(msg_esc); + efree(trace_esc); +} + +int apm_driver_mysql_minit(int module_number TSRMLS_DC) +{ + return SUCCESS; +} + +int apm_driver_mysql_rinit(TSRMLS_D) +{ + APM_G(mysql_is_request_created) = 0; + return SUCCESS; +} + +int apm_driver_mysql_mshutdown(SHUTDOWN_FUNC_ARGS) +{ + if (APM_G(mysql_event_db) != NULL) { + mysql_destroy(TSRMLS_C); + } + + return SUCCESS; +} + +int apm_driver_mysql_rshutdown(TSRMLS_D) +{ + return SUCCESS; +} + +void apm_driver_mysql_process_stats(TSRMLS_D) +{ + char *sql = NULL; + MYSQLND *connection; + + apm_driver_mysql_insert_request(TSRMLS_C); + + MYSQL_INSTANCE_INIT + + sql = emalloc(170); + sprintf( + sql, + "INSERT INTO stats (request_id, duration, user_cpu, sys_cpu, mem_peak_usage) VALUES (@request_id, %f, %f, %f, %ld)", + USEC_TO_SEC(APM_G(duration)), + USEC_TO_SEC(APM_G(user_cpu)), + USEC_TO_SEC(APM_G(sys_cpu)), + APM_G(mem_peak_usage) + ); + + APM_DEBUG("[MySQL driver] Sending: %s\n", sql); + if (mysqlnd_query(connection, sql, strlen(sql)) != 0) + APM_DEBUG("[MySQL driver] Error: %s\n", mysqlnd_error(APM_G(mysql_event_db))); + + efree(sql); +} diff --git a/php_apm.h b/php_apm.h index 4a5ee24..ffb43a8 100644 --- a/php_apm.h +++ b/php_apm.h @@ -44,6 +44,9 @@ #ifdef APM_DRIVER_MYSQL #include #endif +#ifdef APM_DRIVER_MYSQLND + #include +#endif #ifdef PHP_WIN32 #define PHP_APM_API __declspec(dllexport) @@ -274,7 +277,7 @@ ZEND_BEGIN_MODULE_GLOBALS(apm) zend_bool sqlite3_process_silenced_events; #endif -#ifdef APM_DRIVER_MYSQL +#if defined(APM_DRIVER_MYSQL) || defined(APM_DRIVER_MYSQLND) /* Boolean controlling whether the driver is active or not */ zend_bool mysql_enabled; /* Boolean controlling the collection of stats */ @@ -294,7 +297,11 @@ ZEND_BEGIN_MODULE_GLOBALS(apm) /* MySQL database */ char *mysql_db_name; /* DB handle */ +#ifdef APM_DRIVER_MYSQL MYSQL *mysql_event_db; +#else + MYSQLND *mysql_event_db; +#endif /* Option to process silenced events */ zend_bool mysql_process_silenced_events;