mysqlnd_debug.c 23 KB

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