LCOV - code coverage report
Current view: top level - src - excimer_log.c (source / functions) Hit Total Coverage
Test: mediawiki/php/excimer test coverage report Lines: 329 340 96.8 %
Date: 2024-02-28 22:03:29 Functions: 22 23 95.7 %

          Line data    Source code
       1             : /* Copyright 2018 Wikimedia Foundation
       2             :  *
       3             :  * Licensed under the Apache License, Version 2.0 (the "License");
       4             :  * you may not use this file except in compliance with the License.
       5             :  * You may obtain a copy of the License at
       6             :  *
       7             :  *     http://www.apache.org/licenses/LICENSE-2.0
       8             :  *
       9             :  * Unless required by applicable law or agreed to in writing, software
      10             :  * distributed under the License is distributed on an "AS IS" BASIS,
      11             :  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             :  * See the License for the specific language governing permissions and
      13             :  * limitations under the License.
      14             :  */
      15             : 
      16             : #include "php.h"
      17             : #include "Zend/zend_smart_str.h"
      18             : #include "php_excimer.h"
      19             : #include "excimer_log.h"
      20             : 
      21             : static const char excimer_log_truncated_name[] = "excimer_truncated";
      22             : static const char excimer_log_fake_filename[] = "excimer fake file";
      23             : 
      24             : static uint32_t excimer_log_find_or_add_frame(excimer_log *log,
      25             :         zend_execute_data *execute_data, zend_long depth);
      26             : 
      27             : /* {{{ Compatibility functions and macros */
      28             : 
      29             : #if PHP_VERSION_ID >= 70300
      30             : #define excimer_log_new_array zend_new_array
      31             : #else
      32             : static inline HashTable *excimer_log_new_array(uint32_t nSize)
      33             : {
      34             :     HashTable *ht = emalloc(sizeof(HashTable));
      35             :     zend_hash_init(ht, nSize, NULL, ZVAL_PTR_DTOR, 0);
      36             :     return ht;
      37             : }
      38             : #endif
      39             : 
      40             : #if PHP_VERSION_ID >= 70200
      41             : #define excimer_log_smart_str_get_len smart_str_get_len
      42             : #define excimer_log_smart_str_extract smart_str_extract
      43             : #define excimer_log_smart_str_append_printf smart_str_append_printf
      44             : #define excimer_log_known_string ZSTR_KNOWN
      45             : #else
      46             : static inline size_t excimer_log_smart_str_get_len(smart_str *str)
      47             : {
      48             :     return str->s ? ZSTR_LEN(str->s) : 0;
      49             : }
      50             : 
      51             : static inline zend_string *excimer_log_smart_str_extract(smart_str *str)
      52             : {
      53             :     if (str->s) {
      54             :         zend_string *res;
      55             :         smart_str_0(str);
      56             :         res = str->s;
      57             :         str->s = NULL;
      58             :         return res;
      59             :     } else {
      60             :         return ZSTR_EMPTY_ALLOC();
      61             :     }
      62             : }
      63             : 
      64             : 
      65             : static void excimer_log_smart_str_append_printf(smart_str *dest, const char *format, ...)
      66             : {
      67             :     va_list arg;
      68             :     size_t len;
      69             :     char *buf;
      70             : 
      71             :     va_start(arg, format);
      72             :     len = vspprintf(&buf, 0, format, arg);
      73             :     va_end(arg);
      74             :     smart_str_appendl(dest, buf, len);
      75             :     efree(buf);
      76             : }
      77             : #define excimer_log_known_string(index) CG(known_strings)[index]
      78             : #endif
      79             : 
      80             : #if PHP_VERSION_ID >= 80100
      81             : #define excimer_log_add_assoc_array add_assoc_array
      82             : #else
      83           3 : static inline void excimer_log_add_assoc_array(zval *dest, const char *key, HashTable *arr)
      84             : {
      85             :     zval z_tmp;
      86           3 :     ZVAL_ARR(&z_tmp, arr);
      87           3 :     add_assoc_zval(dest, key, &z_tmp);
      88           3 : }
      89             : #endif
      90             : 
      91             : /* }}} */
      92             : 
      93           6 : void excimer_log_init(excimer_log *log)
      94             : {
      95           6 :     log->entries_size = 0;
      96           6 :     log->entries = NULL;
      97           6 :     log->frames = ecalloc(1, sizeof(excimer_log_frame));
      98           6 :     log->frames_size = 1;
      99           6 :     log->reverse_frames = excimer_log_new_array(0);
     100           6 :     log->epoch = 0;
     101           6 :     log->event_count = 0;
     102           6 : }
     103             : 
     104           6 : void excimer_log_destroy(excimer_log *log)
     105             : {
     106           6 :     if (log->entries) {
     107           3 :         efree(log->entries);
     108             :     }
     109           6 :     if (log->frames) {
     110             :         int i;
     111          31 :         for (i = 0; i < log->frames_size; i++) {
     112          25 :             if (log->frames[i].filename) {
     113          19 :                 zend_string_delref(log->frames[i].filename);
     114             :             }
     115          25 :             if (log->frames[i].class_name) {
     116           1 :                 zend_string_delref(log->frames[i].class_name);
     117             :             }
     118          25 :             if (log->frames[i].function_name) {
     119          17 :                 zend_string_delref(log->frames[i].function_name);
     120             :             }
     121             :         }
     122           6 :         efree(log->frames);
     123             :     }
     124           6 :     zend_hash_destroy(log->reverse_frames);
     125           6 :     efree(log->reverse_frames);
     126           6 : }
     127             : 
     128           1 : void excimer_log_set_max_depth(excimer_log *log, zend_long depth)
     129             : {
     130           1 :     log->max_depth = depth;
     131           1 : }
     132             : 
     133           3 : void excimer_log_copy_options(excimer_log *dest, excimer_log  *src)
     134             : {
     135           3 :     dest->max_depth = src->max_depth;
     136           3 :     dest->epoch = src->epoch;
     137           3 :     dest->period = src->period;
     138           3 : }
     139             : 
     140          61 : void excimer_log_add(excimer_log *log, zend_execute_data *execute_data,
     141             :     zend_long event_count, uint64_t timestamp)
     142             : {
     143          61 :     uint32_t frame_index = excimer_log_find_or_add_frame(log, execute_data, 0);
     144             :     excimer_log_entry *entry;
     145             : 
     146          61 :     log->entries = safe_erealloc(log->entries, log->entries_size + 1,
     147             :         sizeof(excimer_log_entry), 0);
     148          61 :     entry = &log->entries[log->entries_size++];
     149          61 :     entry->frame_index = frame_index;
     150          61 :     entry->event_count = event_count;
     151          61 :     log->event_count += event_count;
     152          61 :     entry->timestamp = timestamp;
     153          61 : }
     154             : 
     155           1 : static uint32_t excimer_log_get_truncation_marker(excimer_log *log) {
     156             :     zval* zp_index;
     157             :     zval z_new_index;
     158             :     excimer_log_frame *p_frame;
     159             :     
     160           1 :     zp_index = zend_hash_str_find(log->reverse_frames,
     161             :         excimer_log_truncated_name, sizeof(excimer_log_truncated_name) - 1);
     162           1 :     if (zp_index) {
     163           0 :         return excimer_safe_uint32(Z_LVAL_P(zp_index));
     164             :     }
     165             : 
     166           1 :     ZVAL_LONG(&z_new_index, log->frames_size);
     167           1 :     zend_hash_str_add(log->reverse_frames,
     168             :         excimer_log_truncated_name, sizeof(excimer_log_truncated_name) - 1,
     169             :         &z_new_index);
     170           1 :     log->frames = safe_erealloc(log->frames, log->frames_size + 1,
     171             :         sizeof(excimer_log_frame), 0);
     172           1 :     p_frame = &log->frames[log->frames_size++];
     173             : 
     174           1 :     p_frame->filename = zend_string_init(excimer_log_fake_filename,
     175             :         sizeof(excimer_log_fake_filename) - 1, 0);
     176           1 :     p_frame->lineno = 1;
     177           1 :     p_frame->closure_line = 0;
     178           1 :     p_frame->class_name = NULL;
     179           1 :     p_frame->function_name = zend_string_init(excimer_log_truncated_name,
     180             :         sizeof(excimer_log_truncated_name) - 1, 0);
     181           1 :     p_frame->prev_index = 0;
     182             : 
     183           1 :     return excimer_safe_uint32(Z_LVAL(z_new_index));
     184             : }
     185             : 
     186         186 : static uint32_t excimer_log_find_or_add_frame(excimer_log *log,
     187             :     zend_execute_data *execute_data, zend_long depth)
     188             : {
     189             :     uint32_t prev_index;
     190         186 :     if (!execute_data) {
     191           0 :         return 0;
     192         186 :     } else if (!execute_data->prev_execute_data) {
     193          60 :         prev_index = 0;
     194         126 :     } else if (log->max_depth && depth >= log->max_depth) {
     195           1 :         prev_index = excimer_log_get_truncation_marker(log);
     196             :     } else {
     197         125 :         prev_index = excimer_log_find_or_add_frame(log,
     198             :             execute_data->prev_execute_data, depth + 1);
     199             :     }
     200         186 :     if (!execute_data->func
     201         186 :         || !ZEND_USER_CODE(execute_data->func->common.type))
     202             :     {
     203           0 :         return prev_index;
     204             :     } else {
     205         186 :         zend_function *func = execute_data->func;
     206         186 :         excimer_log_frame frame = {NULL};
     207         186 :         smart_str ss_key = {NULL};
     208             :         zend_string *str_key;
     209             :         zval* zp_index;
     210             : 
     211         186 :         frame.filename = func->op_array.filename;
     212         186 :         zend_string_addref(frame.filename);
     213             : 
     214         186 :         if (func->common.scope && func->common.scope->name) {
     215           5 :             frame.class_name = func->common.scope->name;
     216           5 :             zend_string_addref(frame.class_name);
     217             :         }
     218             : 
     219         186 :         if (func->common.function_name) {
     220         126 :             frame.function_name = func->common.function_name;
     221         126 :             zend_string_addref(frame.function_name);
     222             :         }
     223             : 
     224         186 :         if (func->op_array.fn_flags & ZEND_ACC_CLOSURE) {
     225          20 :             frame.closure_line = func->op_array.line_start;
     226             :         }
     227             : 
     228         186 :         frame.lineno = execute_data->opline->lineno;
     229         186 :         frame.prev_index = prev_index;
     230             : 
     231             :         /* Make a key for reverse lookup */
     232         186 :         smart_str_append(&ss_key, frame.filename);
     233             :         smart_str_appendc(&ss_key, '\0');
     234         186 :         excimer_log_smart_str_append_printf(&ss_key, "%d", frame.lineno);
     235             :         smart_str_appendc(&ss_key, '\0');
     236         186 :         excimer_log_smart_str_append_printf(&ss_key, "%d", frame.prev_index);
     237         186 :         str_key = excimer_log_smart_str_extract(&ss_key);
     238             : 
     239             :         /* Look for a matching frame in the reverse hashtable */
     240         186 :         zp_index = zend_hash_find(log->reverse_frames, str_key);
     241         186 :         if (zp_index) {
     242             :             zend_string_free(str_key);
     243         168 :             zend_string_delref(frame.filename);
     244         168 :             if (frame.class_name) {
     245           4 :                 zend_string_delref(frame.class_name);
     246             :             }
     247         168 :             if (frame.function_name) {
     248         110 :                 zend_string_delref(frame.function_name);
     249             :             }
     250             : 
     251         168 :             return excimer_safe_uint32(Z_LVAL_P(zp_index));
     252             :         } else {
     253             :             zval z_new_index;
     254             : 
     255             :             /* Create a new entry in the array and reverse hashtable */
     256          18 :             ZVAL_LONG(&z_new_index, log->frames_size);
     257          18 :             zend_hash_add(log->reverse_frames, str_key, &z_new_index);
     258          18 :             log->frames = safe_erealloc(log->frames, log->frames_size + 1,
     259             :                 sizeof(excimer_log_frame), 0);
     260          18 :             memcpy(&log->frames[log->frames_size++], &frame, sizeof(excimer_log_frame));
     261             : 
     262             :             zend_string_delref(str_key);
     263          18 :             return excimer_safe_uint32(Z_LVAL(z_new_index));
     264             :         }
     265             :     }
     266             : }
     267             : 
     268           0 : zend_long excimer_log_get_size(excimer_log *log)
     269             : {
     270           0 :     return log->entries_size;
     271             : }
     272             : 
     273         419 : excimer_log_entry *excimer_log_get_entry(excimer_log *log, zend_long i)
     274             : {
     275         419 :     if (i >= 0 && i < log->entries_size) {
     276         419 :         return &log->entries[i];
     277             :     } else {
     278           0 :         return NULL;
     279             :     }
     280             : }
     281             : 
     282         443 : excimer_log_frame *excimer_log_get_frame(excimer_log *log, zend_long i)
     283             : {
     284         443 :     if (i > 0 && i < log->frames_size) {
     285         443 :         return &log->frames[i];
     286             :     } else {
     287           0 :         return NULL;
     288             :     }
     289             : }
     290             : 
     291          43 : static void excimer_log_append_no_spaces(smart_str *dest, zend_string *src)
     292             : {
     293          43 :     size_t new_len = smart_str_alloc(dest, ZSTR_LEN(src), 0);
     294          43 :     size_t prev_len = ZSTR_LEN(dest->s);
     295             :     size_t i;
     296         369 :     for (i = 0; i < ZSTR_LEN(src); i++) {
     297         326 :         char c = ZSTR_VAL(src)[i];
     298         326 :         if (c == ' ' || c == '\0') {
     299           0 :             c = '_';
     300             :         }
     301         326 :         ZSTR_VAL(dest->s)[prev_len + i] = c;
     302             :     }
     303          43 :     ZSTR_LEN(dest->s) = new_len;
     304          43 : }
     305             : 
     306          40 : static void excimer_log_append_frame_name(smart_str *ss, excimer_log_frame *frame) {
     307          40 :     if (frame->closure_line != 0) {
     308             :         /* Annotate anonymous functions with their source location.
     309             :          * Example: {closure:/path/to/file.php(123)}
     310             :          */
     311             :         smart_str_appends(ss, "{closure:");
     312           6 :         excimer_log_append_no_spaces(ss, frame->filename);
     313           6 :         excimer_log_smart_str_append_printf(ss, "(%d)}", frame->closure_line);
     314          34 :     } else if (frame->function_name == NULL) {
     315             :         /* For file-scope code, use the file name */
     316           6 :         excimer_log_append_no_spaces(ss, frame->filename);
     317             :     } else {
     318          28 :         if (frame->class_name) {
     319           3 :             excimer_log_append_no_spaces(ss, frame->class_name);
     320             :             smart_str_appends(ss, "::");
     321             :         }
     322          28 :         excimer_log_append_no_spaces(ss, frame->function_name);
     323             :     }
     324          40 : }
     325             : 
     326           2 : zend_string *excimer_log_format_collapsed(excimer_log *log)
     327             : {
     328             :     zend_long entry_index;
     329             :     zend_long frame_index;
     330             :     zval *zp_count;
     331             :     zval z_count;
     332           2 :     smart_str ss_out = {NULL};
     333             :     HashTable frame_counts_storage, lines_storage;
     334             :     HashTable *ht_frame_counts, *ht_lines;
     335             : 
     336           2 :     ht_frame_counts = &frame_counts_storage;
     337           2 :     memset(ht_frame_counts, 0, sizeof(HashTable));
     338           2 :     zend_hash_init(ht_frame_counts, 0, NULL, NULL, 0);
     339             : 
     340           2 :     ht_lines = &lines_storage;
     341           2 :     memset(ht_lines, 0, sizeof(HashTable));
     342           2 :     zend_hash_init(ht_lines, 0, NULL, NULL, 0);
     343             : 
     344           2 :     excimer_log_frame ** frame_ptrs = NULL;
     345           2 :     size_t frames_capacity = 0;
     346             :     zend_string *str_line;
     347             : 
     348             :     /* Collate frame counts */
     349          33 :     for (entry_index = 0; entry_index < log->entries_size; entry_index++) {
     350          31 :         excimer_log_entry *entry = excimer_log_get_entry(log, entry_index);
     351          31 :         zp_count = zend_hash_index_find(ht_frame_counts, entry->frame_index);
     352          31 :         if (!zp_count) {
     353           5 :             ZVAL_LONG(&z_count, 0);
     354           5 :             zp_count = zend_hash_index_add(ht_frame_counts, entry->frame_index, &z_count);
     355             :         }
     356             : 
     357          31 :         Z_LVAL_P(zp_count) += entry->event_count;
     358             :     }
     359             : 
     360             :     /* Format traces, and deduplicate frames that differ only in hidden line numbers */
     361          19 :     ZEND_HASH_FOREACH_NUM_KEY_VAL(ht_frame_counts, frame_index, zp_count) {
     362           5 :         zend_long current_frame_index = frame_index;
     363           5 :         zend_long num_frames = 0;
     364             :         excimer_log_frame *frame;
     365             :         zend_long i;
     366           5 :         int line_start = 1; /* TODO use bool when PHP 7.4 support is dropped */
     367           5 :         smart_str ss_line = {NULL};
     368             : 
     369             :         /* Build the array of frame pointers */
     370          28 :         while (current_frame_index) {
     371          23 :             frame = excimer_log_get_frame(log, current_frame_index);
     372          23 :             if (num_frames >= frames_capacity) {
     373          11 :                 if (frames_capacity >= ZEND_LONG_MAX - 1) {
     374             :                     /* Probably unreachable */
     375           0 :                     zend_error_noreturn(E_ERROR, "Too many Excimer frames");
     376             :                 }
     377          11 :                 frames_capacity++;
     378          11 :                 frame_ptrs = safe_erealloc(frame_ptrs, frames_capacity, sizeof(*frame_ptrs), 0);
     379             :             }
     380          23 :             frame_ptrs[num_frames++] = frame;
     381          23 :             current_frame_index = frame->prev_index;
     382             :         }
     383             : 
     384             :         /* Run through the array in reverse */
     385          28 :         for (i = num_frames - 1; i >= 0; i--) {
     386          23 :             frame = frame_ptrs[i];
     387             : 
     388          23 :             if (line_start) {
     389           5 :                 line_start = 0;
     390             :             } else {
     391             :                 smart_str_appends(&ss_line, ";");
     392             :             }
     393          23 :             excimer_log_append_frame_name(&ss_line, frame);
     394             :         }
     395             : 
     396             :         /* ht_lines[ss_line] += zp_count */
     397           5 :         str_line = excimer_log_smart_str_extract(&ss_line);
     398           5 :         zval *zp_line_count = zend_hash_find(ht_lines, str_line);
     399           5 :         if (!zp_line_count) {
     400           5 :             ZVAL_LONG(&z_count, 0);
     401           5 :             zp_line_count = zend_hash_add(ht_lines, str_line, &z_count);
     402             :         }
     403           5 :         Z_LVAL_P(zp_line_count) += Z_LVAL_P(zp_count);
     404             :     }
     405             :     ZEND_HASH_FOREACH_END();
     406             : 
     407             :     /* Concatenate lines */
     408          12 :     ZEND_HASH_FOREACH_STR_KEY_VAL(ht_lines, str_line, zp_count) {
     409             :         smart_str_append(&ss_out, str_line);
     410           5 :         excimer_log_smart_str_append_printf(&ss_out, " " ZEND_LONG_FMT "\n", Z_LVAL_P(zp_count));
     411             :     }
     412             :     ZEND_HASH_FOREACH_END();
     413             : 
     414           2 :     zend_hash_destroy(ht_frame_counts);
     415           2 :     zend_hash_destroy(ht_lines);
     416           2 :     efree(frame_ptrs);
     417           2 :     return excimer_log_smart_str_extract(&ss_out);
     418             : }
     419             : 
     420           7 : static HashTable *excimer_log_frame_to_speedscope_array(excimer_log_frame *frame) {
     421           7 :     HashTable *ht_func = excimer_log_new_array(0);
     422             :     zval tmp;
     423           7 :     smart_str ss_name = {NULL};
     424             :     
     425           7 :     excimer_log_append_frame_name(&ss_name, frame);
     426          14 :     ZVAL_STR(&tmp, excimer_log_smart_str_extract(&ss_name));
     427           7 :     zend_hash_str_add(ht_func, "name", sizeof("name")-1, &tmp);
     428             : 
     429           7 :     if (frame->filename) {
     430           7 :         ZVAL_STR_COPY(&tmp, frame->filename);
     431           7 :         zend_hash_add_new(ht_func, excimer_log_known_string(ZEND_STR_FILE), &tmp);
     432             :         /* Don't include the line number since it causes speedscope to split functions */
     433             :     }
     434           7 :     return ht_func;
     435             : }
     436             : 
     437          10 : static zend_string *excimer_log_get_speedscope_frame_key(excimer_log_frame *frame) {
     438          10 :     smart_str ss = {NULL};
     439             :     
     440          10 :     excimer_log_append_frame_name(&ss, frame);
     441             :     smart_str_appendc(&ss, '\0');
     442          10 :     smart_str_append(&ss, frame->filename);
     443          10 :     return excimer_log_smart_str_extract(&ss);
     444             : }
     445             : 
     446          30 : static uint32_t excimer_log_count_frames(excimer_log *log, uint32_t frame_index) {
     447          30 :     uint32_t n = 0;
     448         150 :     while (frame_index) {
     449         120 :         n++;
     450         120 :         frame_index = log->frames[frame_index].prev_index;
     451             :     }
     452          30 :     return n;
     453             : }
     454             : 
     455           1 : void excimer_log_get_speedscope_data(excimer_log *log, zval *zp_data) {
     456           1 :     array_init(zp_data);
     457           1 :     add_assoc_string(zp_data, "$schema", "https://www.speedscope.app/file-format-schema.json");
     458           1 :     add_assoc_string(zp_data, "exporter", "Excimer");
     459             : 
     460           1 :     HashTable *ht_frames = excimer_log_new_array(0);
     461           1 :     HashTable *ht_indexes_by_key = excimer_log_new_array(0);
     462           1 :     zend_long *lp_frame_indexes = ecalloc(log->frames_size, sizeof(zend_long));
     463             :     zend_long i;
     464             :     zval *zp_frame_index;
     465             :     zend_string *str_key;
     466             :     zval z_tmp, *zp_tmp;
     467             : 
     468             :     /* Build the frames array */
     469          11 :     for (i = 1; i < log->frames_size; i++) {
     470             :         zend_long index;
     471          10 :         excimer_log_frame *frame = &log->frames[i];
     472          10 :         str_key = excimer_log_get_speedscope_frame_key(frame);
     473          10 :         zp_frame_index = zend_hash_find(ht_indexes_by_key, str_key);
     474          10 :         if (!zp_frame_index) {
     475             :             /* Add the frame to ht_frames */
     476           7 :             index = zend_hash_num_elements(ht_frames);
     477           7 :             ZVAL_ARR(&z_tmp, excimer_log_frame_to_speedscope_array(frame));
     478           7 :             zend_hash_next_index_insert_new(ht_frames, &z_tmp);
     479             :             /* Add the frame index to ht_indexes_by_key */
     480           7 :             ZVAL_LONG(&z_tmp, index);
     481           7 :             zp_frame_index = zend_hash_add_new(ht_indexes_by_key, str_key, &z_tmp);
     482             :         }
     483          10 :         lp_frame_indexes[i] = Z_LVAL_P(zp_frame_index);
     484             :     }
     485             : 
     486             :     /* zp_data["shared"] = ["frames" => ht_frames] */
     487             :     zval z_shared;
     488           1 :     array_init(&z_shared);
     489           1 :     excimer_log_add_assoc_array(&z_shared, "frames", ht_frames);
     490           1 :     add_assoc_zval(zp_data, "shared", &z_shared);
     491             : 
     492             :     /* Build the samples and weights arrays */
     493           1 :     HashTable *ht_samples = excimer_log_new_array(log->entries_size);
     494           1 :     HashTable *ht_weights = excimer_log_new_array(log->entries_size);
     495           1 :     uint64_t first_timestamp = 0;
     496           1 :     uint64_t last_timestamp = 0;
     497          31 :     for (i = 0; i < log->entries_size; i++) {
     498          30 :         excimer_log_entry *entry = &log->entries[i];
     499          30 :         uint32_t frame_index = entry->frame_index;
     500             : 
     501          30 :         if (i == 0) {
     502           1 :             first_timestamp = entry->timestamp;
     503             :         }
     504          30 :         last_timestamp = entry->timestamp;
     505             : 
     506          30 :         uint32_t num_frames = excimer_log_count_frames(log, frame_index);
     507             :         uint32_t j;
     508             : 
     509             :         /* Create the array with ZEND_HASH_FILL_PACKED. This is just a fast way
     510             :          * to get it into the right state, with num_frames elements. */
     511          30 :         HashTable *ht_stack = excimer_log_new_array(num_frames);
     512          30 :         zend_hash_extend(ht_stack, num_frames, 1);
     513          30 :         ZEND_HASH_FILL_PACKED(ht_stack) {
     514             : #if PHP_VERSION_ID < 70400
     515             :             zval new_val;
     516             : 
     517             :             ZVAL_LONG(&new_val, 0);
     518             :             for (j = 0; j < num_frames; j++) {
     519             :                 ZEND_HASH_FILL_ADD(&new_val);
     520             :             }
     521             : #else
     522         150 :             for (j = 0; j < num_frames; j++) {
     523         120 :                 ZEND_HASH_FILL_SET_LONG(0);
     524         120 :                 ZEND_HASH_FILL_NEXT();
     525             :             }
     526             : #endif
     527          30 :         } ZEND_HASH_FILL_END();
     528             : 
     529             :         /* Write the values in reverse order */
     530         270 :         ZEND_HASH_REVERSE_FOREACH_VAL(ht_stack, zp_tmp) {
     531         120 :             ZVAL_LONG(zp_tmp, lp_frame_indexes[frame_index]);
     532         120 :             frame_index = log->frames[frame_index].prev_index;
     533             :         }
     534             :         ZEND_HASH_FOREACH_END();
     535             : 
     536          30 :         ZVAL_ARR(&z_tmp, ht_stack);
     537          30 :         zend_hash_next_index_insert_new(ht_samples, &z_tmp);
     538             : 
     539          30 :         ZVAL_LONG(&z_tmp, entry->event_count * log->period);
     540          30 :         zend_hash_next_index_insert_new(ht_weights, &z_tmp);
     541             :     }
     542             : 
     543             :     /* Build the profile array */
     544             :     zval z_profile;
     545           1 :     array_init(&z_profile);
     546           1 :     add_assoc_string(&z_profile, "type", "sampled");
     547           1 :     add_assoc_string(&z_profile, "name", "");
     548           1 :     add_assoc_string(&z_profile, "unit", "nanoseconds");
     549           1 :     add_assoc_long(&z_profile, "startValue", 0);
     550           1 :     add_assoc_long(&z_profile, "endValue", last_timestamp - first_timestamp);
     551           1 :     excimer_log_add_assoc_array(&z_profile, "samples", ht_samples);
     552           1 :     excimer_log_add_assoc_array(&z_profile, "weights", ht_weights);
     553             : 
     554             :     /* zp_data["profiles"] = [profile] */
     555             :     zval z_profiles;
     556           1 :     array_init(&z_profiles);
     557             :     add_next_index_zval(&z_profiles, &z_profile);
     558           1 :     add_assoc_zval(zp_data, "profiles", &z_profiles);
     559             : 
     560           1 :     efree(lp_frame_indexes);
     561           1 : }
     562             : 
     563         307 : HashTable *excimer_log_frame_to_array(excimer_log_frame *frame) {
     564         307 :     HashTable *ht_func = excimer_log_new_array(0);
     565             :     zval tmp;
     566             : 
     567         307 :     if (frame->filename) {
     568         307 :         ZVAL_STR_COPY(&tmp, frame->filename);
     569         307 :         zend_hash_add_new(ht_func, excimer_log_known_string(ZEND_STR_FILE), &tmp);
     570         307 :         ZVAL_LONG(&tmp, frame->lineno);
     571         307 :         zend_hash_add_new(ht_func, excimer_log_known_string(ZEND_STR_LINE), &tmp);
     572             :     }
     573             : 
     574         307 :     if (frame->class_name) {
     575          11 :         ZVAL_STR_COPY(&tmp, frame->class_name);
     576          11 :         zend_hash_add_new(ht_func, excimer_log_known_string(ZEND_STR_CLASS), &tmp);
     577             :     }
     578             : 
     579         307 :     if (frame->function_name) {
     580         216 :         ZVAL_STR_COPY(&tmp, frame->function_name);
     581         216 :         zend_hash_add_new(ht_func, excimer_log_known_string(ZEND_STR_FUNCTION), &tmp);
     582             :     }
     583             : 
     584         307 :     if (frame->closure_line) {
     585          42 :         zend_string *s = zend_string_init("closure_line", sizeof("closure_line") - 1, 0);
     586          42 :         ZVAL_LONG(&tmp, frame->closure_line);
     587          42 :         zend_hash_add_new(ht_func, s, &tmp);
     588             :         zend_string_delref(s);
     589             :     }
     590             : 
     591         307 :     return ht_func;
     592             : }
     593             : 
     594          90 : HashTable *excimer_log_trace_to_array(excimer_log *log, zend_long l_frame_index)
     595             : {
     596          90 :     HashTable *ht_trace = excimer_log_new_array(0);
     597          90 :     uint32_t frame_index = excimer_safe_uint32(l_frame_index);
     598         390 :     while (frame_index) {
     599         300 :         excimer_log_frame *frame = excimer_log_get_frame(log, frame_index);
     600         300 :         HashTable *ht_func = excimer_log_frame_to_array(frame);
     601             :         zval tmp;
     602             : 
     603         300 :         ZVAL_ARR(&tmp, ht_func);
     604         300 :         zend_hash_next_index_insert(ht_trace, &tmp);
     605             : 
     606         300 :         frame_index = frame->prev_index;
     607             :     }
     608             : 
     609          90 :     return ht_trace;
     610             : }
     611             : 
     612             : /**
     613             :  * ht[key] += term;
     614             :  */
     615         150 : static void excimer_log_array_incr(HashTable *ht, zend_string *sp_key, zend_long term)
     616             : {
     617         150 :     zval *zp_value = zend_hash_find(ht, sp_key);
     618         150 :     if (!zp_value) {
     619             :         zval z_tmp;
     620           0 :         ZVAL_LONG(&z_tmp, term);
     621           0 :         zend_hash_add_new(ht, sp_key, &z_tmp);
     622             :     } else {
     623         150 :         Z_LVAL_P(zp_value) += term;
     624             :     }
     625         150 : }
     626             : 
     627             : #if PHP_VERSION_ID < 80000
     628           8 : static int excimer_log_aggr_compare(const void *a, const void *b)
     629             : {
     630           8 :     zval *zp_a = &((Bucket*)a)->val;
     631           8 :     zval *zp_b = &((Bucket*)b)->val;
     632             : #else
     633             : static int excimer_log_aggr_compare(Bucket *a, Bucket *b)
     634             : {
     635             :     zval *zp_a = &a->val;
     636             :     zval *zp_b = &b->val;
     637             : #endif
     638             : 
     639           8 :     zval *zp_a_incl = zend_hash_str_find(Z_ARRVAL_P(zp_a), "inclusive", sizeof("inclusive")-1);
     640           8 :     zval *zp_b_incl = zend_hash_str_find(Z_ARRVAL_P(zp_b), "inclusive", sizeof("inclusive")-1);
     641             : 
     642           8 :     return ZEND_NORMALIZE_BOOL(Z_LVAL_P(zp_b_incl) - Z_LVAL_P(zp_a_incl));
     643             : }
     644             : 
     645           1 : HashTable *excimer_log_aggr_by_func(excimer_log *log)
     646             : {
     647           1 :     HashTable *ht_result = excimer_log_new_array(0);
     648           1 :     zend_string *sp_inclusive = zend_string_init("inclusive", sizeof("inclusive")-1, 0);
     649           1 :     zend_string *sp_self = zend_string_init("self", sizeof("self")-1, 0);
     650           1 :     HashTable *ht_unique_names = excimer_log_new_array(0);
     651             :     size_t entry_index;
     652             :     zval z_zero;
     653             : 
     654           1 :     ZVAL_LONG(&z_zero, 0);
     655             : 
     656          31 :     for (entry_index = 0; entry_index < log->entries_size; entry_index++) {
     657          30 :         excimer_log_entry *entry = excimer_log_get_entry(log, entry_index);
     658          30 :         uint32_t frame_index = entry->frame_index;
     659          30 :         int is_top = 1;
     660             : 
     661         150 :         while (frame_index) {
     662         120 :             excimer_log_frame *frame = excimer_log_get_frame(log, frame_index);
     663         120 :             smart_str ss_name = {NULL};
     664             :             zend_string *sp_name;
     665             :             zval *zp_info;
     666             :             zval z_tmp;
     667             : 
     668             :             /* Make a human-readable name */
     669         120 :             if (frame->closure_line != 0) {
     670             :                 /* Annotate anonymous functions with their source location.
     671             :                  * Example: {closure:/path/to/file.php(123)}
     672             :                  */
     673             :                 smart_str_appends(&ss_name, "{closure:");
     674          20 :                 smart_str_append(&ss_name, frame->filename);
     675          20 :                 excimer_log_smart_str_append_printf(&ss_name, "(%d)}", frame->closure_line);
     676         100 :             } else if (frame->function_name == NULL) {
     677             :                 /* For file-scope code, use the file name */
     678          30 :                 smart_str_append(&ss_name, frame->filename);
     679             :             } else {
     680          70 :                 if (frame->class_name) {
     681           5 :                     smart_str_append(&ss_name, frame->class_name);
     682             :                     smart_str_appends(&ss_name, "::");
     683             :                 }
     684          70 :                 smart_str_append(&ss_name, frame->function_name);
     685             :             }
     686         120 :             sp_name = excimer_log_smart_str_extract(&ss_name);
     687             : 
     688             :             /* If it is not in ht_result, add it, along with frame info */
     689         120 :             zp_info = zend_hash_find(ht_result, sp_name);
     690         120 :             if (!zp_info) {
     691           7 :                 ZVAL_ARR(&z_tmp, excimer_log_frame_to_array(frame));
     692           7 :                 zend_hash_add_new(Z_ARRVAL(z_tmp), sp_self, &z_zero);
     693           7 :                 zend_hash_add_new(Z_ARRVAL(z_tmp), sp_inclusive, &z_zero);
     694           7 :                 zp_info = zend_hash_add(ht_result, sp_name, &z_tmp);
     695             :             }
     696             : 
     697             :             /* If this is the top frame of a log entry, increment the "self" key */
     698         120 :             if (is_top) {
     699          30 :                 excimer_log_array_incr(Z_ARRVAL_P(zp_info), sp_self, entry->event_count);
     700             :             }
     701             : 
     702             :             /* If this is the first instance of a function in an entry, i.e.
     703             :              * counting recursive functions only once, increment the "inclusive" key */
     704         120 :             if (zend_hash_find(ht_unique_names, sp_name) == NULL) {
     705         120 :                 excimer_log_array_incr(Z_ARRVAL_P(zp_info), sp_inclusive, entry->event_count);
     706             :                 /* Add the function to the unique_names array */
     707         120 :                 zend_hash_add_new(ht_unique_names, sp_name, &z_zero);
     708             :             }
     709             : 
     710         120 :             is_top = 0;
     711         120 :             frame_index = frame->prev_index;
     712             :             zend_string_delref(sp_name);
     713             :         }
     714          30 :         zend_hash_clean(ht_unique_names);
     715             :     }
     716           1 :     zend_hash_destroy(ht_unique_names);
     717             :     zend_string_delref(sp_self);
     718             :     zend_string_delref(sp_inclusive);
     719             : 
     720             :     /* Sort the result in descending order by inclusive */
     721           1 :     zend_hash_sort(ht_result, excimer_log_aggr_compare, 0);
     722             : 
     723           1 :     return ht_result;
     724             : }

Generated by: LCOV version 1.13