profile.c (28303B)
1 #include <assert.h> 2 #include <math.h> 3 #include <stdbool.h> 4 #include <stdint.h> 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <string.h> 8 #include <uv.h> 9 10 #include "nvim/ascii_defs.h" 11 #include "nvim/charset.h" 12 #include "nvim/cmdexpand_defs.h" 13 #include "nvim/debugger.h" 14 #include "nvim/errors.h" 15 #include "nvim/eval/typval_defs.h" 16 #include "nvim/eval/userfunc.h" 17 #include "nvim/eval/vars.h" 18 #include "nvim/ex_cmds_defs.h" 19 #include "nvim/fileio.h" 20 #include "nvim/garray.h" 21 #include "nvim/gettext_defs.h" 22 #include "nvim/globals.h" 23 #include "nvim/hashtab.h" 24 #include "nvim/hashtab_defs.h" 25 #include "nvim/keycodes.h" 26 #include "nvim/memory.h" 27 #include "nvim/message.h" 28 #include "nvim/os/fs.h" 29 #include "nvim/os/os.h" 30 #include "nvim/os/time.h" 31 #include "nvim/pos_defs.h" 32 #include "nvim/profile.h" 33 #include "nvim/runtime.h" 34 #include "nvim/types_defs.h" 35 36 #include "profile.c.generated.h" 37 38 /// Struct used in sn_prl_ga for every line of a script. 39 typedef struct { 40 int snp_count; ///< nr of times line was executed 41 proftime_T sn_prl_total; ///< time spent in a line + children 42 proftime_T sn_prl_self; ///< time spent in a line itself 43 } sn_prl_T; 44 45 #define PRL_ITEM(si, idx) (((sn_prl_T *)(si)->sn_prl_ga.ga_data)[(idx)]) 46 47 static proftime_T prof_wait_time; 48 static char *startuptime_buf = NULL; // --startuptime buffer 49 50 /// Gets the current time. 51 /// 52 /// @return the current time 53 proftime_T profile_start(void) FUNC_ATTR_WARN_UNUSED_RESULT 54 { 55 return os_hrtime(); 56 } 57 58 /// Computes the time elapsed. 59 /// 60 /// @return Elapsed time from `tm` until now. 61 proftime_T profile_end(proftime_T tm) FUNC_ATTR_WARN_UNUSED_RESULT 62 { 63 return profile_sub(os_hrtime(), tm); 64 } 65 66 /// Gets a string representing time `tm`. 67 /// 68 /// @warning Do not modify or free this string, not multithread-safe. 69 /// 70 /// @param tm Time 71 /// @return Static string representing `tm` in the form "seconds.microseconds". 72 const char *profile_msg(proftime_T tm) FUNC_ATTR_WARN_UNUSED_RESULT 73 { 74 static char buf[50]; 75 snprintf(buf, sizeof(buf), "%10.6lf", 76 (double)profile_signed(tm) / 1000000000.0); 77 return buf; 78 } 79 80 /// Gets the time `msec` into the future. 81 /// 82 /// @param msec milliseconds, the maximum number of milliseconds is 83 /// (2^63 / 10^6) - 1 = 9.223372e+12. 84 /// @return if msec > 0, returns the time msec past now. Otherwise returns 85 /// the zero time. 86 proftime_T profile_setlimit(int64_t msec) FUNC_ATTR_WARN_UNUSED_RESULT 87 { 88 if (msec <= 0) { 89 // no limit 90 return profile_zero(); 91 } 92 assert(msec <= (INT64_MAX / 1000000LL) - 1); 93 proftime_T nsec = (proftime_T)msec * 1000000ULL; 94 return os_hrtime() + nsec; 95 } 96 97 /// Checks if current time has passed `tm`. 98 /// 99 /// @return true if the current time is past `tm`, false if not or if the 100 /// timer was not set. 101 bool profile_passed_limit(proftime_T tm) FUNC_ATTR_WARN_UNUSED_RESULT 102 { 103 if (tm == 0) { 104 // timer was not set 105 return false; 106 } 107 108 return profile_cmp(os_hrtime(), tm) < 0; 109 } 110 111 /// Gets the zero time. 112 /// 113 /// @return the zero time 114 proftime_T profile_zero(void) FUNC_ATTR_CONST 115 { 116 return 0; 117 } 118 119 /// Divides time `tm` by `count`. 120 /// 121 /// @return 0 if count <= 0, otherwise tm / count 122 proftime_T profile_divide(proftime_T tm, int count) FUNC_ATTR_CONST 123 { 124 if (count <= 0) { 125 return profile_zero(); 126 } 127 128 return (proftime_T)round((double)tm / (double)count); 129 } 130 131 /// Adds time `tm2` to `tm1`. 132 /// 133 /// @return `tm1` + `tm2` 134 proftime_T profile_add(proftime_T tm1, proftime_T tm2) FUNC_ATTR_CONST 135 { 136 return tm1 + tm2; 137 } 138 139 /// Subtracts time `tm2` from `tm1`. 140 /// 141 /// Unsigned overflow (wraparound) occurs if `tm2` is greater than `tm1`. 142 /// Use `profile_signed()` to get the signed integer value. 143 /// 144 /// @see profile_signed 145 /// 146 /// @return `tm1` - `tm2` 147 proftime_T profile_sub(proftime_T tm1, proftime_T tm2) FUNC_ATTR_CONST 148 { 149 return tm1 - tm2; 150 } 151 152 /// Adds the `self` time from the total time and the `children` time. 153 /// 154 /// @return if `total` <= `children`, then self, otherwise `self` + `total` - 155 /// `children` 156 proftime_T profile_self(proftime_T self, proftime_T total, proftime_T children) 157 FUNC_ATTR_CONST 158 { 159 // check that the result won't be negative, which can happen with 160 // recursive calls. 161 if (total <= children) { 162 return self; 163 } 164 165 // add the total time to self and subtract the children's time from self 166 return profile_sub(profile_add(self, total), children); 167 } 168 169 /// Gets the current waittime. 170 /// 171 /// @return the current waittime 172 static proftime_T profile_get_wait(void) FUNC_ATTR_PURE 173 { 174 return prof_wait_time; 175 } 176 177 /// Sets the current waittime. 178 void profile_set_wait(proftime_T wait) 179 { 180 prof_wait_time = wait; 181 } 182 183 /// Subtracts the passed waittime since `tm`. 184 /// 185 /// @return `tma` - (waittime - `tm`) 186 proftime_T profile_sub_wait(proftime_T tm, proftime_T tma) FUNC_ATTR_PURE 187 { 188 proftime_T tm3 = profile_sub(profile_get_wait(), tm); 189 return profile_sub(tma, tm3); 190 } 191 192 /// Checks if time `tm1` is equal to `tm2`. 193 /// 194 /// @return true if `tm1` == `tm2` 195 static bool profile_equal(proftime_T tm1, proftime_T tm2) FUNC_ATTR_CONST 196 { 197 return tm1 == tm2; 198 } 199 200 /// Converts time duration `tm` (`profile_sub` result) to a signed integer. 201 /// 202 /// @return signed representation of the given time value 203 int64_t profile_signed(proftime_T tm) 204 FUNC_ATTR_CONST 205 { 206 // (tm > INT64_MAX) is >=150 years, so we can assume it was produced by 207 // arithmetic of two proftime_T values. For human-readable representation 208 // (and Vim-compat) we want the difference after unsigned wraparound. #10452 209 return (tm <= INT64_MAX) ? (int64_t)tm : -(int64_t)(UINT64_MAX - tm); 210 } 211 212 /// Compares profiling times. 213 /// 214 /// Times `tm1` and `tm2` must be less than 150 years apart. 215 /// 216 /// @return <0: `tm2` < `tm1` 217 /// 0: `tm2` == `tm1` 218 /// >0: `tm2` > `tm1` 219 int profile_cmp(proftime_T tm1, proftime_T tm2) FUNC_ATTR_CONST 220 { 221 if (tm1 == tm2) { 222 return 0; 223 } 224 return profile_signed(tm2 - tm1) < 0 ? -1 : 1; 225 } 226 227 static char *profile_fname = NULL; 228 229 /// Reset all profiling information. 230 void profile_reset(void) 231 { 232 // Reset sourced files. 233 for (int id = 1; id <= script_items.ga_len; id++) { 234 scriptitem_T *si = SCRIPT_ITEM(id); 235 if (si->sn_prof_on) { 236 si->sn_prof_on = false; 237 si->sn_pr_force = false; 238 si->sn_pr_child = profile_zero(); 239 si->sn_pr_nest = 0; 240 si->sn_pr_count = 0; 241 si->sn_pr_total = profile_zero(); 242 si->sn_pr_self = profile_zero(); 243 si->sn_pr_start = profile_zero(); 244 si->sn_pr_children = profile_zero(); 245 ga_clear(&si->sn_prl_ga); 246 si->sn_prl_start = profile_zero(); 247 si->sn_prl_children = profile_zero(); 248 si->sn_prl_wait = profile_zero(); 249 si->sn_prl_idx = -1; 250 si->sn_prl_execed = 0; 251 } 252 } 253 254 // Reset functions. 255 hashtab_T *const functbl = func_tbl_get(); 256 size_t todo = functbl->ht_used; 257 hashitem_T *hi = functbl->ht_array; 258 259 for (; todo > 0; hi++) { 260 if (!HASHITEM_EMPTY(hi)) { 261 todo--; 262 ufunc_T *uf = HI2UF(hi); 263 if (uf->uf_prof_initialized) { 264 uf->uf_profiling = 0; 265 uf->uf_tm_count = 0; 266 uf->uf_tm_total = profile_zero(); 267 uf->uf_tm_self = profile_zero(); 268 uf->uf_tm_children = profile_zero(); 269 270 for (int i = 0; i < uf->uf_lines.ga_len; i++) { 271 uf->uf_tml_count[i] = 0; 272 uf->uf_tml_total[i] = uf->uf_tml_self[i] = 0; 273 } 274 275 uf->uf_tml_start = profile_zero(); 276 uf->uf_tml_children = profile_zero(); 277 uf->uf_tml_wait = profile_zero(); 278 uf->uf_tml_idx = -1; 279 uf->uf_tml_execed = 0; 280 } 281 } 282 } 283 284 XFREE_CLEAR(profile_fname); 285 } 286 287 /// ":profile cmd args" 288 void ex_profile(exarg_T *eap) 289 { 290 static proftime_T pause_time; 291 292 char *e = skiptowhite(eap->arg); 293 int len = (int)(e - eap->arg); 294 e = skipwhite(e); 295 296 if (len == 5 && strncmp(eap->arg, "start", 5) == 0 && *e != NUL) { 297 xfree(profile_fname); 298 profile_fname = expand_env_save_opt(e, true); 299 do_profiling = PROF_YES; 300 profile_set_wait(profile_zero()); 301 set_vim_var_nr(VV_PROFILING, 1); 302 } else if (do_profiling == PROF_NONE) { 303 emsg(_("E750: First use \":profile start {fname}\"")); 304 } else if (strcmp(eap->arg, "stop") == 0) { 305 profile_dump(); 306 do_profiling = PROF_NONE; 307 set_vim_var_nr(VV_PROFILING, 0); 308 profile_reset(); 309 } else if (strcmp(eap->arg, "pause") == 0) { 310 if (do_profiling == PROF_YES) { 311 pause_time = profile_start(); 312 } 313 do_profiling = PROF_PAUSED; 314 } else if (strcmp(eap->arg, "continue") == 0) { 315 if (do_profiling == PROF_PAUSED) { 316 pause_time = profile_end(pause_time); 317 profile_set_wait(profile_add(profile_get_wait(), pause_time)); 318 } 319 do_profiling = PROF_YES; 320 } else if (strcmp(eap->arg, "dump") == 0) { 321 profile_dump(); 322 } else { 323 // The rest is similar to ":breakadd". 324 ex_breakadd(eap); 325 } 326 } 327 328 /// Command line expansion for :profile. 329 static enum { 330 PEXP_SUBCMD, ///< expand :profile sub-commands 331 PEXP_FUNC, ///< expand :profile func {funcname} 332 } pexpand_what; 333 334 static char *pexpand_cmds[] = { 335 "continue", 336 "dump", 337 "file", 338 "func", 339 "pause", 340 "start", 341 "stop", 342 NULL 343 }; 344 345 /// Function given to ExpandGeneric() to obtain the profile command 346 /// specific expansion. 347 char *get_profile_name(expand_T *xp, int idx) 348 FUNC_ATTR_PURE 349 { 350 switch (pexpand_what) { 351 case PEXP_SUBCMD: 352 return pexpand_cmds[idx]; 353 default: 354 return NULL; 355 } 356 } 357 358 /// Handle command line completion for :profile command. 359 void set_context_in_profile_cmd(expand_T *xp, const char *arg) 360 { 361 // Default: expand subcommands. 362 xp->xp_context = EXPAND_PROFILE; 363 pexpand_what = PEXP_SUBCMD; 364 xp->xp_pattern = (char *)arg; 365 366 char *const end_subcmd = skiptowhite(arg); 367 if (*end_subcmd == NUL) { 368 return; 369 } 370 371 if ((end_subcmd - arg == 5 && strncmp(arg, "start", 5) == 0) 372 || (end_subcmd - arg == 4 && strncmp(arg, "file", 4) == 0)) { 373 xp->xp_context = EXPAND_FILES; 374 xp->xp_pattern = skipwhite(end_subcmd); 375 return; 376 } else if (end_subcmd - arg == 4 && strncmp(arg, "func", 4) == 0) { 377 xp->xp_context = EXPAND_USER_FUNC; 378 xp->xp_pattern = skipwhite(end_subcmd); 379 return; 380 } 381 382 xp->xp_context = EXPAND_NOTHING; 383 } 384 385 static proftime_T wait_time; 386 387 /// Called when starting to wait for the user to type a character. 388 void prof_input_start(void) 389 { 390 wait_time = profile_start(); 391 } 392 393 /// Called when finished waiting for the user to type a character. 394 void prof_input_end(void) 395 { 396 wait_time = profile_end(wait_time); 397 profile_set_wait(profile_add(profile_get_wait(), wait_time)); 398 } 399 400 /// @return true when a function defined in the current script should be 401 /// profiled. 402 bool prof_def_func(void) 403 FUNC_ATTR_PURE 404 { 405 if (current_sctx.sc_sid > 0) { 406 return SCRIPT_ITEM(current_sctx.sc_sid)->sn_pr_force; 407 } 408 return false; 409 } 410 411 /// Print the count and times for one function or function line. 412 /// 413 /// @param prefer_self when equal print only self time 414 static void prof_func_line(FILE *fd, int count, const proftime_T *total, const proftime_T *self, 415 bool prefer_self) 416 { 417 if (count > 0) { 418 fprintf(fd, "%5d ", count); 419 if (prefer_self && profile_equal(*total, *self)) { 420 fprintf(fd, " "); 421 } else { 422 fprintf(fd, "%s ", profile_msg(*total)); 423 } 424 if (!prefer_self && profile_equal(*total, *self)) { 425 fprintf(fd, " "); 426 } else { 427 fprintf(fd, "%s ", profile_msg(*self)); 428 } 429 } else { 430 fprintf(fd, " "); 431 } 432 } 433 434 /// @param prefer_self when equal print only self time 435 static void prof_sort_list(FILE *fd, ufunc_T **sorttab, int st_len, char *title, bool prefer_self) 436 { 437 fprintf(fd, "FUNCTIONS SORTED ON %s TIME\n", title); 438 fprintf(fd, "count total (s) self (s) function\n"); 439 for (int i = 0; i < 20 && i < st_len; i++) { 440 ufunc_T *fp = sorttab[i]; 441 prof_func_line(fd, fp->uf_tm_count, &fp->uf_tm_total, &fp->uf_tm_self, 442 prefer_self); 443 if ((uint8_t)fp->uf_name[0] == K_SPECIAL) { 444 fprintf(fd, " <SNR>%s()\n", fp->uf_name + 3); 445 } else { 446 fprintf(fd, " %s()\n", fp->uf_name); 447 } 448 } 449 fprintf(fd, "\n"); 450 } 451 452 /// Compare function for total time sorting. 453 static int prof_total_cmp(const void *s1, const void *s2) 454 { 455 ufunc_T *p1 = *(ufunc_T **)s1; 456 ufunc_T *p2 = *(ufunc_T **)s2; 457 return profile_cmp(p1->uf_tm_total, p2->uf_tm_total); 458 } 459 460 /// Compare function for self time sorting. 461 static int prof_self_cmp(const void *s1, const void *s2) 462 { 463 ufunc_T *p1 = *(ufunc_T **)s1; 464 ufunc_T *p2 = *(ufunc_T **)s2; 465 return profile_cmp(p1->uf_tm_self, p2->uf_tm_self); 466 } 467 468 /// Start profiling function "fp". 469 void func_do_profile(ufunc_T *fp) 470 { 471 int len = fp->uf_lines.ga_len; 472 473 if (!fp->uf_prof_initialized) { 474 if (len == 0) { 475 len = 1; // avoid getting error for allocating zero bytes 476 } 477 fp->uf_tm_count = 0; 478 fp->uf_tm_self = profile_zero(); 479 fp->uf_tm_total = profile_zero(); 480 481 if (fp->uf_tml_count == NULL) { 482 fp->uf_tml_count = xcalloc((size_t)len, sizeof(int)); 483 } 484 485 if (fp->uf_tml_total == NULL) { 486 fp->uf_tml_total = xcalloc((size_t)len, sizeof(proftime_T)); 487 } 488 489 if (fp->uf_tml_self == NULL) { 490 fp->uf_tml_self = xcalloc((size_t)len, sizeof(proftime_T)); 491 } 492 493 fp->uf_tml_idx = -1; 494 fp->uf_prof_initialized = true; 495 } 496 497 fp->uf_profiling = true; 498 } 499 500 /// Prepare profiling for entering a child or something else that is not 501 /// counted for the script/function itself. 502 /// Should always be called in pair with prof_child_exit(). 503 /// 504 /// @param tm place to store waittime 505 void prof_child_enter(proftime_T *tm) 506 { 507 funccall_T *fc = get_current_funccal(); 508 509 if (fc != NULL && fc->fc_func->uf_profiling) { 510 fc->fc_prof_child = profile_start(); 511 } 512 513 script_prof_save(tm); 514 } 515 516 /// Take care of time spent in a child. 517 /// Should always be called after prof_child_enter(). 518 /// 519 /// @param tm where waittime was stored 520 void prof_child_exit(proftime_T *tm) 521 { 522 funccall_T *fc = get_current_funccal(); 523 524 if (fc != NULL && fc->fc_func->uf_profiling) { 525 fc->fc_prof_child = profile_end(fc->fc_prof_child); 526 // don't count waiting time 527 fc->fc_prof_child = profile_sub_wait(*tm, fc->fc_prof_child); 528 fc->fc_func->uf_tm_children = 529 profile_add(fc->fc_func->uf_tm_children, fc->fc_prof_child); 530 fc->fc_func->uf_tml_children = 531 profile_add(fc->fc_func->uf_tml_children, fc->fc_prof_child); 532 } 533 script_prof_restore(tm); 534 } 535 536 /// Called when starting to read a function line. 537 /// "sourcing_lnum" must be correct! 538 /// When skipping lines it may not actually be executed, but we won't find out 539 /// until later and we need to store the time now. 540 void func_line_start(void *cookie) 541 { 542 funccall_T *fcp = (funccall_T *)cookie; 543 ufunc_T *fp = fcp->fc_func; 544 545 if (fp->uf_profiling && SOURCING_LNUM >= 1 && SOURCING_LNUM <= fp->uf_lines.ga_len) { 546 fp->uf_tml_idx = SOURCING_LNUM - 1; 547 // Skip continuation lines. 548 while (fp->uf_tml_idx > 0 && FUNCLINE(fp, fp->uf_tml_idx) == NULL) { 549 fp->uf_tml_idx--; 550 } 551 fp->uf_tml_execed = false; 552 fp->uf_tml_start = profile_start(); 553 fp->uf_tml_children = profile_zero(); 554 fp->uf_tml_wait = profile_get_wait(); 555 } 556 } 557 558 /// Called when actually executing a function line. 559 void func_line_exec(void *cookie) 560 { 561 funccall_T *fcp = (funccall_T *)cookie; 562 ufunc_T *fp = fcp->fc_func; 563 564 if (fp->uf_profiling && fp->uf_tml_idx >= 0) { 565 fp->uf_tml_execed = true; 566 } 567 } 568 569 /// Called when done with a function line. 570 void func_line_end(void *cookie) 571 { 572 funccall_T *fcp = (funccall_T *)cookie; 573 ufunc_T *fp = fcp->fc_func; 574 575 if (fp->uf_profiling && fp->uf_tml_idx >= 0) { 576 if (fp->uf_tml_execed) { 577 fp->uf_tml_count[fp->uf_tml_idx]++; 578 fp->uf_tml_start = profile_end(fp->uf_tml_start); 579 fp->uf_tml_start = profile_sub_wait(fp->uf_tml_wait, fp->uf_tml_start); 580 fp->uf_tml_total[fp->uf_tml_idx] = 581 profile_add(fp->uf_tml_total[fp->uf_tml_idx], fp->uf_tml_start); 582 fp->uf_tml_self[fp->uf_tml_idx] = 583 profile_self(fp->uf_tml_self[fp->uf_tml_idx], fp->uf_tml_start, 584 fp->uf_tml_children); 585 } 586 fp->uf_tml_idx = -1; 587 } 588 } 589 590 /// Dump the profiling results for all functions in file "fd". 591 static void func_dump_profile(FILE *fd) 592 { 593 hashtab_T *const functbl = func_tbl_get(); 594 int st_len = 0; 595 596 int todo = (int)functbl->ht_used; 597 if (todo == 0) { 598 return; // nothing to dump 599 } 600 601 ufunc_T **sorttab = xmalloc(sizeof(ufunc_T *) * (size_t)todo); 602 603 for (hashitem_T *hi = functbl->ht_array; todo > 0; hi++) { 604 if (!HASHITEM_EMPTY(hi)) { 605 todo--; 606 ufunc_T *fp = HI2UF(hi); 607 if (fp->uf_prof_initialized) { 608 sorttab[st_len++] = fp; 609 610 if ((uint8_t)fp->uf_name[0] == K_SPECIAL) { 611 fprintf(fd, "FUNCTION <SNR>%s()\n", fp->uf_name + 3); 612 } else { 613 fprintf(fd, "FUNCTION %s()\n", fp->uf_name); 614 } 615 if (fp->uf_script_ctx.sc_sid != 0) { 616 bool should_free; 617 char *p = get_scriptname(fp->uf_script_ctx, &should_free); 618 fprintf(fd, " Defined: %s:%" PRIdLINENR "\n", 619 p, fp->uf_script_ctx.sc_lnum); 620 if (should_free) { 621 xfree(p); 622 } 623 } 624 if (fp->uf_tm_count == 1) { 625 fprintf(fd, "Called 1 time\n"); 626 } else { 627 fprintf(fd, "Called %d times\n", fp->uf_tm_count); 628 } 629 fprintf(fd, "Total time: %s\n", profile_msg(fp->uf_tm_total)); 630 fprintf(fd, " Self time: %s\n", profile_msg(fp->uf_tm_self)); 631 fprintf(fd, "\n"); 632 fprintf(fd, "count total (s) self (s)\n"); 633 634 for (int i = 0; i < fp->uf_lines.ga_len; i++) { 635 if (FUNCLINE(fp, i) == NULL) { 636 continue; 637 } 638 prof_func_line(fd, fp->uf_tml_count[i], 639 &fp->uf_tml_total[i], &fp->uf_tml_self[i], true); 640 fprintf(fd, "%s\n", FUNCLINE(fp, i)); 641 } 642 fprintf(fd, "\n"); 643 } 644 } 645 } 646 647 if (st_len > 0) { 648 qsort((void *)sorttab, (size_t)st_len, sizeof(ufunc_T *), 649 prof_total_cmp); 650 prof_sort_list(fd, sorttab, st_len, "TOTAL", false); 651 qsort((void *)sorttab, (size_t)st_len, sizeof(ufunc_T *), 652 prof_self_cmp); 653 prof_sort_list(fd, sorttab, st_len, "SELF", true); 654 } 655 656 xfree(sorttab); 657 } 658 659 /// Start profiling a script. 660 void profile_init(scriptitem_T *si) 661 { 662 si->sn_pr_count = 0; 663 si->sn_pr_total = profile_zero(); 664 si->sn_pr_self = profile_zero(); 665 666 ga_init(&si->sn_prl_ga, sizeof(sn_prl_T), 100); 667 si->sn_prl_idx = -1; 668 si->sn_prof_on = true; 669 si->sn_pr_nest = 0; 670 } 671 672 /// Save time when starting to invoke another script or function. 673 /// 674 /// @param tm place to store wait time 675 void script_prof_save(proftime_T *tm) 676 { 677 if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= script_items.ga_len) { 678 scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid); 679 if (si->sn_prof_on && si->sn_pr_nest++ == 0) { 680 si->sn_pr_child = profile_start(); 681 } 682 } 683 *tm = profile_get_wait(); 684 } 685 686 /// Count time spent in children after invoking another script or function. 687 void script_prof_restore(const proftime_T *tm) 688 { 689 if (!SCRIPT_ID_VALID(current_sctx.sc_sid)) { 690 return; 691 } 692 693 scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid); 694 if (si->sn_prof_on && --si->sn_pr_nest == 0) { 695 si->sn_pr_child = profile_end(si->sn_pr_child); 696 // don't count wait time 697 si->sn_pr_child = profile_sub_wait(*tm, si->sn_pr_child); 698 si->sn_pr_children = profile_add(si->sn_pr_children, si->sn_pr_child); 699 si->sn_prl_children = profile_add(si->sn_prl_children, si->sn_pr_child); 700 } 701 } 702 703 /// Dump the profiling results for all scripts in file "fd". 704 static void script_dump_profile(FILE *fd) 705 { 706 sn_prl_T *pp; 707 708 for (int id = 1; id <= script_items.ga_len; id++) { 709 scriptitem_T *si = SCRIPT_ITEM(id); 710 if (si->sn_prof_on) { 711 fprintf(fd, "SCRIPT %s\n", si->sn_name); 712 if (si->sn_pr_count == 1) { 713 fprintf(fd, "Sourced 1 time\n"); 714 } else { 715 fprintf(fd, "Sourced %d times\n", si->sn_pr_count); 716 } 717 fprintf(fd, "Total time: %s\n", profile_msg(si->sn_pr_total)); 718 fprintf(fd, " Self time: %s\n", profile_msg(si->sn_pr_self)); 719 fprintf(fd, "\n"); 720 fprintf(fd, "count total (s) self (s)\n"); 721 722 FILE *sfd = os_fopen(si->sn_name, "r"); 723 if (sfd == NULL) { 724 fprintf(fd, "Cannot open file!\n"); 725 } else { 726 // Keep going till the end of file, so that trailing 727 // continuation lines are listed. 728 for (int i = 0;; i++) { 729 if (vim_fgets(IObuff, IOSIZE, sfd)) { 730 break; 731 } 732 // When a line has been truncated, append NL, taking care 733 // of multi-byte characters . 734 if (IObuff[IOSIZE - 2] != NUL && IObuff[IOSIZE - 2] != NL) { 735 int n = IOSIZE - 2; 736 737 // Move to the first byte of this char. 738 // utf_head_off() doesn't work, because it checks 739 // for a truncated character. 740 while (n > 0 && (IObuff[n] & 0xc0) == 0x80) { 741 n--; 742 } 743 744 IObuff[n] = NL; 745 IObuff[n + 1] = NUL; 746 } 747 if (i < si->sn_prl_ga.ga_len 748 && (pp = &PRL_ITEM(si, i))->snp_count > 0) { 749 fprintf(fd, "%5d ", pp->snp_count); 750 if (profile_equal(pp->sn_prl_total, pp->sn_prl_self)) { 751 fprintf(fd, " "); 752 } else { 753 fprintf(fd, "%s ", profile_msg(pp->sn_prl_total)); 754 } 755 fprintf(fd, "%s ", profile_msg(pp->sn_prl_self)); 756 } else { 757 fprintf(fd, " "); 758 } 759 fprintf(fd, "%s", IObuff); 760 } 761 fclose(sfd); 762 } 763 fprintf(fd, "\n"); 764 } 765 } 766 } 767 768 /// Dump the profiling info. 769 void profile_dump(void) 770 { 771 if (profile_fname == NULL) { 772 return; 773 } 774 775 FILE *fd = os_fopen(profile_fname, "w"); 776 if (fd == NULL) { 777 semsg(_(e_notopen), profile_fname); 778 } else { 779 script_dump_profile(fd); 780 func_dump_profile(fd); 781 fclose(fd); 782 } 783 } 784 785 /// Called when starting to read a script line. 786 /// "sourcing_lnum" must be correct! 787 /// When skipping lines it may not actually be executed, but we won't find out 788 /// until later and we need to store the time now. 789 void script_line_start(void) 790 { 791 if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) { 792 return; 793 } 794 scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid); 795 if (si->sn_prof_on && SOURCING_LNUM >= 1) { 796 // Grow the array before starting the timer, so that the time spent 797 // here isn't counted. 798 ga_grow(&si->sn_prl_ga, SOURCING_LNUM - si->sn_prl_ga.ga_len); 799 si->sn_prl_idx = SOURCING_LNUM - 1; 800 while (si->sn_prl_ga.ga_len <= si->sn_prl_idx 801 && si->sn_prl_ga.ga_len < si->sn_prl_ga.ga_maxlen) { 802 // Zero counters for a line that was not used before. 803 sn_prl_T *pp = &PRL_ITEM(si, si->sn_prl_ga.ga_len); 804 pp->snp_count = 0; 805 pp->sn_prl_total = profile_zero(); 806 pp->sn_prl_self = profile_zero(); 807 si->sn_prl_ga.ga_len++; 808 } 809 si->sn_prl_execed = false; 810 si->sn_prl_start = profile_start(); 811 si->sn_prl_children = profile_zero(); 812 si->sn_prl_wait = profile_get_wait(); 813 } 814 } 815 816 /// Called when actually executing a function line. 817 void script_line_exec(void) 818 { 819 if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) { 820 return; 821 } 822 scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid); 823 if (si->sn_prof_on && si->sn_prl_idx >= 0) { 824 si->sn_prl_execed = true; 825 } 826 } 827 828 /// Called when done with a function line. 829 void script_line_end(void) 830 { 831 if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) { 832 return; 833 } 834 scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid); 835 if (si->sn_prof_on && si->sn_prl_idx >= 0 836 && si->sn_prl_idx < si->sn_prl_ga.ga_len) { 837 if (si->sn_prl_execed) { 838 sn_prl_T *pp = &PRL_ITEM(si, si->sn_prl_idx); 839 pp->snp_count++; 840 si->sn_prl_start = profile_end(si->sn_prl_start); 841 si->sn_prl_start = profile_sub_wait(si->sn_prl_wait, si->sn_prl_start); 842 pp->sn_prl_total = profile_add(pp->sn_prl_total, si->sn_prl_start); 843 pp->sn_prl_self = profile_self(pp->sn_prl_self, si->sn_prl_start, 844 si->sn_prl_children); 845 } 846 si->sn_prl_idx = -1; 847 } 848 } 849 850 /// globals for use in the startuptime related functionality (time_*). 851 static proftime_T g_start_time; 852 static proftime_T g_prev_time; 853 854 /// Saves the previous time before doing something that could nest. 855 /// 856 /// After calling this function, the static global `g_prev_time` will 857 /// contain the current time. 858 /// 859 /// @param[out] rel to the time elapsed so far 860 /// @param[out] start the current time 861 void time_push(proftime_T *rel, proftime_T *start) 862 { 863 proftime_T now = profile_start(); 864 865 // subtract the previous time from now, store it in `rel` 866 *rel = profile_sub(now, g_prev_time); 867 *start = now; 868 869 // reset global `g_prev_time` for the next call 870 g_prev_time = now; 871 } 872 873 /// Computes the prev time after doing something that could nest. 874 /// 875 /// Subtracts `tp` from the static global `g_prev_time`. 876 /// 877 /// @param tp the time to subtract 878 void time_pop(proftime_T tp) 879 { 880 g_prev_time -= tp; 881 } 882 883 /// Prints the difference between `then` and `now`. 884 /// 885 /// the format is "msec.usec". 886 static void time_diff(proftime_T then, proftime_T now) 887 { 888 proftime_T diff = profile_sub(now, then); 889 fprintf(time_fd, "%07.3lf", (double)diff / 1.0E6); 890 } 891 892 /// Initializes the startuptime code. 893 /// 894 /// Must be called once before calling other startuptime code (such as 895 /// time_{push,pop,msg,...}). 896 /// 897 /// @param message the message that will be displayed 898 void time_start(const char *message) 899 { 900 if (time_fd == NULL) { 901 return; 902 } 903 904 // initialize the global variables 905 g_prev_time = g_start_time = profile_start(); 906 907 fprintf(time_fd, "\ntimes in msec\n"); 908 fprintf(time_fd, " clock self+sourced self: sourced script\n"); 909 fprintf(time_fd, " clock elapsed: other lines\n\n"); 910 911 time_msg(message, NULL); 912 } 913 914 /// Prints out timing info. 915 /// 916 /// @warning don't forget to call `time_start()` once before calling this. 917 /// 918 /// @param mesg the message to display next to the timing information 919 /// @param start only for do_source: start time 920 void time_msg(const char *mesg, const proftime_T *start) 921 { 922 if (time_fd == NULL) { 923 return; 924 } 925 926 // print out the difference between `start` (init earlier) and `now` 927 proftime_T now = profile_start(); 928 time_diff(g_start_time, now); 929 930 // if `start` was supplied, print the diff between `start` and `now` 931 if (start != NULL) { 932 fprintf(time_fd, " "); 933 time_diff(*start, now); 934 } 935 936 // print the difference between the global `g_prev_time` and `now` 937 fprintf(time_fd, " "); 938 time_diff(g_prev_time, now); 939 940 // reset `g_prev_time` and print the message 941 g_prev_time = now; 942 fprintf(time_fd, ": %s\n", mesg); 943 } 944 945 /// Initializes the `time_fd` stream for the --startuptime report. 946 /// 947 /// @param fname startuptime report file path 948 /// @param proc_name name of the current Nvim process to write in the report. 949 void time_init(const char *fname, const char *proc_name) 950 { 951 const size_t bufsize = 8192; // Big enough for the entire --startuptime report. 952 time_fd = fopen(fname, "a"); 953 if (time_fd == NULL) { 954 fprintf(stderr, _(e_notopen), fname); 955 return; 956 } 957 startuptime_buf = xmalloc(sizeof(char) * (bufsize + 1)); 958 // The startuptime file is (potentially) written by multiple Nvim processes concurrently. So each 959 // report is buffered, and flushed to disk (`time_finish`) once after startup. `_IOFBF` mode 960 // ensures the buffer is not auto-flushed ("controlled buffering"). 961 int r = setvbuf(time_fd, startuptime_buf, _IOFBF, bufsize + 1); 962 if (r != 0) { 963 XFREE_CLEAR(startuptime_buf); 964 fclose(time_fd); 965 time_fd = NULL; 966 fprintf(stderr, "time_init: setvbuf failed: %d %s", r, uv_err_name(r)); 967 return; 968 } 969 fprintf(time_fd, "--- Startup times for process: %s ---\n", proc_name); 970 } 971 972 /// Flushes the startuptimes to disk for the current process 973 void time_finish(void) 974 { 975 if (time_fd == NULL) { 976 return; 977 } 978 assert(startuptime_buf != NULL); 979 TIME_MSG("--- NVIM STARTED ---\n"); 980 981 // flush buffer to disk 982 fclose(time_fd); 983 time_fd = NULL; 984 985 XFREE_CLEAR(startuptime_buf); 986 }