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