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