1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 7 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2006-2017 The PHP Group |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 | Authors: Andrey Hristov <andrey@php.net> |
16 | Ulf Wendel <uw@php.net> |
17 | Georg Richter <georg@php.net> |
18 +----------------------------------------------------------------------+
19 */
20
21 #include "php.h"
22 #include "mysqlnd.h"
23 #include "mysqlnd_priv.h"
24 #include "mysqlnd_debug.h"
25
26 static const char * const mysqlnd_debug_default_trace_file = "/tmp/mysqlnd.trace";
27 static const char * const mysqlnd_debug_empty_string = "";
28
29
30 /* {{{ mysqlnd_debug::open */
31 static enum_func_status
MYSQLND_METHOD(mysqlnd_debug,open)32 MYSQLND_METHOD(mysqlnd_debug, open)(MYSQLND_DEBUG * self, zend_bool reopen)
33 {
34 if (!self->file_name) {
35 return FAIL;
36 }
37
38 self->stream = php_stream_open_wrapper(self->file_name,
39 reopen == TRUE || self->flags & MYSQLND_DEBUG_APPEND? "ab":"wb",
40 REPORT_ERRORS, NULL);
41 return self->stream? PASS:FAIL;
42 }
43 /* }}} */
44
45
46 /* {{{ mysqlnd_debug::log */
47 static enum_func_status
MYSQLND_METHOD(mysqlnd_debug,log)48 MYSQLND_METHOD(mysqlnd_debug, log)(MYSQLND_DEBUG * self,
49 unsigned int line, const char * const file,
50 unsigned int level, const char * type, const char * message)
51 {
52 char pipe_buffer[512];
53 enum_func_status ret;
54 int i;
55 char * message_line;
56 unsigned int message_line_len;
57 unsigned int flags = self->flags;
58 char pid_buffer[10], time_buffer[30], file_buffer[200],
59 line_buffer[6], level_buffer[7];
60
61 if (!self->stream && FAIL == self->m->open(self, FALSE)) {
62 return FAIL;
63 }
64
65 if (level == -1) {
66 level = zend_stack_count(&self->call_stack);
67 }
68 i = MIN(level, sizeof(pipe_buffer) / 2 - 1);
69 pipe_buffer[i*2] = '\0';
70 for (;i > 0;i--) {
71 pipe_buffer[i*2 - 1] = ' ';
72 pipe_buffer[i*2 - 2] = '|';
73 }
74
75
76 if (flags & MYSQLND_DEBUG_DUMP_PID) {
77 snprintf(pid_buffer, sizeof(pid_buffer) - 1, "%5u: ", self->pid);
78 pid_buffer[sizeof(pid_buffer) - 1 ] = '\0';
79 }
80 if (flags & MYSQLND_DEBUG_DUMP_TIME) {
81 /* The following from FF's DBUG library, which is in the public domain */
82 #if defined(PHP_WIN32)
83 /* FIXME This doesn't give microseconds as in Unix case, and the resolution is
84 in system ticks, 10 ms intervals. See my_getsystime.c for high res */
85 SYSTEMTIME loc_t;
86 GetLocalTime(&loc_t);
87 snprintf(time_buffer, sizeof(time_buffer) - 1,
88 /* "%04d-%02d-%02d " */
89 "%02d:%02d:%02d.%06d ",
90 /*tm_p->tm_year + 1900, tm_p->tm_mon + 1, tm_p->tm_mday,*/
91 loc_t.wHour, loc_t.wMinute, loc_t.wSecond, loc_t.wMilliseconds);
92 time_buffer[sizeof(time_buffer) - 1 ] = '\0';
93 #else
94 struct timeval tv;
95 struct tm *tm_p;
96 if (gettimeofday(&tv, NULL) != -1) {
97 if ((tm_p= localtime((const time_t *)&tv.tv_sec))) {
98 snprintf(time_buffer, sizeof(time_buffer) - 1,
99 /* "%04d-%02d-%02d " */
100 "%02d:%02d:%02d.%06d ",
101 /*tm_p->tm_year + 1900, tm_p->tm_mon + 1, tm_p->tm_mday,*/
102 tm_p->tm_hour, tm_p->tm_min, tm_p->tm_sec,
103 (int) (tv.tv_usec));
104 time_buffer[sizeof(time_buffer) - 1 ] = '\0';
105 }
106 }
107 #endif
108 }
109 if (flags & MYSQLND_DEBUG_DUMP_FILE) {
110 snprintf(file_buffer, sizeof(file_buffer) - 1, "%14s: ", file);
111 file_buffer[sizeof(file_buffer) - 1 ] = '\0';
112 }
113 if (flags & MYSQLND_DEBUG_DUMP_LINE) {
114 snprintf(line_buffer, sizeof(line_buffer) - 1, "%5u: ", line);
115 line_buffer[sizeof(line_buffer) - 1 ] = '\0';
116 }
117 if (flags & MYSQLND_DEBUG_DUMP_LEVEL) {
118 snprintf(level_buffer, sizeof(level_buffer) - 1, "%4u: ", level);
119 level_buffer[sizeof(level_buffer) - 1 ] = '\0';
120 }
121
122 message_line_len = mnd_sprintf(&message_line, 0, "%s%s%s%s%s%s%s%s\n",
123 flags & MYSQLND_DEBUG_DUMP_PID? pid_buffer:"",
124 flags & MYSQLND_DEBUG_DUMP_TIME? time_buffer:"",
125 flags & MYSQLND_DEBUG_DUMP_FILE? file_buffer:"",
126 flags & MYSQLND_DEBUG_DUMP_LINE? line_buffer:"",
127 flags & MYSQLND_DEBUG_DUMP_LEVEL? level_buffer:"",
128 pipe_buffer, type? type:"", message);
129
130 ret = php_stream_write(self->stream, message_line, message_line_len)? PASS:FAIL;
131 mnd_sprintf_free(message_line);
132 if (flags & MYSQLND_DEBUG_FLUSH) {
133 self->m->close(self);
134 self->m->open(self, TRUE);
135 }
136 return ret;
137 }
138 /* }}} */
139
140
141 /* {{{ mysqlnd_debug::log_va */
142 static enum_func_status
MYSQLND_METHOD(mysqlnd_debug,log_va)143 MYSQLND_METHOD(mysqlnd_debug, log_va)(MYSQLND_DEBUG *self,
144 unsigned int line, const char * const file,
145 unsigned int level, const char * type,
146 const char *format, ...)
147 {
148 char pipe_buffer[512];
149 int i;
150 enum_func_status ret;
151 char * message_line, *buffer;
152 unsigned int message_line_len;
153 va_list args;
154 unsigned int flags = self->flags;
155 char pid_buffer[10], time_buffer[30], file_buffer[200],
156 line_buffer[6], level_buffer[7];
157
158 if (!self->stream && FAIL == self->m->open(self, FALSE)) {
159 return FAIL;
160 }
161
162 if (level == -1) {
163 level = zend_stack_count(&self->call_stack);
164 }
165 i = MIN(level, sizeof(pipe_buffer) / 2 - 1);
166 pipe_buffer[i*2] = '\0';
167 for (;i > 0;i--) {
168 pipe_buffer[i*2 - 1] = ' ';
169 pipe_buffer[i*2 - 2] = '|';
170 }
171
172
173 if (flags & MYSQLND_DEBUG_DUMP_PID) {
174 snprintf(pid_buffer, sizeof(pid_buffer) - 1, "%5u: ", self->pid);
175 pid_buffer[sizeof(pid_buffer) - 1 ] = '\0';
176 }
177 if (flags & MYSQLND_DEBUG_DUMP_TIME) {
178 /* The following from FF's DBUG library, which is in the public domain */
179 #if defined(PHP_WIN32)
180 /* FIXME This doesn't give microseconds as in Unix case, and the resolution is
181 in system ticks, 10 ms intervals. See my_getsystime.c for high res */
182 SYSTEMTIME loc_t;
183 GetLocalTime(&loc_t);
184 snprintf(time_buffer, sizeof(time_buffer) - 1,
185 /* "%04d-%02d-%02d " */
186 "%02d:%02d:%02d.%06d ",
187 /*tm_p->tm_year + 1900, tm_p->tm_mon + 1, tm_p->tm_mday,*/
188 loc_t.wHour, loc_t.wMinute, loc_t.wSecond, loc_t.wMilliseconds);
189 time_buffer[sizeof(time_buffer) - 1 ] = '\0';
190 #else
191 struct timeval tv;
192 struct tm *tm_p;
193 if (gettimeofday(&tv, NULL) != -1) {
194 if ((tm_p= localtime((const time_t *)&tv.tv_sec))) {
195 snprintf(time_buffer, sizeof(time_buffer) - 1,
196 /* "%04d-%02d-%02d " */
197 "%02d:%02d:%02d.%06d ",
198 /*tm_p->tm_year + 1900, tm_p->tm_mon + 1, tm_p->tm_mday,*/
199 tm_p->tm_hour, tm_p->tm_min, tm_p->tm_sec,
200 (int) (tv.tv_usec));
201 time_buffer[sizeof(time_buffer) - 1 ] = '\0';
202 }
203 }
204 #endif
205 }
206 if (flags & MYSQLND_DEBUG_DUMP_FILE) {
207 snprintf(file_buffer, sizeof(file_buffer) - 1, "%14s: ", file);
208 file_buffer[sizeof(file_buffer) - 1 ] = '\0';
209 }
210 if (flags & MYSQLND_DEBUG_DUMP_LINE) {
211 snprintf(line_buffer, sizeof(line_buffer) - 1, "%5u: ", line);
212 line_buffer[sizeof(line_buffer) - 1 ] = '\0';
213 }
214 if (flags & MYSQLND_DEBUG_DUMP_LEVEL) {
215 snprintf(level_buffer, sizeof(level_buffer) - 1, "%4u: ", level);
216 level_buffer[sizeof(level_buffer) - 1 ] = '\0';
217 }
218
219 va_start(args, format);
220 mnd_vsprintf(&buffer, 0, format, args);
221 va_end(args);
222
223 message_line_len = mnd_sprintf(&message_line, 0, "%s%s%s%s%s%s%s%s\n",
224 flags & MYSQLND_DEBUG_DUMP_PID? pid_buffer:"",
225 flags & MYSQLND_DEBUG_DUMP_TIME? time_buffer:"",
226 flags & MYSQLND_DEBUG_DUMP_FILE? file_buffer:"",
227 flags & MYSQLND_DEBUG_DUMP_LINE? line_buffer:"",
228 flags & MYSQLND_DEBUG_DUMP_LEVEL? level_buffer:"",
229 pipe_buffer, type? type:"", buffer);
230 mnd_sprintf_free(buffer);
231 ret = php_stream_write(self->stream, message_line, message_line_len)? PASS:FAIL;
232 mnd_sprintf_free(message_line);
233
234 if (flags & MYSQLND_DEBUG_FLUSH) {
235 self->m->close(self);
236 self->m->open(self, TRUE);
237 }
238 return ret;
239 }
240 /* }}} */
241
242
243 /* FALSE - The DBG_ calls won't be traced, TRUE - will be traced */
244 /* {{{ mysqlnd_debug::func_enter */
245 static zend_bool
MYSQLND_METHOD(mysqlnd_debug,func_enter)246 MYSQLND_METHOD(mysqlnd_debug, func_enter)(MYSQLND_DEBUG * self,
247 unsigned int line, const char * const file,
248 const char * const func_name, unsigned int func_name_len)
249 {
250 if ((self->flags & MYSQLND_DEBUG_DUMP_TRACE) == 0 || self->file_name == NULL) {
251 return FALSE;
252 }
253 if ((uint) zend_stack_count(&self->call_stack) >= self->nest_level_limit) {
254 return FALSE;
255 }
256
257 if ((self->flags & MYSQLND_DEBUG_TRACE_MEMORY_CALLS) == 0 && self->skip_functions) {
258 const char ** p = self->skip_functions;
259 while (*p) {
260 if (*p == func_name) {
261 zend_stack_push(&self->call_stack, &mysqlnd_debug_empty_string);
262 #ifndef MYSQLND_PROFILING_DISABLED
263 if (self->flags & MYSQLND_DEBUG_PROFILE_CALLS) {
264 uint64_t some_time = 0;
265 zend_stack_push(&self->call_time_stack, &some_time);
266 }
267 #endif
268 return FALSE;
269 }
270 p++;
271 }
272 }
273
274 zend_stack_push(&self->call_stack, &func_name);
275 #ifndef MYSQLND_PROFILING_DISABLED
276 if (self->flags & MYSQLND_DEBUG_PROFILE_CALLS) {
277 uint64_t some_time = 0;
278 zend_stack_push(&self->call_time_stack, &some_time);
279 }
280 #endif
281
282 if (zend_hash_num_elements(&self->not_filtered_functions) &&
283 0 == zend_hash_str_exists(&self->not_filtered_functions, func_name, strlen(func_name)))
284 {
285 return FALSE;
286 }
287
288 self->m->log_va(self, line, file, zend_stack_count(&self->call_stack) - 1, NULL, ">%s", func_name);
289 return TRUE;
290 }
291 /* }}} */
292
293 #ifndef MYSQLND_PROFILING_DISABLED
294 struct st_mysqlnd_dbg_function_profile {
295 uint64_t calls;
296 uint64_t min_own;
297 uint64_t max_own;
298 uint64_t avg_own;
299 uint64_t own_underporm_calls;
300 uint64_t min_in_calls;
301 uint64_t max_in_calls;
302 uint64_t avg_in_calls;
303 uint64_t in_calls_underporm_calls;
304 uint64_t min_total;
305 uint64_t max_total;
306 uint64_t avg_total;
307 uint64_t total_underporm_calls;
308 };
309 #define PROFILE_UNDERPERFORM_THRESHOLD 10
310 #endif
311
312 /* {{{ mysqlnd_debug::func_leave */
313 static enum_func_status
MYSQLND_METHOD(mysqlnd_debug,func_leave)314 MYSQLND_METHOD(mysqlnd_debug, func_leave)(MYSQLND_DEBUG * self, unsigned int line, const char * const file, uint64_t call_time)
315 {
316 char **func_name;
317 uint64_t * parent_non_own_time_ptr = NULL, * mine_non_own_time_ptr = NULL;
318 uint64_t mine_non_own_time = 0;
319 zend_bool profile_calls = self->flags & MYSQLND_DEBUG_PROFILE_CALLS? TRUE:FALSE;
320
321 if ((self->flags & MYSQLND_DEBUG_DUMP_TRACE) == 0 || self->file_name == NULL) {
322 return PASS;
323 }
324 if ((uint) zend_stack_count(&self->call_stack) >= self->nest_level_limit) {
325 return PASS;
326 }
327
328 func_name = zend_stack_top(&self->call_stack);
329
330 #ifndef MYSQLND_PROFILING_DISABLED
331 if (profile_calls) {
332 mine_non_own_time_ptr = zend_stack_top(&self->call_time_stack);
333 mine_non_own_time = *mine_non_own_time_ptr;
334 zend_stack_del_top(&self->call_time_stack); /* callee - removing ourselves */
335 }
336 #endif
337
338 if ((*func_name)[0] == '\0') {
339 ; /* don't log that function */
340 } else if (!zend_hash_num_elements(&self->not_filtered_functions) ||
341 1 == zend_hash_str_exists(&self->not_filtered_functions, (*func_name), strlen((*func_name))))
342 {
343 #ifndef MYSQLND_PROFILING_DISABLED
344 if (FALSE == profile_calls) {
345 #endif
346 self->m->log_va(self, line, file, zend_stack_count(&self->call_stack) - 1, NULL, "<%s", *func_name);
347
348 #ifndef MYSQLND_PROFILING_DISABLED
349 } else {
350 struct st_mysqlnd_dbg_function_profile f_profile_stack = {0};
351 struct st_mysqlnd_dbg_function_profile * f_profile = NULL;
352 uint64_t own_time = call_time - mine_non_own_time;
353 uint func_name_len = strlen(*func_name);
354
355 self->m->log_va(self, line, file, zend_stack_count(&self->call_stack) - 1, NULL, "<%s (total=%u own=%u in_calls=%u)",
356 *func_name, (unsigned int) call_time, (unsigned int) own_time, (unsigned int) mine_non_own_time
357 );
358
359 if ((f_profile = zend_hash_str_find_ptr(&self->function_profiles, *func_name, func_name_len)) != NULL) {
360 /* found */
361 if (f_profile) {
362 if (mine_non_own_time < f_profile->min_in_calls) {
363 f_profile->min_in_calls = mine_non_own_time;
364 } else if (mine_non_own_time > f_profile->max_in_calls) {
365 f_profile->max_in_calls = mine_non_own_time;
366 }
367 f_profile->avg_in_calls = (f_profile->avg_in_calls * f_profile->calls + mine_non_own_time) / (f_profile->calls + 1);
368
369 if (own_time < f_profile->min_own) {
370 f_profile->min_own = own_time;
371 } else if (own_time > f_profile->max_own) {
372 f_profile->max_own = own_time;
373 }
374 f_profile->avg_own = (f_profile->avg_own * f_profile->calls + own_time) / (f_profile->calls + 1);
375
376 if (call_time < f_profile->min_total) {
377 f_profile->min_total = call_time;
378 } else if (call_time > f_profile->max_total) {
379 f_profile->max_total = call_time;
380 }
381 f_profile->avg_total = (f_profile->avg_total * f_profile->calls + call_time) / (f_profile->calls + 1);
382
383 ++f_profile->calls;
384 if (f_profile->calls > PROFILE_UNDERPERFORM_THRESHOLD) {
385 if (f_profile->avg_in_calls < mine_non_own_time) {
386 f_profile->in_calls_underporm_calls++;
387 }
388 if (f_profile->avg_own < own_time) {
389 f_profile->own_underporm_calls++;
390 }
391 if (f_profile->avg_total < call_time) {
392 f_profile->total_underporm_calls++;
393 }
394 }
395 }
396 } else {
397 /* add */
398 f_profile = &f_profile_stack;
399 f_profile->min_in_calls = f_profile->max_in_calls = f_profile->avg_in_calls = mine_non_own_time;
400 f_profile->min_total = f_profile->max_total = f_profile->avg_total = call_time;
401 f_profile->min_own = f_profile->max_own = f_profile->avg_own = own_time;
402 f_profile->calls = 1;
403 zend_hash_str_add_mem(&self->function_profiles, *func_name, func_name_len, f_profile, sizeof(struct st_mysqlnd_dbg_function_profile));
404 }
405 if ((uint) zend_stack_count(&self->call_time_stack)) {
406 uint64_t parent_non_own_time = 0;
407
408 parent_non_own_time_ptr = zend_stack_top(&self->call_time_stack);
409 parent_non_own_time = *parent_non_own_time_ptr;
410 parent_non_own_time += call_time;
411 zend_stack_del_top(&self->call_time_stack); /* the caller */
412 zend_stack_push(&self->call_time_stack, &parent_non_own_time); /* add back the caller */
413 }
414 }
415 #endif
416 }
417
418 return zend_stack_del_top(&self->call_stack) == SUCCESS? PASS:FAIL;
419 }
420 /* }}} */
421
422
423 /* {{{ mysqlnd_debug::close */
424 static enum_func_status
MYSQLND_METHOD(mysqlnd_debug,close)425 MYSQLND_METHOD(mysqlnd_debug, close)(MYSQLND_DEBUG * self)
426 {
427 if (self->stream) {
428 #ifndef MYSQLND_PROFILING_DISABLED
429 if (!(self->flags & MYSQLND_DEBUG_FLUSH) && (self->flags & MYSQLND_DEBUG_PROFILE_CALLS)) {
430 struct st_mysqlnd_dbg_function_profile * f_profile;
431 zend_string *string_key = NULL;
432
433 self->m->log_va(self, __LINE__, __FILE__, 0, "info : ",
434 "number of functions: %d", zend_hash_num_elements(&self->function_profiles));
435 ZEND_HASH_FOREACH_STR_KEY_PTR(&self->function_profiles, string_key, f_profile) {
436 self->m->log_va(self, __LINE__, __FILE__, -1, "info : ",
437 "%-40s\tcalls=%5llu own_slow=%5llu in_calls_slow=%5llu total_slow=%5llu"
438 " min_own=%5llu max_own=%7llu avg_own=%7llu "
439 " min_in_calls=%5llu max_in_calls=%7llu avg_in_calls=%7llu"
440 " min_total=%5llu max_total=%7llu avg_total=%7llu"
441 ,ZSTR_VAL(string_key)
442 ,(uint64_t) f_profile->calls
443 ,(uint64_t) f_profile->own_underporm_calls
444 ,(uint64_t) f_profile->in_calls_underporm_calls
445 ,(uint64_t) f_profile->total_underporm_calls
446
447 ,(uint64_t) f_profile->min_own
448 ,(uint64_t) f_profile->max_own
449 ,(uint64_t) f_profile->avg_own
450 ,(uint64_t) f_profile->min_in_calls
451 ,(uint64_t) f_profile->max_in_calls
452 ,(uint64_t) f_profile->avg_in_calls
453 ,(uint64_t) f_profile->min_total
454 ,(uint64_t) f_profile->max_total
455 ,(uint64_t) f_profile->avg_total
456 );
457 } ZEND_HASH_FOREACH_END();
458 }
459 #endif
460
461 php_stream_close(self->stream);
462 self->stream = NULL;
463 }
464 /* no DBG_RETURN please */
465 return PASS;
466 }
467 /* }}} */
468
469
470 /* {{{ mysqlnd_res_meta::free */
471 static enum_func_status
MYSQLND_METHOD(mysqlnd_debug,free)472 MYSQLND_METHOD(mysqlnd_debug, free)(MYSQLND_DEBUG * self)
473 {
474 if (self->file_name && self->file_name != mysqlnd_debug_default_trace_file) {
475 efree(self->file_name);
476 self->file_name = NULL;
477 }
478 zend_stack_destroy(&self->call_stack);
479 zend_stack_destroy(&self->call_time_stack);
480 zend_hash_destroy(&self->not_filtered_functions);
481 zend_hash_destroy(&self->function_profiles);
482 free(self);
483 return PASS;
484 }
485 /* }}} */
486
487 enum mysqlnd_debug_parser_state
488 {
489 PARSER_WAIT_MODIFIER,
490 PARSER_WAIT_COLON,
491 PARSER_WAIT_VALUE
492 };
493
494
495 /* {{{ mysqlnd_res_meta::set_mode */
496 static void
MYSQLND_METHOD(mysqlnd_debug,set_mode)497 MYSQLND_METHOD(mysqlnd_debug, set_mode)(MYSQLND_DEBUG * self, const char * const mode)
498 {
499 unsigned int mode_len, i;
500 enum mysqlnd_debug_parser_state state = PARSER_WAIT_MODIFIER;
501
502 mode_len = mode? strlen(mode) : 0;
503
504 self->flags = 0;
505 self->nest_level_limit = 0;
506 if (self->file_name && self->file_name != mysqlnd_debug_default_trace_file) {
507 efree(self->file_name);
508 self->file_name = NULL;
509 }
510 if (zend_hash_num_elements(&self->not_filtered_functions)) {
511 zend_hash_destroy(&self->not_filtered_functions);
512 zend_hash_init(&self->not_filtered_functions, 0, NULL, NULL, 0);
513 }
514
515 for (i = 0; i < mode_len; i++) {
516 switch (mode[i]) {
517 case 'O':
518 case 'A':
519 self->flags |= MYSQLND_DEBUG_FLUSH;
520 case 'a':
521 case 'o':
522 if (mode[i] == 'a' || mode[i] == 'A') {
523 self->flags |= MYSQLND_DEBUG_APPEND;
524 }
525 if (i + 1 < mode_len && mode[i+1] == ',') {
526 unsigned int j = i + 2;
527 #ifdef PHP_WIN32
528 if (i+4 < mode_len && mode[i+3] == ':' && (mode[i+4] == '\\' || mode[i+5] == '/')) {
529 j = i + 5;
530 }
531 #endif
532 while (j < mode_len) {
533 if (mode[j] == ':') {
534 break;
535 }
536 j++;
537 }
538 if (j > i + 2) {
539 self->file_name = estrndup(mode + i + 2, j - i - 2);
540 }
541 i = j;
542 } else {
543 if (!self->file_name)
544 self->file_name = (char *) mysqlnd_debug_default_trace_file;
545 }
546 state = PARSER_WAIT_COLON;
547 break;
548 case ':':
549 #if 0
550 if (state != PARSER_WAIT_COLON) {
551 php_error_docref(NULL, E_WARNING, "Consecutive semicolons at position %u", i);
552 }
553 #endif
554 state = PARSER_WAIT_MODIFIER;
555 break;
556 case 'f': /* limit output to these functions */
557 if (i + 1 < mode_len && mode[i+1] == ',') {
558 unsigned int j = i + 2;
559 i++;
560 while (j < mode_len) {
561 if (mode[j] == ':') {
562 /* function names with :: */
563 if ((j + 1 < mode_len) && mode[j+1] == ':') {
564 j += 2;
565 continue;
566 }
567 }
568 if (mode[j] == ',' || mode[j] == ':') {
569 if (j > i + 2) {
570 char func_name[1024];
571 unsigned int func_name_len = MIN(sizeof(func_name) - 1, j - i - 1);
572 memcpy(func_name, mode + i + 1, func_name_len);
573 func_name[func_name_len] = '\0';
574
575 zend_hash_str_add_empty_element(&self->not_filtered_functions,
576 func_name, func_name_len);
577 i = j;
578 }
579 if (mode[j] == ':') {
580 break;
581 }
582 }
583 j++;
584 }
585 i = j;
586 } else {
587 #if 0
588 php_error_docref(NULL, E_WARNING,
589 "Expected list of functions for '%c' found none", mode[i]);
590 #endif
591 }
592 state = PARSER_WAIT_COLON;
593 break;
594 case 'D':
595 case 'd':
596 case 'g':
597 case 'p':
598 /* unsupported */
599 if ((i + 1) < mode_len && mode[i+1] == ',') {
600 i+= 2;
601 while (i < mode_len) {
602 if (mode[i] == ':') {
603 break;
604 }
605 i++;
606 }
607 }
608 state = PARSER_WAIT_COLON;
609 break;
610 case 'F':
611 self->flags |= MYSQLND_DEBUG_DUMP_FILE;
612 state = PARSER_WAIT_COLON;
613 break;
614 case 'i':
615 self->flags |= MYSQLND_DEBUG_DUMP_PID;
616 state = PARSER_WAIT_COLON;
617 break;
618 case 'L':
619 self->flags |= MYSQLND_DEBUG_DUMP_LINE;
620 state = PARSER_WAIT_COLON;
621 break;
622 case 'n':
623 self->flags |= MYSQLND_DEBUG_DUMP_LEVEL;
624 state = PARSER_WAIT_COLON;
625 break;
626 case 't':
627 if (mode[i+1] == ',') {
628 unsigned int j = i + 2;
629 while (j < mode_len) {
630 if (mode[j] == ':') {
631 break;
632 }
633 j++;
634 }
635 if (j > i + 2) {
636 char *value_str = estrndup(mode + i + 2, j - i - 2);
637 self->nest_level_limit = atoi(value_str);
638 efree(value_str);
639 }
640 i = j;
641 } else {
642 self->nest_level_limit = 200; /* default value for FF DBUG */
643 }
644 self->flags |= MYSQLND_DEBUG_DUMP_TRACE;
645 state = PARSER_WAIT_COLON;
646 break;
647 case 'T':
648 self->flags |= MYSQLND_DEBUG_DUMP_TIME;
649 state = PARSER_WAIT_COLON;
650 break;
651 case 'N':
652 case 'P':
653 case 'r':
654 case 'S':
655 state = PARSER_WAIT_COLON;
656 break;
657 case 'm': /* mysqlnd extension - trace memory functions */
658 self->flags |= MYSQLND_DEBUG_TRACE_MEMORY_CALLS;
659 state = PARSER_WAIT_COLON;
660 break;
661 case 'x': /* mysqlnd extension - profile calls */
662 self->flags |= MYSQLND_DEBUG_PROFILE_CALLS;
663 state = PARSER_WAIT_COLON;
664 break;
665 default:
666 if (state == PARSER_WAIT_MODIFIER) {
667 #if 0
668 php_error_docref(NULL, E_WARNING, "Unrecognized format '%c'", mode[i]);
669 #endif
670 if (i+1 < mode_len && mode[i+1] == ',') {
671 i+= 2;
672 while (i < mode_len) {
673 if (mode[i] == ':') {
674 break;
675 }
676 i++;
677 }
678 }
679 state = PARSER_WAIT_COLON;
680 } else if (state == PARSER_WAIT_COLON) {
681 #if 0
682 php_error_docref(NULL, E_WARNING, "Colon expected, '%c' found", mode[i]);
683 #endif
684 }
685 break;
686 }
687 }
688 }
689 /* }}} */
690
691 MYSQLND_CLASS_METHODS_START(mysqlnd_debug)
692 MYSQLND_METHOD(mysqlnd_debug, open),
693 MYSQLND_METHOD(mysqlnd_debug, set_mode),
694 MYSQLND_METHOD(mysqlnd_debug, log),
695 MYSQLND_METHOD(mysqlnd_debug, log_va),
696 MYSQLND_METHOD(mysqlnd_debug, func_enter),
697 MYSQLND_METHOD(mysqlnd_debug, func_leave),
698 MYSQLND_METHOD(mysqlnd_debug, close),
699 MYSQLND_METHOD(mysqlnd_debug, free),
700 MYSQLND_CLASS_METHODS_END;
701
702
703 /* {{{ mysqlnd_debug_init */
704 PHPAPI MYSQLND_DEBUG *
mysqlnd_debug_init(const char * skip_functions[])705 mysqlnd_debug_init(const char * skip_functions[])
706 {
707 MYSQLND_DEBUG *ret = calloc(1, sizeof(MYSQLND_DEBUG));
708
709 ret->nest_level_limit = 0;
710 ret->pid = getpid();
711 zend_stack_init(&ret->call_stack, sizeof(char *));
712 zend_stack_init(&ret->call_time_stack, sizeof(uint64_t));
713 zend_hash_init(&ret->not_filtered_functions, 0, NULL, NULL, 0);
714 zend_hash_init(&ret->function_profiles, 0, NULL, NULL, 0);
715
716 ret->m = & mysqlnd_mysqlnd_debug_methods;
717 ret->skip_functions = skip_functions;
718
719 return ret;
720 }
721 /* }}} */
722
723
724 /* {{{ mysqlnd_debug */
mysqlnd_debug(const char * mode)725 PHPAPI void mysqlnd_debug(const char * mode)
726 {
727 #if PHP_DEBUG
728 MYSQLND_DEBUG * dbg = MYSQLND_G(dbg);
729 if (!dbg) {
730 struct st_mysqlnd_plugin_trace_log * trace_log_plugin = mysqlnd_plugin_find("debug_trace");
731 if (trace_log_plugin) {
732 dbg = trace_log_plugin->methods.trace_instance_init(mysqlnd_debug_std_no_trace_funcs);
733 if (!dbg) {
734 return;
735 }
736 MYSQLND_G(dbg) = dbg;
737 }
738 }
739 if (dbg) {
740 dbg->m->close(dbg);
741 dbg->m->set_mode(dbg, mode);
742 while (zend_stack_count(&dbg->call_stack)) {
743 zend_stack_del_top(&dbg->call_stack);
744 }
745 while (zend_stack_count(&dbg->call_time_stack)) {
746 zend_stack_del_top(&dbg->call_time_stack);
747 }
748 }
749 #endif
750 }
751 /* }}} */
752
753
754 static struct st_mysqlnd_plugin_trace_log mysqlnd_plugin_trace_log_plugin =
755 {
756 {
757 MYSQLND_PLUGIN_API_VERSION,
758 "debug_trace",
759 MYSQLND_VERSION_ID,
760 PHP_MYSQLND_VERSION,
761 "PHP License 3.01",
762 "Andrey Hristov <andrey@php.net>, Ulf Wendel <uw@php.net>, Georg Richter <georg@php.net>",
763 {
764 NULL, /* no statistics , will be filled later if there are some */
765 NULL, /* no statistics */
766 },
767 {
768 NULL /* plugin shutdown */
769 }
770 },
771 {/* methods */
772 mysqlnd_debug_init,
773 }
774 };
775
776
777 /* {{{ mysqlnd_debug_trace_plugin_register */
778 void
mysqlnd_debug_trace_plugin_register(void)779 mysqlnd_debug_trace_plugin_register(void)
780 {
781 mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_plugin_trace_log_plugin);
782 }
783 /* }}} */
784
785
786 /*
787 * Local variables:
788 * tab-width: 4
789 * c-basic-offset: 4
790 * End:
791 * vim600: noet sw=4 ts=4 fdm=marker
792 * vim<600: noet sw=4 ts=4
793 */
794