source: trunk/coverager.c @ 208

Last change on this file since 208 was 208, checked in by moo, 8 years ago

coverager: simplify cov data and fix clean

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