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 : static inline void excimer_log_add_assoc_array(zval *dest, const char *key, HashTable *arr)
84 : {
85 : zval z_tmp;
86 : ZVAL_ARR(&z_tmp, arr);
87 : add_assoc_zval(dest, key, &z_tmp);
88 : }
89 : #endif
90 :
91 : /* }}} */
92 :
93 8 : void excimer_log_init(excimer_log *log)
94 : {
95 8 : log->entries_size = 0;
96 8 : log->entries = NULL;
97 8 : log->frames = ecalloc(1, sizeof(excimer_log_frame));
98 8 : log->frames_size = 1;
99 8 : log->reverse_frames = excimer_log_new_array(0);
100 8 : log->epoch = 0;
101 8 : log->event_count = 0;
102 8 : }
103 :
104 8 : void excimer_log_destroy(excimer_log *log)
105 : {
106 8 : if (log->entries) {
107 4 : efree(log->entries);
108 : }
109 8 : if (log->frames) {
110 : int i;
111 37 : for (i = 0; i < log->frames_size; i++) {
112 29 : if (log->frames[i].filename) {
113 21 : zend_string_delref(log->frames[i].filename);
114 : }
115 29 : if (log->frames[i].class_name) {
116 1 : zend_string_delref(log->frames[i].class_name);
117 : }
118 29 : if (log->frames[i].function_name) {
119 18 : zend_string_delref(log->frames[i].function_name);
120 : }
121 : }
122 8 : efree(log->frames);
123 : }
124 8 : zend_hash_destroy(log->reverse_frames);
125 8 : efree(log->reverse_frames);
126 8 : }
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 4 : void excimer_log_copy_options(excimer_log *dest, excimer_log *src)
134 : {
135 4 : dest->max_depth = src->max_depth;
136 4 : dest->epoch = src->epoch;
137 4 : dest->period = src->period;
138 4 : }
139 :
140 62 : void excimer_log_add(excimer_log *log, zend_execute_data *execute_data,
141 : zend_long event_count, uint64_t timestamp)
142 : {
143 62 : uint32_t frame_index = excimer_log_find_or_add_frame(log, execute_data, 0);
144 : excimer_log_entry *entry;
145 :
146 62 : log->entries = safe_erealloc(log->entries, log->entries_size + 1,
147 : sizeof(excimer_log_entry), 0);
148 62 : entry = &log->entries[log->entries_size++];
149 62 : entry->frame_index = frame_index;
150 62 : entry->event_count = event_count;
151 62 : log->event_count += event_count;
152 62 : entry->timestamp = timestamp;
153 62 : }
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 188 : 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 188 : if (!execute_data) {
191 0 : return 0;
192 188 : } else if (!execute_data->prev_execute_data) {
193 61 : prev_index = 0;
194 127 : } else if (log->max_depth && depth >= log->max_depth) {
195 1 : prev_index = excimer_log_get_truncation_marker(log);
196 : } else {
197 126 : prev_index = excimer_log_find_or_add_frame(log,
198 : execute_data->prev_execute_data, depth + 1);
199 : }
200 188 : if (!execute_data->func
201 188 : || !ZEND_USER_CODE(execute_data->func->common.type))
202 : {
203 0 : return prev_index;
204 : } else {
205 188 : zend_function *func = execute_data->func;
206 188 : excimer_log_frame frame = {NULL};
207 188 : smart_str ss_key = {NULL};
208 : zend_string *str_key;
209 : zval* zp_index;
210 :
211 188 : frame.filename = func->op_array.filename;
212 188 : zend_string_addref(frame.filename);
213 :
214 188 : 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 188 : if (func->common.function_name) {
220 127 : frame.function_name = func->common.function_name;
221 127 : zend_string_addref(frame.function_name);
222 : }
223 :
224 188 : if (func->op_array.fn_flags & ZEND_ACC_CLOSURE) {
225 16 : frame.closure_line = func->op_array.line_start;
226 : }
227 :
228 188 : frame.lineno = execute_data->opline->lineno;
229 188 : frame.prev_index = prev_index;
230 :
231 : /* Make a key for reverse lookup */
232 188 : smart_str_append(&ss_key, frame.filename);
233 : smart_str_appendc(&ss_key, '\0');
234 188 : excimer_log_smart_str_append_printf(&ss_key, "%d", frame.lineno);
235 : smart_str_appendc(&ss_key, '\0');
236 188 : excimer_log_smart_str_append_printf(&ss_key, "%d", frame.prev_index);
237 188 : str_key = excimer_log_smart_str_extract(&ss_key);
238 :
239 : /* Look for a matching frame in the reverse hashtable */
240 188 : zp_index = zend_hash_find(log->reverse_frames, str_key);
241 188 : 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 20 : ZVAL_LONG(&z_new_index, log->frames_size);
257 20 : zend_hash_add(log->reverse_frames, str_key, &z_new_index);
258 20 : log->frames = safe_erealloc(log->frames, log->frames_size + 1,
259 : sizeof(excimer_log_frame), 0);
260 20 : memcpy(&log->frames[log->frames_size++], &frame, sizeof(excimer_log_frame));
261 :
262 : zend_string_delref(str_key);
263 20 : 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 421 : excimer_log_entry *excimer_log_get_entry(excimer_log *log, zend_long i)
274 : {
275 421 : if (i >= 0 && i < log->entries_size) {
276 421 : return &log->entries[i];
277 : } else {
278 0 : return NULL;
279 : }
280 : }
281 :
282 445 : excimer_log_frame *excimer_log_get_frame(excimer_log *log, zend_long i)
283 : {
284 445 : if (i > 0 && i < log->frames_size) {
285 445 : 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 382 : for (i = 0; i < ZSTR_LEN(src); i++) {
297 339 : char c = ZSTR_VAL(src)[i];
298 339 : if (c == ' ' || c == '\0') {
299 0 : c = '_';
300 : }
301 339 : 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 21 : 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 21 : 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 : zend_string_release(str_key);
485 : }
486 1 : zend_array_destroy(ht_indexes_by_key);
487 :
488 : /* zp_data["shared"] = ["frames" => ht_frames] */
489 : zval z_shared;
490 1 : array_init(&z_shared);
491 1 : excimer_log_add_assoc_array(&z_shared, "frames", ht_frames);
492 1 : add_assoc_zval(zp_data, "shared", &z_shared);
493 :
494 : /* Build the samples and weights arrays */
495 1 : HashTable *ht_samples = excimer_log_new_array(log->entries_size);
496 1 : HashTable *ht_weights = excimer_log_new_array(log->entries_size);
497 1 : uint64_t first_timestamp = 0;
498 1 : uint64_t last_timestamp = 0;
499 31 : for (i = 0; i < log->entries_size; i++) {
500 30 : excimer_log_entry *entry = &log->entries[i];
501 30 : uint32_t frame_index = entry->frame_index;
502 :
503 30 : if (i == 0) {
504 1 : first_timestamp = entry->timestamp;
505 : }
506 30 : last_timestamp = entry->timestamp;
507 :
508 30 : uint32_t num_frames = excimer_log_count_frames(log, frame_index);
509 : uint32_t j;
510 :
511 : /* Create the array with ZEND_HASH_FILL_PACKED. This is just a fast way
512 : * to get it into the right state, with num_frames elements. */
513 30 : HashTable *ht_stack = excimer_log_new_array(num_frames);
514 30 : zend_hash_extend(ht_stack, num_frames, 1);
515 30 : ZEND_HASH_FILL_PACKED(ht_stack) {
516 : #if PHP_VERSION_ID < 70400
517 : zval new_val;
518 :
519 : ZVAL_LONG(&new_val, 0);
520 : for (j = 0; j < num_frames; j++) {
521 : ZEND_HASH_FILL_ADD(&new_val);
522 : }
523 : #else
524 150 : for (j = 0; j < num_frames; j++) {
525 120 : ZEND_HASH_FILL_SET_LONG(0);
526 120 : ZEND_HASH_FILL_NEXT();
527 : }
528 : #endif
529 30 : } ZEND_HASH_FILL_END();
530 :
531 : /* Write the values in reverse order */
532 270 : ZEND_HASH_REVERSE_FOREACH_VAL(ht_stack, zp_tmp) {
533 120 : ZVAL_LONG(zp_tmp, lp_frame_indexes[frame_index]);
534 120 : frame_index = log->frames[frame_index].prev_index;
535 : }
536 : ZEND_HASH_FOREACH_END();
537 :
538 30 : ZVAL_ARR(&z_tmp, ht_stack);
539 30 : zend_hash_next_index_insert_new(ht_samples, &z_tmp);
540 :
541 30 : ZVAL_LONG(&z_tmp, entry->event_count * log->period);
542 30 : zend_hash_next_index_insert_new(ht_weights, &z_tmp);
543 : }
544 :
545 : /* Build the profile array */
546 : zval z_profile;
547 1 : array_init(&z_profile);
548 1 : add_assoc_string(&z_profile, "type", "sampled");
549 1 : add_assoc_string(&z_profile, "name", "");
550 1 : add_assoc_string(&z_profile, "unit", "nanoseconds");
551 1 : add_assoc_long(&z_profile, "startValue", 0);
552 1 : add_assoc_long(&z_profile, "endValue", last_timestamp - first_timestamp);
553 1 : excimer_log_add_assoc_array(&z_profile, "samples", ht_samples);
554 1 : excimer_log_add_assoc_array(&z_profile, "weights", ht_weights);
555 :
556 : /* zp_data["profiles"] = [profile] */
557 : zval z_profiles;
558 1 : array_init(&z_profiles);
559 : add_next_index_zval(&z_profiles, &z_profile);
560 1 : add_assoc_zval(zp_data, "profiles", &z_profiles);
561 :
562 1 : efree(lp_frame_indexes);
563 1 : }
564 :
565 309 : HashTable *excimer_log_frame_to_array(excimer_log_frame *frame) {
566 309 : HashTable *ht_func = excimer_log_new_array(0);
567 : zval tmp;
568 :
569 309 : if (frame->filename) {
570 927 : ZVAL_STR_COPY(&tmp, frame->filename);
571 309 : zend_hash_add_new(ht_func, excimer_log_known_string(ZEND_STR_FILE), &tmp);
572 309 : ZVAL_LONG(&tmp, frame->lineno);
573 309 : zend_hash_add_new(ht_func, excimer_log_known_string(ZEND_STR_LINE), &tmp);
574 : }
575 :
576 309 : if (frame->class_name) {
577 22 : ZVAL_STR_COPY(&tmp, frame->class_name);
578 11 : zend_hash_add_new(ht_func, excimer_log_known_string(ZEND_STR_CLASS), &tmp);
579 : }
580 :
581 309 : if (frame->function_name) {
582 651 : ZVAL_STR_COPY(&tmp, frame->function_name);
583 217 : zend_hash_add_new(ht_func, excimer_log_known_string(ZEND_STR_FUNCTION), &tmp);
584 : }
585 :
586 309 : if (frame->closure_line) {
587 34 : zend_string *s = zend_string_init("closure_line", sizeof("closure_line") - 1, 0);
588 34 : ZVAL_LONG(&tmp, frame->closure_line);
589 34 : zend_hash_add_new(ht_func, s, &tmp);
590 : zend_string_delref(s);
591 : }
592 :
593 309 : return ht_func;
594 : }
595 :
596 91 : HashTable *excimer_log_trace_to_array(excimer_log *log, zend_long l_frame_index)
597 : {
598 91 : HashTable *ht_trace = excimer_log_new_array(0);
599 91 : uint32_t frame_index = excimer_safe_uint32(l_frame_index);
600 393 : while (frame_index) {
601 302 : excimer_log_frame *frame = excimer_log_get_frame(log, frame_index);
602 302 : HashTable *ht_func = excimer_log_frame_to_array(frame);
603 : zval tmp;
604 :
605 302 : ZVAL_ARR(&tmp, ht_func);
606 302 : zend_hash_next_index_insert(ht_trace, &tmp);
607 :
608 302 : frame_index = frame->prev_index;
609 : }
610 :
611 91 : return ht_trace;
612 : }
613 :
614 : /**
615 : * ht[key] += term;
616 : */
617 150 : static void excimer_log_array_incr(HashTable *ht, zend_string *sp_key, zend_long term)
618 : {
619 150 : zval *zp_value = zend_hash_find(ht, sp_key);
620 150 : if (!zp_value) {
621 : zval z_tmp;
622 0 : ZVAL_LONG(&z_tmp, term);
623 0 : zend_hash_add_new(ht, sp_key, &z_tmp);
624 : } else {
625 150 : Z_LVAL_P(zp_value) += term;
626 : }
627 150 : }
628 :
629 : #if PHP_VERSION_ID < 80000
630 : static int excimer_log_aggr_compare(const void *a, const void *b)
631 : {
632 : zval *zp_a = &((Bucket*)a)->val;
633 : zval *zp_b = &((Bucket*)b)->val;
634 : #else
635 8 : static int excimer_log_aggr_compare(Bucket *a, Bucket *b)
636 : {
637 8 : zval *zp_a = &a->val;
638 8 : zval *zp_b = &b->val;
639 : #endif
640 :
641 8 : zval *zp_a_incl = zend_hash_str_find(Z_ARRVAL_P(zp_a), "inclusive", sizeof("inclusive")-1);
642 8 : zval *zp_b_incl = zend_hash_str_find(Z_ARRVAL_P(zp_b), "inclusive", sizeof("inclusive")-1);
643 :
644 8 : return ZEND_NORMALIZE_BOOL(Z_LVAL_P(zp_b_incl) - Z_LVAL_P(zp_a_incl));
645 : }
646 :
647 1 : HashTable *excimer_log_aggr_by_func(excimer_log *log)
648 : {
649 1 : HashTable *ht_result = excimer_log_new_array(0);
650 1 : zend_string *sp_inclusive = zend_string_init("inclusive", sizeof("inclusive")-1, 0);
651 1 : zend_string *sp_self = zend_string_init("self", sizeof("self")-1, 0);
652 1 : HashTable *ht_unique_names = excimer_log_new_array(0);
653 : size_t entry_index;
654 : zval z_zero;
655 :
656 1 : ZVAL_LONG(&z_zero, 0);
657 :
658 31 : for (entry_index = 0; entry_index < log->entries_size; entry_index++) {
659 30 : excimer_log_entry *entry = excimer_log_get_entry(log, entry_index);
660 30 : uint32_t frame_index = entry->frame_index;
661 30 : int is_top = 1;
662 :
663 150 : while (frame_index) {
664 120 : excimer_log_frame *frame = excimer_log_get_frame(log, frame_index);
665 120 : smart_str ss_name = {NULL};
666 : zend_string *sp_name;
667 : zval *zp_info;
668 : zval z_tmp;
669 :
670 : /* Make a human-readable name */
671 120 : if (frame->closure_line != 0) {
672 : /* Annotate anonymous functions with their source location.
673 : * Example: {closure:/path/to/file.php(123)}
674 : */
675 : smart_str_appends(&ss_name, "{closure:");
676 16 : smart_str_append(&ss_name, frame->filename);
677 16 : excimer_log_smart_str_append_printf(&ss_name, "(%d)}", frame->closure_line);
678 104 : } else if (frame->function_name == NULL) {
679 : /* For file-scope code, use the file name */
680 30 : smart_str_append(&ss_name, frame->filename);
681 : } else {
682 74 : if (frame->class_name) {
683 5 : smart_str_append(&ss_name, frame->class_name);
684 : smart_str_appends(&ss_name, "::");
685 : }
686 74 : smart_str_append(&ss_name, frame->function_name);
687 : }
688 120 : sp_name = excimer_log_smart_str_extract(&ss_name);
689 :
690 : /* If it is not in ht_result, add it, along with frame info */
691 120 : zp_info = zend_hash_find(ht_result, sp_name);
692 120 : if (!zp_info) {
693 7 : ZVAL_ARR(&z_tmp, excimer_log_frame_to_array(frame));
694 7 : zend_hash_add_new(Z_ARRVAL(z_tmp), sp_self, &z_zero);
695 7 : zend_hash_add_new(Z_ARRVAL(z_tmp), sp_inclusive, &z_zero);
696 7 : zp_info = zend_hash_add(ht_result, sp_name, &z_tmp);
697 : }
698 :
699 : /* If this is the top frame of a log entry, increment the "self" key */
700 120 : if (is_top) {
701 30 : excimer_log_array_incr(Z_ARRVAL_P(zp_info), sp_self, entry->event_count);
702 : }
703 :
704 : /* If this is the first instance of a function in an entry, i.e.
705 : * counting recursive functions only once, increment the "inclusive" key */
706 120 : if (zend_hash_find(ht_unique_names, sp_name) == NULL) {
707 120 : excimer_log_array_incr(Z_ARRVAL_P(zp_info), sp_inclusive, entry->event_count);
708 : /* Add the function to the unique_names array */
709 120 : zend_hash_add_new(ht_unique_names, sp_name, &z_zero);
710 : }
711 :
712 120 : is_top = 0;
713 120 : frame_index = frame->prev_index;
714 : zend_string_delref(sp_name);
715 : }
716 30 : zend_hash_clean(ht_unique_names);
717 : }
718 1 : zend_hash_destroy(ht_unique_names);
719 : zend_string_delref(sp_self);
720 : zend_string_delref(sp_inclusive);
721 :
722 : /* Sort the result in descending order by inclusive */
723 1 : zend_hash_sort(ht_result, excimer_log_aggr_compare, 0);
724 :
725 1 : return ht_result;
726 : }
|