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 : }
|