mysqlnd_debug.c 24 KB

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