xref: /PHP-7.0/ext/mysqlnd/mysqlnd_debug.c (revision 478f119a)
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