root/ext/mysqlnd/mysqlnd_debug.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. MYSQLND_METHOD
  2. MYSQLND_METHOD
  3. MYSQLND_CLASS_METHODS_START
  4. _mysqlnd_debug
  5. mysqlnd_debug_trace_plugin_register

/*
  +----------------------------------------------------------------------+
  | PHP Version 5                                                        |
  +----------------------------------------------------------------------+
  | Copyright (c) 2006-2015 The PHP Group                                |
  +----------------------------------------------------------------------+
  | 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: Georg Richter <georg@mysql.com>                             |
  |          Andrey Hristov <andrey@mysql.com>                           |
  |          Ulf Wendel <uwendel@mysql.com>                              |
  +----------------------------------------------------------------------+
*/

/* $Id$ */

#include "php.h"
#include "mysqlnd.h"
#include "mysqlnd_priv.h"
#include "mysqlnd_debug.h"

static const char * const mysqlnd_debug_default_trace_file = "/tmp/mysqlnd.trace";

#ifdef ZTS 
#define MYSQLND_ZTS(self) TSRMLS_D = (self)->TSRMLS_C
#else
#define MYSQLND_ZTS(self)
#endif


/* {{{ mysqlnd_debug::open */
static enum_func_status
MYSQLND_METHOD(mysqlnd_debug, open)(MYSQLND_DEBUG * self, zend_bool reopen)
{
        MYSQLND_ZTS(self);

        if (!self->file_name) {
                return FAIL;
        }

        self->stream = php_stream_open_wrapper(self->file_name,
                                                                                   reopen == TRUE || self->flags & MYSQLND_DEBUG_APPEND? "ab":"wb",
                                                                                   REPORT_ERRORS, NULL);
        return self->stream? PASS:FAIL;
}
/* }}} */


/* {{{ mysqlnd_debug::log */
static enum_func_status
MYSQLND_METHOD(mysqlnd_debug, log)(MYSQLND_DEBUG * self,
                                                                   unsigned int line, const char * const file,
                                                                   unsigned int level, const char * type, const char * message)
{
        char pipe_buffer[512];
        enum_func_status ret;
        int i;
        char * message_line;
        unsigned int message_line_len;
        unsigned int flags = self->flags;
        char pid_buffer[10], time_buffer[30], file_buffer[200],
                 line_buffer[6], level_buffer[7];
        MYSQLND_ZTS(self);

        if (!self->stream && FAIL == self->m->open(self, FALSE)) {
                return FAIL;
        }

        if (level == -1) {
                level = zend_stack_count(&self->call_stack);
        }
        i = MIN(level, sizeof(pipe_buffer) / 2  - 1);
        pipe_buffer[i*2] = '\0';
        for (;i > 0;i--) {
                pipe_buffer[i*2 - 1] = ' ';
                pipe_buffer[i*2 - 2] = '|';
        }


        if (flags & MYSQLND_DEBUG_DUMP_PID) {
                snprintf(pid_buffer, sizeof(pid_buffer) - 1, "%5u: ", self->pid);
                pid_buffer[sizeof(pid_buffer) - 1 ] = '\0';
        }
        if (flags & MYSQLND_DEBUG_DUMP_TIME) {
                /* The following from FF's DBUG library, which is in the public domain */
#if defined(PHP_WIN32)
                /* FIXME This doesn't give microseconds as in Unix case, and the resolution is
                in system ticks, 10 ms intervals. See my_getsystime.c for high res */
                SYSTEMTIME loc_t;
                GetLocalTime(&loc_t);
                snprintf(time_buffer, sizeof(time_buffer) - 1,
                                 /* "%04d-%02d-%02d " */
                                 "%02d:%02d:%02d.%06d ",
                                 /*tm_p->tm_year + 1900, tm_p->tm_mon + 1, tm_p->tm_mday,*/
                                 loc_t.wHour, loc_t.wMinute, loc_t.wSecond, loc_t.wMilliseconds);
                time_buffer[sizeof(time_buffer) - 1 ] = '\0';
#else
                struct timeval tv;
                struct tm *tm_p;
                if (gettimeofday(&tv, NULL) != -1) {
                        if ((tm_p= localtime((const time_t *)&tv.tv_sec))) {
                                snprintf(time_buffer, sizeof(time_buffer) - 1,
                                                 /* "%04d-%02d-%02d " */
                                                 "%02d:%02d:%02d.%06d ",
                                                 /*tm_p->tm_year + 1900, tm_p->tm_mon + 1, tm_p->tm_mday,*/
                                                 tm_p->tm_hour, tm_p->tm_min, tm_p->tm_sec,
                                                 (int) (tv.tv_usec));
                                time_buffer[sizeof(time_buffer) - 1 ] = '\0';
                        }
                }
#endif
        }
        if (flags & MYSQLND_DEBUG_DUMP_FILE) {
                snprintf(file_buffer, sizeof(file_buffer) - 1, "%14s: ", file);
                file_buffer[sizeof(file_buffer) - 1 ] = '\0';
        }
        if (flags & MYSQLND_DEBUG_DUMP_LINE) {
                snprintf(line_buffer, sizeof(line_buffer) - 1, "%5u: ", line);
                line_buffer[sizeof(line_buffer) - 1 ] = '\0';
        }
        if (flags & MYSQLND_DEBUG_DUMP_LEVEL) {
                snprintf(level_buffer, sizeof(level_buffer) - 1, "%4u: ", level);
                level_buffer[sizeof(level_buffer) - 1 ] = '\0';
        }

        message_line_len = mnd_sprintf(&message_line, 0, "%s%s%s%s%s%s%s%s\n",
                                                                flags & MYSQLND_DEBUG_DUMP_PID? pid_buffer:"",
                                                                flags & MYSQLND_DEBUG_DUMP_TIME? time_buffer:"",
                                                                flags & MYSQLND_DEBUG_DUMP_FILE? file_buffer:"",
                                                                flags & MYSQLND_DEBUG_DUMP_LINE? line_buffer:"",
                                                                flags & MYSQLND_DEBUG_DUMP_LEVEL? level_buffer:"",
                                                                pipe_buffer, type? type:"", message);

        ret = php_stream_write(self->stream, message_line, message_line_len)? PASS:FAIL;
        mnd_sprintf_free(message_line);
        if (flags & MYSQLND_DEBUG_FLUSH) {
                self->m->close(self);
                self->m->open(self, TRUE);
        }
        return ret;
}
/* }}} */


/* {{{ mysqlnd_debug::log_va */
static enum_func_status
MYSQLND_METHOD(mysqlnd_debug, log_va)(MYSQLND_DEBUG *self,
                                                                          unsigned int line, const char * const file,
                                                                          unsigned int level, const char * type,
                                                                          const char *format, ...)
{
        char pipe_buffer[512];
        int i;
        enum_func_status ret;
        char * message_line, *buffer;
        unsigned int message_line_len;
        va_list args;
        unsigned int flags = self->flags;
        char pid_buffer[10], time_buffer[30], file_buffer[200],
                 line_buffer[6], level_buffer[7];
        MYSQLND_ZTS(self);

        if (!self->stream && FAIL == self->m->open(self, FALSE)) {
                return FAIL;
        }

        if (level == -1) {
                level = zend_stack_count(&self->call_stack);
        }
        i = MIN(level, sizeof(pipe_buffer) / 2  - 1);
        pipe_buffer[i*2] = '\0';
        for (;i > 0;i--) {
                pipe_buffer[i*2 - 1] = ' ';
                pipe_buffer[i*2 - 2] = '|';
        }


        if (flags & MYSQLND_DEBUG_DUMP_PID) {
                snprintf(pid_buffer, sizeof(pid_buffer) - 1, "%5u: ", self->pid);
                pid_buffer[sizeof(pid_buffer) - 1 ] = '\0';
        }
        if (flags & MYSQLND_DEBUG_DUMP_TIME) {
                /* The following from FF's DBUG library, which is in the public domain */
#if defined(PHP_WIN32)
                /* FIXME This doesn't give microseconds as in Unix case, and the resolution is
                in system ticks, 10 ms intervals. See my_getsystime.c for high res */
                SYSTEMTIME loc_t;
                GetLocalTime(&loc_t);
                snprintf(time_buffer, sizeof(time_buffer) - 1,
                                 /* "%04d-%02d-%02d " */
                                 "%02d:%02d:%02d.%06d ",
                                 /*tm_p->tm_year + 1900, tm_p->tm_mon + 1, tm_p->tm_mday,*/
                                 loc_t.wHour, loc_t.wMinute, loc_t.wSecond, loc_t.wMilliseconds);
                time_buffer[sizeof(time_buffer) - 1 ] = '\0';
#else
                struct timeval tv;
                struct tm *tm_p;
                if (gettimeofday(&tv, NULL) != -1) {
                        if ((tm_p= localtime((const time_t *)&tv.tv_sec))) {
                                snprintf(time_buffer, sizeof(time_buffer) - 1,
                                                 /* "%04d-%02d-%02d " */
                                                 "%02d:%02d:%02d.%06d ",
                                                 /*tm_p->tm_year + 1900, tm_p->tm_mon + 1, tm_p->tm_mday,*/
                                                 tm_p->tm_hour, tm_p->tm_min, tm_p->tm_sec,
                                                 (int) (tv.tv_usec));
                                time_buffer[sizeof(time_buffer) - 1 ] = '\0';
                        }
                }
#endif
        }
        if (flags & MYSQLND_DEBUG_DUMP_FILE) {
                snprintf(file_buffer, sizeof(file_buffer) - 1, "%14s: ", file);
                file_buffer[sizeof(file_buffer) - 1 ] = '\0';
        }
        if (flags & MYSQLND_DEBUG_DUMP_LINE) {
                snprintf(line_buffer, sizeof(line_buffer) - 1, "%5u: ", line);
                line_buffer[sizeof(line_buffer) - 1 ] = '\0';
        }
        if (flags & MYSQLND_DEBUG_DUMP_LEVEL) {
                snprintf(level_buffer, sizeof(level_buffer) - 1, "%4u: ", level);
                level_buffer[sizeof(level_buffer) - 1 ] = '\0';
        }

        va_start(args, format);
        mnd_vsprintf(&buffer, 0, format, args);
        va_end(args);

        message_line_len = mnd_sprintf(&message_line, 0, "%s%s%s%s%s%s%s%s\n",
                                                                flags & MYSQLND_DEBUG_DUMP_PID? pid_buffer:"",
                                                                flags & MYSQLND_DEBUG_DUMP_TIME? time_buffer:"",
                                                                flags & MYSQLND_DEBUG_DUMP_FILE? file_buffer:"",
                                                                flags & MYSQLND_DEBUG_DUMP_LINE? line_buffer:"",
                                                                flags & MYSQLND_DEBUG_DUMP_LEVEL? level_buffer:"",
                                                                pipe_buffer, type? type:"", buffer);
        mnd_sprintf_free(buffer);
        ret = php_stream_write(self->stream, message_line, message_line_len)? PASS:FAIL;
        mnd_sprintf_free(message_line);

        if (flags & MYSQLND_DEBUG_FLUSH) {
                self->m->close(self);
                self->m->open(self, TRUE);
        }
        return ret;
}
/* }}} */


/* FALSE - The DBG_ calls won't be traced, TRUE - will be traced */
/* {{{ mysqlnd_debug::func_enter */
static zend_bool
MYSQLND_METHOD(mysqlnd_debug, func_enter)(MYSQLND_DEBUG * self,
                                                                                  unsigned int line, const char * const file,
                                                                                  const char * const func_name, unsigned int func_name_len)
{
        if ((self->flags & MYSQLND_DEBUG_DUMP_TRACE) == 0 || self->file_name == NULL) {
                return FALSE;
        }
        if ((uint) zend_stack_count(&self->call_stack) >= self->nest_level_limit) {
                return FALSE;
        }

        if ((self->flags & MYSQLND_DEBUG_TRACE_MEMORY_CALLS) == 0 && self->skip_functions) {
                const char ** p = self->skip_functions;
                while (*p) {
                        if (*p == func_name) {
                                zend_stack_push(&self->call_stack, "", sizeof(""));
#ifndef MYSQLND_PROFILING_DISABLED
                                if (self->flags & MYSQLND_DEBUG_PROFILE_CALLS) {
                                        uint64_t some_time = 0;
                                        zend_stack_push(&self->call_time_stack, &some_time, sizeof(some_time));
                                }
#endif
                                return FALSE;
                        }
                        p++;
                }
        }

        zend_stack_push(&self->call_stack, func_name, func_name_len + 1);
#ifndef MYSQLND_PROFILING_DISABLED
        if (self->flags & MYSQLND_DEBUG_PROFILE_CALLS) {
                uint64_t some_time = 0;
                zend_stack_push(&self->call_time_stack, &some_time, sizeof(some_time));
        }
#endif

        if (zend_hash_num_elements(&self->not_filtered_functions) &&
                0 == zend_hash_exists(&self->not_filtered_functions, func_name, strlen(func_name) + 1))
        {
                return FALSE;
        }

        self->m->log_va(self, line, file, zend_stack_count(&self->call_stack) - 1, NULL, ">%s", func_name);
        return TRUE;
}
/* }}} */

#ifndef MYSQLND_PROFILING_DISABLED
struct st_mysqlnd_dbg_function_profile {
        uint64_t calls;
        uint64_t min_own;
        uint64_t max_own;
        uint64_t avg_own;
        uint64_t own_underporm_calls;
        uint64_t min_in_calls;
        uint64_t max_in_calls;
        uint64_t avg_in_calls;
        uint64_t in_calls_underporm_calls;
        uint64_t min_total;
        uint64_t max_total;
        uint64_t avg_total;     
        uint64_t total_underporm_calls;
};
#define PROFILE_UNDERPERFORM_THRESHOLD 10
#endif

/* {{{ mysqlnd_debug::func_leave */
static enum_func_status
MYSQLND_METHOD(mysqlnd_debug, func_leave)(MYSQLND_DEBUG * self, unsigned int line, const char * const file, uint64_t call_time)
{
        char *func_name;
        uint64_t * parent_non_own_time_ptr = NULL, * mine_non_own_time_ptr = NULL;
        uint64_t mine_non_own_time = 0;
        zend_bool profile_calls = self->flags & MYSQLND_DEBUG_PROFILE_CALLS? TRUE:FALSE;

        if ((self->flags & MYSQLND_DEBUG_DUMP_TRACE) == 0 || self->file_name == NULL) {
                return PASS;
        }
        if ((uint) zend_stack_count(&self->call_stack) >= self->nest_level_limit) {
                return PASS;
        }

        zend_stack_top(&self->call_stack, (void **)&func_name);

#ifndef MYSQLND_PROFILING_DISABLED
        if (profile_calls) {
                zend_stack_top(&self->call_time_stack, (void **)&mine_non_own_time_ptr);
                mine_non_own_time = *mine_non_own_time_ptr;
                zend_stack_del_top(&self->call_time_stack); /* callee - removing ourselves */
        }
#endif

        if (func_name[0] == '\0') {
                ; /* don't log that function */
        } else if (!zend_hash_num_elements(&self->not_filtered_functions) ||
                           1 == zend_hash_exists(&self->not_filtered_functions, func_name, strlen(func_name) + 1))
        {
#ifndef MYSQLND_PROFILING_DISABLED
                if (FALSE == profile_calls) {
#endif
                        self->m->log_va(self, line, file, zend_stack_count(&self->call_stack) - 1, NULL, "<%s", func_name);

#ifndef MYSQLND_PROFILING_DISABLED
                } else {
                        struct st_mysqlnd_dbg_function_profile f_profile_stack = {0};
                        struct st_mysqlnd_dbg_function_profile * f_profile = NULL;
                        uint64_t own_time = call_time - mine_non_own_time;
                        uint func_name_len = strlen(func_name);

                        self->m->log_va(self, line, file, zend_stack_count(&self->call_stack) - 1, NULL, "<%s (total=%u own=%u in_calls=%u)",
                                                func_name, (unsigned int) call_time, (unsigned int) own_time, (unsigned int) mine_non_own_time
                                        );

                        if (SUCCESS == zend_hash_find(&self->function_profiles, func_name, func_name_len + 1, (void **) &f_profile)) {
                                /* found */
                                        if (f_profile) {
                                        if (mine_non_own_time < f_profile->min_in_calls) {
                                                f_profile->min_in_calls = mine_non_own_time;
                                        } else if (mine_non_own_time > f_profile->max_in_calls) {
                                                f_profile->max_in_calls = mine_non_own_time;
                                        }
                                        f_profile->avg_in_calls = (f_profile->avg_in_calls * f_profile->calls + mine_non_own_time) / (f_profile->calls + 1);

                                        if (own_time < f_profile->min_own) {
                                                f_profile->min_own = own_time;
                                        } else if (own_time > f_profile->max_own) {
                                                f_profile->max_own = own_time;
                                        }
                                        f_profile->avg_own = (f_profile->avg_own * f_profile->calls + own_time) / (f_profile->calls + 1);

                                        if (call_time < f_profile->min_total) {
                                                f_profile->min_total = call_time;
                                        } else if (call_time > f_profile->max_total) {
                                                f_profile->max_total = call_time;
                                        }
                                        f_profile->avg_total = (f_profile->avg_total * f_profile->calls + call_time) / (f_profile->calls + 1);

                                        ++f_profile->calls;
                                        if (f_profile->calls > PROFILE_UNDERPERFORM_THRESHOLD) {
                                                if (f_profile->avg_in_calls < mine_non_own_time) {
                                                        f_profile->in_calls_underporm_calls++;
                                                }
                                                if (f_profile->avg_own < own_time) {
                                                        f_profile->own_underporm_calls++;
                                                }
                                                if (f_profile->avg_total < call_time) {
                                                        f_profile->total_underporm_calls++;
                                                }
                                        }
                                }
                        } else {
                                /* add */
                                f_profile = &f_profile_stack;
                                f_profile->min_in_calls = f_profile->max_in_calls = f_profile->avg_in_calls = mine_non_own_time;
                                f_profile->min_total = f_profile->max_total = f_profile->avg_total = call_time;
                                f_profile->min_own = f_profile->max_own = f_profile->avg_own = own_time;
                                f_profile->calls = 1;
                                zend_hash_add(&self->function_profiles, func_name, func_name_len+1, f_profile, sizeof(struct st_mysqlnd_dbg_function_profile), NULL);
                        }
                        if ((uint) zend_stack_count(&self->call_time_stack)) {
                                uint64_t parent_non_own_time = 0;

                                zend_stack_top(&self->call_time_stack, (void **)&parent_non_own_time_ptr);
                                parent_non_own_time = *parent_non_own_time_ptr;
                                parent_non_own_time += call_time;
                                zend_stack_del_top(&self->call_time_stack); /* the caller */
                                zend_stack_push(&self->call_time_stack, &parent_non_own_time, sizeof(parent_non_own_time)); /* add back the caller */
                        }
                }
#endif
        }

        return zend_stack_del_top(&self->call_stack) == SUCCESS? PASS:FAIL;
}
/* }}} */


/* {{{ mysqlnd_debug::close */
static enum_func_status
MYSQLND_METHOD(mysqlnd_debug, close)(MYSQLND_DEBUG * self)
{
        MYSQLND_ZTS(self);
        if (self->stream) {
#ifndef MYSQLND_PROFILING_DISABLED
                if (!(self->flags & MYSQLND_DEBUG_FLUSH) && (self->flags & MYSQLND_DEBUG_PROFILE_CALLS)) {
                        struct st_mysqlnd_dbg_function_profile * f_profile;
                        HashPosition pos_values;

                        self->m->log_va(self, __LINE__, __FILE__, 0, "info : ", 
                                        "number of functions: %d", zend_hash_num_elements(&self->function_profiles));
                        zend_hash_internal_pointer_reset_ex(&self->function_profiles, &pos_values);
                        while (zend_hash_get_current_data_ex(&self->function_profiles, (void **) &f_profile, &pos_values) == SUCCESS) {
                                char    *string_key = NULL;
                                uint    string_key_len;
                                ulong   num_key;

                                zend_hash_get_current_key_ex(&self->function_profiles, &string_key, &string_key_len, &num_key, 0, &pos_values);

                                self->m->log_va(self, __LINE__, __FILE__, -1, "info : ",
                                                "%-40s\tcalls=%5llu  own_slow=%5llu  in_calls_slow=%5llu  total_slow=%5llu"
                                                "   min_own=%5llu  max_own=%7llu  avg_own=%7llu   "
                                                "   min_in_calls=%5llu  max_in_calls=%7llu  avg_in_calls=%7llu"
                                                "   min_total=%5llu  max_total=%7llu  avg_total=%7llu"
                                                ,string_key
                                                ,(uint64_t) f_profile->calls
                                                ,(uint64_t) f_profile->own_underporm_calls
                                                ,(uint64_t) f_profile->in_calls_underporm_calls
                                                ,(uint64_t) f_profile->total_underporm_calls
                                                
                                                ,(uint64_t) f_profile->min_own
                                                ,(uint64_t) f_profile->max_own
                                                ,(uint64_t) f_profile->avg_own
                                                ,(uint64_t) f_profile->min_in_calls
                                                ,(uint64_t) f_profile->max_in_calls
                                                ,(uint64_t) f_profile->avg_in_calls
                                                ,(uint64_t) f_profile->min_total
                                                ,(uint64_t) f_profile->max_total
                                                ,(uint64_t) f_profile->avg_total
                                                );
                                zend_hash_move_forward_ex(&self->function_profiles, &pos_values);
                        }
                }
#endif  

                php_stream_free(self->stream, PHP_STREAM_FREE_CLOSE);
                self->stream = NULL;
        }
        /* no DBG_RETURN please */
        return PASS;
}
/* }}} */


/* {{{ mysqlnd_res_meta::free */
static enum_func_status
MYSQLND_METHOD(mysqlnd_debug, free)(MYSQLND_DEBUG * self)
{
        if (self->file_name && self->file_name != mysqlnd_debug_default_trace_file) {
                efree(self->file_name);
                self->file_name = NULL;
        }
        zend_stack_destroy(&self->call_stack);
        zend_stack_destroy(&self->call_time_stack);
        zend_hash_destroy(&self->not_filtered_functions);
        zend_hash_destroy(&self->function_profiles);
        free(self);
        return PASS;
}
/* }}} */

enum mysqlnd_debug_parser_state
{
        PARSER_WAIT_MODIFIER,
        PARSER_WAIT_COLON,
        PARSER_WAIT_VALUE
};


/* {{{ mysqlnd_res_meta::set_mode */
static void
MYSQLND_METHOD(mysqlnd_debug, set_mode)(MYSQLND_DEBUG * self, const char * const mode)
{
        unsigned int mode_len, i;
        enum mysqlnd_debug_parser_state state = PARSER_WAIT_MODIFIER;

        mode_len = mode? strlen(mode) : 0;

        self->flags = 0;
        self->nest_level_limit = 0;
        if (self->file_name && self->file_name != mysqlnd_debug_default_trace_file) {
                efree(self->file_name);
                self->file_name = NULL;
        }
        if (zend_hash_num_elements(&self->not_filtered_functions)) {
                zend_hash_destroy(&self->not_filtered_functions);
                zend_hash_init(&self->not_filtered_functions, 0, NULL, NULL, 0);
        }

        for (i = 0; i < mode_len; i++) {
                switch (mode[i]) {
                        case 'O':
                        case 'A':
                                self->flags |= MYSQLND_DEBUG_FLUSH;
                        case 'a':
                        case 'o':
                                if (mode[i] == 'a' || mode[i] == 'A') {
                                        self->flags |= MYSQLND_DEBUG_APPEND;
                                }
                                if (i + 1 < mode_len && mode[i+1] == ',') {
                                        unsigned int j = i + 2;
#ifdef PHP_WIN32
                                        if (i+4 < mode_len && mode[i+3] == ':' && (mode[i+4] == '\\' || mode[i+5] == '/')) {
                                                j = i + 5;
                                        }
#endif
                                        while (j < mode_len) {
                                                if (mode[j] == ':') {
                                                        break;
                                                }
                                                j++;
                                        }
                                        if (j > i + 2) {
                                                self->file_name = estrndup(mode + i + 2, j - i - 2);
                                        }
                                        i = j;
                                } else {
                                        if (!self->file_name)
                                                self->file_name = (char *) mysqlnd_debug_default_trace_file;
                                }
                                state = PARSER_WAIT_COLON;
                                break;
                        case ':':
#if 0
                                if (state != PARSER_WAIT_COLON) {
                                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Consecutive semicolons at position %u", i);
                                }
#endif
                                state = PARSER_WAIT_MODIFIER;
                                break;
                        case 'f': /* limit output to these functions */
                                if (i + 1 < mode_len && mode[i+1] == ',') {
                                        unsigned int j = i + 2;
                                        i++;
                                        while (j < mode_len) {
                                                if (mode[j] == ':') {
                                                        /* function names with :: */
                                                        if ((j + 1 < mode_len) && mode[j+1] == ':') {
                                                                j += 2;
                                                                continue;
                                                        }
                                                }
                                                if (mode[j] == ',' || mode[j] == ':') {
                                                        if (j > i + 2) {
                                                                char func_name[1024];
                                                                unsigned int func_name_len = MIN(sizeof(func_name) - 1, j - i - 1);
                                                                memcpy(func_name, mode + i + 1, func_name_len);
                                                                func_name[func_name_len] = '\0'; 

                                                                zend_hash_add_empty_element(&self->not_filtered_functions,
                                                                                                                        func_name, func_name_len + 1);
                                                                i = j;
                                                        }
                                                        if (mode[j] == ':') {
                                                                break;
                                                        }
                                                }
                                                j++;
                                        }
                                        i = j;
                                } else {
#if 0
                                        php_error_docref(NULL TSRMLS_CC, E_WARNING,
                                                                         "Expected list of functions for '%c' found none", mode[i]);
#endif
                                }
                                state = PARSER_WAIT_COLON;
                                break;
                        case 'D':
                        case 'd':
                        case 'g':
                        case 'p':
                                /* unsupported */
                                if ((i + 1) < mode_len && mode[i+1] == ',') {
                                        i+= 2;
                                        while (i < mode_len) {
                                                if (mode[i] == ':') {
                                                        break;
                                                }
                                                i++;
                                        }
                                }
                                state = PARSER_WAIT_COLON;
                                break;
                        case 'F':
                                self->flags |= MYSQLND_DEBUG_DUMP_FILE;
                                state = PARSER_WAIT_COLON;
                                break;
                        case 'i':
                                self->flags |= MYSQLND_DEBUG_DUMP_PID;
                                state = PARSER_WAIT_COLON;
                                break;
                        case 'L':
                                self->flags |= MYSQLND_DEBUG_DUMP_LINE;
                                state = PARSER_WAIT_COLON;
                                break;
                        case 'n':
                                self->flags |= MYSQLND_DEBUG_DUMP_LEVEL;
                                state = PARSER_WAIT_COLON;
                                break;
                        case 't':
                                if (mode[i+1] == ',') {
                                        unsigned int j = i + 2;
                                        while (j < mode_len) {
                                                if (mode[j] == ':') {
                                                        break;
                                                }
                                                j++;
                                        }
                                        if (j > i + 2) {
                                                char *value_str = estrndup(mode + i + 2, j - i - 2);
                                                self->nest_level_limit = atoi(value_str);
                                                efree(value_str);
                                        }
                                        i = j;
                                } else {
                                        self->nest_level_limit = 200; /* default value for FF DBUG */
                                }
                                self->flags |= MYSQLND_DEBUG_DUMP_TRACE;
                                state = PARSER_WAIT_COLON;
                                break;
                        case 'T':
                                self->flags |= MYSQLND_DEBUG_DUMP_TIME;
                                state = PARSER_WAIT_COLON;
                                break;
                        case 'N':
                        case 'P':
                        case 'r':
                        case 'S':
                                state = PARSER_WAIT_COLON;
                                break;
                        case 'm': /* mysqlnd extension - trace memory functions */
                                self->flags |= MYSQLND_DEBUG_TRACE_MEMORY_CALLS;
                                state = PARSER_WAIT_COLON;
                                break;
                        case 'x': /* mysqlnd extension - profile calls */
                                self->flags |= MYSQLND_DEBUG_PROFILE_CALLS;
                                state = PARSER_WAIT_COLON;
                                break;                          
                        default:
                                if (state == PARSER_WAIT_MODIFIER) {
#if 0
                                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unrecognized format '%c'", mode[i]);
#endif
                                        if (i+1 < mode_len && mode[i+1] == ',') {
                                                i+= 2;
                                                while (i < mode_len) {
                                                        if (mode[i] == ':') {
                                                                break;
                                                        }
                                                        i++;
                                                }
                                        }
                                        state = PARSER_WAIT_COLON;
                                } else if (state == PARSER_WAIT_COLON) {
#if 0
                                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Colon expected, '%c' found", mode[i]);
#endif
                                }
                                break;
                }
        }
}
/* }}} */

MYSQLND_CLASS_METHODS_START(mysqlnd_debug)
        MYSQLND_METHOD(mysqlnd_debug, open),
        MYSQLND_METHOD(mysqlnd_debug, set_mode),
        MYSQLND_METHOD(mysqlnd_debug, log),
        MYSQLND_METHOD(mysqlnd_debug, log_va),
        MYSQLND_METHOD(mysqlnd_debug, func_enter),
        MYSQLND_METHOD(mysqlnd_debug, func_leave),
        MYSQLND_METHOD(mysqlnd_debug, close),
        MYSQLND_METHOD(mysqlnd_debug, free),
MYSQLND_CLASS_METHODS_END;


/* {{{ mysqlnd_debug_init */
PHPAPI MYSQLND_DEBUG *
mysqlnd_debug_init(const char * skip_functions[] TSRMLS_DC)
{
        MYSQLND_DEBUG *ret = calloc(1, sizeof(MYSQLND_DEBUG));
#ifdef ZTS
        ret->TSRMLS_C = TSRMLS_C;
#endif
        ret->nest_level_limit = 0;
        ret->pid = getpid();
        zend_stack_init(&ret->call_stack);
        zend_stack_init(&ret->call_time_stack);
        zend_hash_init(&ret->not_filtered_functions, 0, NULL, NULL, 0);
        zend_hash_init(&ret->function_profiles, 0, NULL, NULL, 0);

        ret->m = & mysqlnd_mysqlnd_debug_methods;
        ret->skip_functions = skip_functions;

        return ret;
}
/* }}} */


/* {{{ _mysqlnd_debug */
PHPAPI void _mysqlnd_debug(const char * mode TSRMLS_DC)
{
#if PHP_DEBUG
        MYSQLND_DEBUG * dbg = MYSQLND_G(dbg);
        if (!dbg) {
                struct st_mysqlnd_plugin_trace_log * trace_log_plugin = mysqlnd_plugin_find("debug_trace");
                if (trace_log_plugin) {
                        dbg = trace_log_plugin->methods.trace_instance_init(mysqlnd_debug_std_no_trace_funcs TSRMLS_CC);
                        if (!dbg) {
                                return;
                        }
                        MYSQLND_G(dbg) = dbg;
                }
        }
        if (dbg) {
                dbg->m->close(dbg);
                dbg->m->set_mode(dbg, mode);
                while (zend_stack_count(&dbg->call_stack)) {
                        zend_stack_del_top(&dbg->call_stack);
                }
                while (zend_stack_count(&dbg->call_time_stack)) {
                        zend_stack_del_top(&dbg->call_time_stack);
                }
        }
#endif
}
/* }}} */


static struct st_mysqlnd_plugin_trace_log mysqlnd_plugin_trace_log_plugin =
{
        {
                MYSQLND_PLUGIN_API_VERSION,
                "debug_trace",
                MYSQLND_VERSION_ID,
                MYSQLND_VERSION,
                "PHP License 3.01",
                "Andrey Hristov <andrey@mysql.com>,  Ulf Wendel <uwendel@mysql.com>, Georg Richter <georg@mysql.com>",
                {
                        NULL, /* no statistics , will be filled later if there are some */
                        NULL, /* no statistics */
                },
                {
                        NULL /* plugin shutdown */
                }
        },
        {/* methods */
                mysqlnd_debug_init,
                mysqlnd_get_backtrace
        }
};


/* {{{ mysqlnd_debug_trace_plugin_register */
void
mysqlnd_debug_trace_plugin_register(TSRMLS_D)
{
        mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_plugin_trace_log_plugin TSRMLS_CC);
}
/* }}} */


/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: noet sw=4 ts=4 fdm=marker
 * vim<600: noet sw=4 ts=4
 */

/* [<][>][^][v][top][bottom][index][help] */