neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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 }