XCache is a fast, stable PHP opcode cacher that has been proven and is now running on production servers under high load. https://xcache.lighttpd.net/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

594 lines
14 KiB

  1. #if 0
  2. #define XCACHE_DEBUG
  3. #endif
  4. #include <stdio.h>
  5. #include "xcache.h"
  6. #include "ext/standard/flock_compat.h"
  7. #ifdef HAVE_SYS_FILE_H
  8. # include <sys/file.h>
  9. #endif
  10. #include <sys/types.h>
  11. #include <sys/stat.h>
  12. #include <fcntl.h>
  13. #include "stack.h"
  14. #include "xcache_globals.h"
  15. #include "coverager.h"
  16. #include "utils.h"
  17. typedef HashTable *coverager_t;
  18. #define PCOV_HEADER_MAGIC 0x564f4350
  19. static char *xc_coveragedump_dir = NULL;
  20. static zend_compile_file_t *old_compile_file = NULL;
  21. /* dumper */
  22. static void xc_destroy_coverage(void *pDest) /* {{{ */
  23. {
  24. coverager_t cov = *(coverager_t*) pDest;
  25. TRACE("destroy %p", cov);
  26. zend_hash_destroy(cov);
  27. efree(cov);
  28. }
  29. /* }}} */
  30. void xcache_mkdirs_ex(char *root, int rootlen, char *path, int pathlen TSRMLS_DC) /* {{{ */
  31. {
  32. char *fullpath;
  33. struct stat st;
  34. ALLOCA_FLAG(use_heap)
  35. TRACE("mkdirs %s %d %s %d", root, rootlen, path, pathlen);
  36. fullpath = my_do_alloca(rootlen + pathlen + 1, use_heap);
  37. memcpy(fullpath, root, rootlen);
  38. memcpy(fullpath + rootlen, path, pathlen);
  39. fullpath[rootlen + pathlen] = '\0';
  40. if (stat(fullpath, &st) != 0) {
  41. char *chr;
  42. chr = strrchr(path, PHP_DIR_SEPARATOR);
  43. if (chr && chr != path) {
  44. *chr = '\0';
  45. xcache_mkdirs_ex(root, rootlen, path, chr - path TSRMLS_CC);
  46. *chr = PHP_DIR_SEPARATOR;
  47. }
  48. TRACE("mkdir %s", fullpath);
  49. #if PHP_MAJOR_VERSION > 5
  50. php_stream_mkdir(fullpath, 0700, REPORT_ERRORS, NULL);
  51. #else
  52. mkdir(fullpath, 0700);
  53. #endif
  54. }
  55. my_free_alloca(fullpath, use_heap);
  56. }
  57. /* }}} */
  58. static void xc_coverager_save_cov(char *srcfile, char *outfilename, coverager_t cov TSRMLS_DC) /* {{{ */
  59. {
  60. long *buf = NULL, *p;
  61. long covlines, *phits;
  62. int fd = -1;
  63. int size;
  64. int newfile;
  65. struct stat srcstat, outstat;
  66. HashPosition pos;
  67. char *contents = NULL;
  68. long len;
  69. if (stat(srcfile, &srcstat) != 0) {
  70. return;
  71. }
  72. newfile = 0;
  73. if (stat(outfilename, &outstat) != 0) {
  74. newfile = 1;
  75. }
  76. else {
  77. if (srcstat.st_mtime > outstat.st_mtime) {
  78. newfile = 1;
  79. }
  80. }
  81. fd = open(outfilename, O_RDWR | O_CREAT, 0600);
  82. if (fd < 0) {
  83. char *chr;
  84. chr = strrchr(srcfile, PHP_DIR_SEPARATOR);
  85. if (chr) {
  86. *chr = '\0';
  87. xcache_mkdirs_ex(xc_coveragedump_dir, strlen(xc_coveragedump_dir), srcfile, chr - srcfile TSRMLS_CC);
  88. *chr = PHP_DIR_SEPARATOR;
  89. }
  90. fd = open(outfilename, O_RDWR | O_CREAT, 0600);
  91. if (fd < 0) {
  92. goto bailout;
  93. }
  94. }
  95. if (flock(fd, LOCK_EX) != SUCCESS) {
  96. goto bailout;
  97. }
  98. if (newfile) {
  99. TRACE("%s", "new file");
  100. }
  101. else if (outstat.st_size) {
  102. len = outstat.st_size;
  103. contents = emalloc(len);
  104. if (read(fd, (void *) contents, len) != len) {
  105. goto bailout;
  106. }
  107. TRACE("oldsize %d", (int) len);
  108. do {
  109. p = (long *) contents;
  110. len -= sizeof(long);
  111. if (len < 0) {
  112. break;
  113. }
  114. if (*p++ != PCOV_HEADER_MAGIC) {
  115. TRACE("wrong magic in file %s", outfilename);
  116. break;
  117. }
  118. p += 2; /* skip covliens */
  119. len -= sizeof(long) * 2;
  120. if (len < 0) {
  121. break;
  122. }
  123. for (; len >= sizeof(long) * 2; len -= sizeof(long) * 2, p += 2) {
  124. if (zend_hash_index_find(cov, p[0], (void**)&phits) == SUCCESS) {
  125. if (p[1] == -1) {
  126. /* OPTIMIZE: already marked */
  127. continue;
  128. }
  129. if (*phits != -1) {
  130. p[1] += *phits;
  131. }
  132. }
  133. zend_hash_index_update(cov, p[0], &p[1], sizeof(p[1]), NULL);
  134. }
  135. } while (0);
  136. efree(contents);
  137. contents = NULL;
  138. }
  139. /* serialize */
  140. size = (zend_hash_num_elements(cov) + 1) * sizeof(long) * 2 + sizeof(long);
  141. p = buf = emalloc(size);
  142. *p++ = PCOV_HEADER_MAGIC;
  143. p += 2; /* for covlines */
  144. covlines = 0;
  145. zend_hash_internal_pointer_reset_ex(cov, &pos);
  146. while (zend_hash_get_current_data_ex(cov, (void**)&phits, &pos) == SUCCESS) {
  147. *p++ = pos->h;
  148. *p++ = *phits;
  149. if (*phits > 0) {
  150. covlines ++;
  151. }
  152. zend_hash_move_forward_ex(cov, &pos);
  153. }
  154. p = buf + 1;
  155. p[0] = 0;
  156. p[1] = covlines;
  157. if (ftruncate(fd, 0) != 0) {
  158. goto bailout;
  159. }
  160. lseek(fd, 0, SEEK_SET);
  161. if (write(fd, (char *) buf, size) != size) {
  162. goto bailout;
  163. }
  164. bailout:
  165. if (contents) efree(contents);
  166. if (fd >= 0) close(fd);
  167. if (buf) efree(buf);
  168. }
  169. /* }}} */
  170. static void xc_coverager_initenv(TSRMLS_D) /* {{{ */
  171. {
  172. if (!XG(coverages)) {
  173. XG(coverages) = emalloc(sizeof(HashTable));
  174. zend_hash_init(XG(coverages), 0, NULL, xc_destroy_coverage, 0);
  175. }
  176. }
  177. /* }}} */
  178. static void xc_coverager_clean(TSRMLS_D) /* {{{ */
  179. {
  180. if (XG(coverages)) {
  181. HashPosition pos;
  182. coverager_t *pcov;
  183. zend_hash_internal_pointer_reset_ex(XG(coverages), &pos);
  184. while (zend_hash_get_current_data_ex(XG(coverages), (void **) &pcov, &pos) == SUCCESS) {
  185. long *phits;
  186. coverager_t cov;
  187. HashPosition pos2;
  188. cov = *pcov;
  189. zend_hash_internal_pointer_reset_ex(cov, &pos2);
  190. while (zend_hash_get_current_data_ex(cov, (void**)&phits, &pos2) == SUCCESS) {
  191. long hits = *phits;
  192. if (hits != -1) {
  193. hits = -1;
  194. zend_hash_index_update(cov, pos2->h, &hits, sizeof(hits), NULL);
  195. }
  196. zend_hash_move_forward_ex(cov, &pos2);
  197. }
  198. zend_hash_move_forward_ex(XG(coverages), &pos);
  199. }
  200. }
  201. }
  202. /* }}} */
  203. static void xc_coverager_cleanup(TSRMLS_D) /* {{{ */
  204. {
  205. if (XG(coverages)) {
  206. zend_hash_destroy(XG(coverages));
  207. efree(XG(coverages));
  208. XG(coverages) = NULL;
  209. }
  210. }
  211. /* }}} */
  212. static void xc_coverager_enable(TSRMLS_D) /* {{{ */
  213. {
  214. XG(coverage_enabled) = 1;
  215. }
  216. /* }}} */
  217. static void xc_coverager_disable(TSRMLS_D) /* {{{ */
  218. {
  219. XG(coverage_enabled) = 0;
  220. }
  221. /* }}} */
  222. void xc_coverager_request_init(TSRMLS_D) /* {{{ */
  223. {
  224. if (XG(coverager)) {
  225. xc_coverager_enable(TSRMLS_C);
  226. #ifdef ZEND_COMPILE_EXTENDED_INFO
  227. CG(compiler_options) |= ZEND_COMPILE_EXTENDED_INFO;
  228. #else
  229. CG(extended_info) = 1;
  230. #endif
  231. }
  232. else {
  233. XG(coverage_enabled) = 0;
  234. }
  235. }
  236. /* }}} */
  237. static void xc_coverager_autodump(TSRMLS_D) /* {{{ */
  238. {
  239. coverager_t *pcov;
  240. zstr s;
  241. char *outfilename;
  242. int dumpdir_len, outfilelen, alloc_len = 0;
  243. uint size;
  244. HashPosition pos;
  245. if (XG(coverages) && xc_coveragedump_dir) {
  246. dumpdir_len = strlen(xc_coveragedump_dir);
  247. alloc_len = dumpdir_len + 1 + 128;
  248. outfilename = emalloc(alloc_len);
  249. strcpy(outfilename, xc_coveragedump_dir);
  250. zend_hash_internal_pointer_reset_ex(XG(coverages), &pos);
  251. while (zend_hash_get_current_data_ex(XG(coverages), (void **) &pcov, &pos) == SUCCESS) {
  252. zend_hash_get_current_key_ex(XG(coverages), &s, &size, NULL, 0, &pos);
  253. outfilelen = dumpdir_len + size + 5;
  254. if (alloc_len < outfilelen) {
  255. alloc_len = outfilelen + 128;
  256. outfilename = erealloc(outfilename, alloc_len);
  257. }
  258. strcpy(outfilename + dumpdir_len, ZSTR_S(s));
  259. strcpy(outfilename + dumpdir_len + size - 1, ".pcov");
  260. TRACE("outfilename %s", outfilename);
  261. xc_coverager_save_cov(ZSTR_S(s), outfilename, *pcov TSRMLS_CC);
  262. zend_hash_move_forward_ex(XG(coverages), &pos);
  263. }
  264. efree(outfilename);
  265. }
  266. }
  267. /* }}} */
  268. static void xc_coverager_dump(zval *return_value TSRMLS_DC) /* {{{ */
  269. {
  270. coverager_t *pcov;
  271. HashPosition pos;
  272. if (XG(coverages)) {
  273. array_init(return_value);
  274. zend_hash_internal_pointer_reset_ex(XG(coverages), &pos);
  275. while (zend_hash_get_current_data_ex(XG(coverages), (void **) &pcov, &pos) == SUCCESS) {
  276. zval *lines;
  277. long *phits;
  278. coverager_t cov;
  279. HashPosition pos2;
  280. zstr filename;
  281. uint size;
  282. cov = *pcov;
  283. zend_hash_get_current_key_ex(XG(coverages), &filename, &size, NULL, 0, &pos);
  284. MAKE_STD_ZVAL(lines);
  285. array_init(lines);
  286. zend_hash_internal_pointer_reset_ex(cov, &pos2);
  287. while (zend_hash_get_current_data_ex(cov, (void**)&phits, &pos2) == SUCCESS) {
  288. long hits = *phits;
  289. add_index_long(lines, pos2->h, hits >= 0 ? hits : 0);
  290. zend_hash_move_forward_ex(cov, &pos2);
  291. }
  292. add_assoc_zval_ex(return_value, ZSTR_S(filename), strlen(ZSTR_S(filename)) + 1, lines);
  293. zend_hash_move_forward_ex(XG(coverages), &pos);
  294. }
  295. }
  296. else {
  297. RETVAL_NULL();
  298. }
  299. }
  300. /* }}} */
  301. void xc_coverager_request_shutdown(TSRMLS_D) /* {{{ */
  302. {
  303. if (XG(coverager)) {
  304. xc_coverager_autodump(TSRMLS_C);
  305. xc_coverager_cleanup(TSRMLS_C);
  306. }
  307. }
  308. /* }}} */
  309. /* helper func to store hits into coverages */
  310. static coverager_t xc_coverager_get(const char *filename TSRMLS_DC) /* {{{ */
  311. {
  312. int len = strlen(filename) + 1;
  313. coverager_t cov, *pcov;
  314. if (zend_u_hash_find(XG(coverages), IS_STRING, filename, len, (void **) &pcov) == SUCCESS) {
  315. TRACE("got coverage %s %p", filename, *pcov);
  316. return *pcov;
  317. }
  318. else {
  319. cov = emalloc(sizeof(HashTable));
  320. zend_hash_init(cov, 0, NULL, NULL, 0);
  321. zend_u_hash_add(XG(coverages), IS_STRING, filename, len, (void **) &cov, sizeof(cov), NULL);
  322. TRACE("new coverage %s %p", filename, cov);
  323. return cov;
  324. }
  325. }
  326. /* }}} */
  327. static void xc_coverager_add_hits(HashTable *cov, long line, long hits TSRMLS_DC) /* {{{ */
  328. {
  329. long *poldhits;
  330. if (line == 0) {
  331. return;
  332. }
  333. if (zend_hash_index_find(cov, line, (void**)&poldhits) == SUCCESS) {
  334. if (hits == -1) {
  335. /* OPTIMIZE: -1 == init-ing, but it's already initized */
  336. return;
  337. }
  338. if (*poldhits != -1) {
  339. hits += *poldhits;
  340. }
  341. }
  342. zend_hash_index_update(cov, line, &hits, sizeof(hits), NULL);
  343. }
  344. /* }}} */
  345. static int xc_coverager_get_op_array_size_no_tail(zend_op_array *op_array) /* {{{ */
  346. {
  347. zend_uint last = op_array->last;
  348. do {
  349. next_op:
  350. if (last == 0) {
  351. break;
  352. }
  353. switch (op_array->opcodes[last - 1].opcode) {
  354. #ifdef ZEND_HANDLE_EXCEPTION
  355. case ZEND_HANDLE_EXCEPTION:
  356. #endif
  357. case ZEND_RETURN:
  358. case ZEND_EXT_STMT:
  359. --last;
  360. goto next_op;
  361. }
  362. } while (0);
  363. return last;
  364. }
  365. /* }}} */
  366. /* prefill */
  367. static int xc_coverager_init_op_array(zend_op_array *op_array TSRMLS_DC) /* {{{ */
  368. {
  369. zend_uint size;
  370. coverager_t cov;
  371. zend_uint i;
  372. if (op_array->type != ZEND_USER_FUNCTION) {
  373. return 0;
  374. }
  375. size = xc_coverager_get_op_array_size_no_tail(op_array);
  376. cov = xc_coverager_get(op_array->filename TSRMLS_CC);
  377. for (i = 0; i < size; i ++) {
  378. switch (op_array->opcodes[i].opcode) {
  379. case ZEND_EXT_STMT:
  380. #if 0
  381. case ZEND_EXT_FCALL_BEGIN:
  382. case ZEND_EXT_FCALL_END:
  383. #endif
  384. xc_coverager_add_hits(cov, op_array->opcodes[i].lineno, -1 TSRMLS_CC);
  385. break;
  386. }
  387. }
  388. return 0;
  389. }
  390. /* }}} */
  391. static void xc_coverager_init_compile_result(zend_op_array *op_array TSRMLS_DC) /* {{{ */
  392. {
  393. xc_compile_result_t cr;
  394. xc_compile_result_init_cur(&cr, op_array TSRMLS_CC);
  395. xc_apply_op_array(&cr, (apply_func_t) xc_coverager_init_op_array TSRMLS_CC);
  396. xc_compile_result_free(&cr);
  397. }
  398. /* }}} */
  399. static zend_op_array *xc_compile_file_for_coverage(zend_file_handle *h, int type TSRMLS_DC) /* {{{ */
  400. {
  401. zend_op_array *op_array;
  402. op_array = old_compile_file(h, type TSRMLS_CC);
  403. if (op_array) {
  404. if (XG(coverager)) {
  405. xc_coverager_initenv(TSRMLS_C);
  406. xc_coverager_init_compile_result(op_array TSRMLS_CC);
  407. }
  408. }
  409. return op_array;
  410. }
  411. /* }}} */
  412. /* hits */
  413. void xc_coverager_handle_ext_stmt(zend_op_array *op_array, zend_uchar op) /* {{{ */
  414. {
  415. TSRMLS_FETCH();
  416. if (XG(coverages) && XG(coverage_enabled)) {
  417. int size = xc_coverager_get_op_array_size_no_tail(op_array);
  418. int oplineno = (*EG(opline_ptr)) - op_array->opcodes;
  419. if (oplineno < size) {
  420. xc_coverager_add_hits(xc_coverager_get(op_array->filename TSRMLS_CC), (*EG(opline_ptr))->lineno, 1 TSRMLS_CC);
  421. }
  422. }
  423. }
  424. /* }}} */
  425. /* init/destroy */
  426. int xc_coverager_init(int module_number TSRMLS_DC) /* {{{ */
  427. {
  428. old_compile_file = zend_compile_file;
  429. zend_compile_file = xc_compile_file_for_coverage;
  430. if (cfg_get_string("xcache.coveragedump_directory", &xc_coveragedump_dir) == SUCCESS && xc_coveragedump_dir) {
  431. int len;
  432. xc_coveragedump_dir = pestrdup(xc_coveragedump_dir, 1);
  433. len = strlen(xc_coveragedump_dir);
  434. if (len) {
  435. if (xc_coveragedump_dir[len - 1] == '/') {
  436. xc_coveragedump_dir[len - 1] = '\0';
  437. }
  438. }
  439. if (!strlen(xc_coveragedump_dir)) {
  440. pefree(xc_coveragedump_dir, 1);
  441. xc_coveragedump_dir = NULL;
  442. }
  443. }
  444. return SUCCESS;
  445. }
  446. /* }}} */
  447. void xc_coverager_destroy() /* {{{ */
  448. {
  449. if (old_compile_file == xc_compile_file_for_coverage) {
  450. zend_compile_file = old_compile_file;
  451. }
  452. if (xc_coveragedump_dir) {
  453. pefree(xc_coveragedump_dir, 1);
  454. xc_coveragedump_dir = NULL;
  455. }
  456. }
  457. /* }}} */
  458. /* user api */
  459. /* {{{ proto array xcache_coverager_decode(string data)
  460. * decode specified data which is saved by auto dumper to array
  461. */
  462. PHP_FUNCTION(xcache_coverager_decode)
  463. {
  464. char *str;
  465. int len;
  466. long *p;
  467. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &str, &len) == FAILURE) {
  468. return;
  469. }
  470. array_init(return_value);
  471. p = (long*) str;
  472. len -= sizeof(long);
  473. if (len < 0) {
  474. return;
  475. }
  476. if (*p++ != PCOV_HEADER_MAGIC) {
  477. TRACE("%s", "wrong magic in xcache_coverager_decode");
  478. return;
  479. }
  480. for (; len >= sizeof(long) * 2; len -= sizeof(long) * 2, p += 2) {
  481. add_index_long(return_value, p[0], p[1] < 0 ? 0 : p[1]);
  482. }
  483. }
  484. /* }}} */
  485. /* {{{ proto void xcache_coverager_start([bool clean = true])
  486. * starts coverager data collecting
  487. */
  488. PHP_FUNCTION(xcache_coverager_start)
  489. {
  490. zend_bool clean = 1;
  491. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &clean) == FAILURE) {
  492. return;
  493. }
  494. if (clean) {
  495. xc_coverager_clean(TSRMLS_C);
  496. }
  497. if (XG(coverager)) {
  498. xc_coverager_enable(TSRMLS_C);
  499. }
  500. else {
  501. php_error(E_WARNING, "You can only start coverager after you set 'xcache.coverager' to 'On' in ini");
  502. }
  503. }
  504. /* }}} */
  505. /* {{{ proto void xcache_coverager_stop([bool clean = false])
  506. * stop coverager data collecting
  507. */
  508. PHP_FUNCTION(xcache_coverager_stop)
  509. {
  510. zend_bool clean = 0;
  511. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &clean) == FAILURE) {
  512. return;
  513. }
  514. xc_coverager_disable(TSRMLS_C);
  515. if (clean) {
  516. xc_coverager_clean(TSRMLS_C);
  517. }
  518. }
  519. /* }}} */
  520. /* {{{ proto array xcache_coverager_get([bool clean = false])
  521. * get coverager data collected
  522. */
  523. PHP_FUNCTION(xcache_coverager_get)
  524. {
  525. zend_bool clean = 0;
  526. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &clean) == FAILURE) {
  527. return;
  528. }
  529. xc_coverager_dump(return_value TSRMLS_CC);
  530. if (clean) {
  531. xc_coverager_clean(TSRMLS_C);
  532. }
  533. }
  534. /* }}} */