mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
Merge pull request #19733 from zeertzjq/vim-8.1.1684
vim-patch:8.1.{1684,1689}: profiling code is spread out
This commit is contained in:
commit
d4f5e0db3c
@ -241,6 +241,10 @@ preprocess_patch() {
|
|||||||
LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/map\(\.[ch]\)/\1\/mapping\2/g' \
|
LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/map\(\.[ch]\)/\1\/mapping\2/g' \
|
||||||
"$file" > "$file".tmp && mv "$file".tmp "$file"
|
"$file" > "$file".tmp && mv "$file".tmp "$file"
|
||||||
|
|
||||||
|
# Rename profiler.c to profile.c
|
||||||
|
LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/profiler\(\.[ch]\)/\1\/profile\2/g' \
|
||||||
|
"$file" > "$file".tmp && mv "$file".tmp "$file"
|
||||||
|
|
||||||
# Rename session.c to ex_session.c
|
# Rename session.c to ex_session.c
|
||||||
LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/session\(\.[ch]\)/\1\/ex_session\2/g' \
|
LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/session\(\.[ch]\)/\1\/ex_session\2/g' \
|
||||||
"$file" > "$file".tmp && mv "$file".tmp "$file"
|
"$file" > "$file".tmp && mv "$file".tmp "$file"
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include "nvim/map.h"
|
#include "nvim/map.h"
|
||||||
#include "nvim/option.h"
|
#include "nvim/option.h"
|
||||||
#include "nvim/os/input.h"
|
#include "nvim/os/input.h"
|
||||||
|
#include "nvim/profile.h"
|
||||||
#include "nvim/regexp.h"
|
#include "nvim/regexp.h"
|
||||||
#include "nvim/search.h"
|
#include "nvim/search.h"
|
||||||
#include "nvim/state.h"
|
#include "nvim/state.h"
|
||||||
|
@ -36,8 +36,6 @@ typedef struct {
|
|||||||
#include "nvim/hashtab.h"
|
#include "nvim/hashtab.h"
|
||||||
// for dict_T
|
// for dict_T
|
||||||
#include "nvim/eval/typval.h"
|
#include "nvim/eval/typval.h"
|
||||||
// for proftime_T
|
|
||||||
#include "nvim/profile.h"
|
|
||||||
// for String
|
// for String
|
||||||
#include "nvim/api/private/defs.h"
|
#include "nvim/api/private/defs.h"
|
||||||
// for Map(K, V)
|
// for Map(K, V)
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include "nvim/api/vimscript.h"
|
#include "nvim/api/vimscript.h"
|
||||||
#include "nvim/context.h"
|
#include "nvim/context.h"
|
||||||
#include "nvim/eval/encode.h"
|
#include "nvim/eval/encode.h"
|
||||||
|
#include "nvim/eval/userfunc.h"
|
||||||
#include "nvim/ex_docmd.h"
|
#include "nvim/ex_docmd.h"
|
||||||
#include "nvim/option.h"
|
#include "nvim/option.h"
|
||||||
#include "nvim/shada.h"
|
#include "nvim/shada.h"
|
||||||
@ -249,7 +250,7 @@ static inline void ctx_save_funcs(Context *ctx, bool scriptonly)
|
|||||||
ctx->funcs = (Array)ARRAY_DICT_INIT;
|
ctx->funcs = (Array)ARRAY_DICT_INIT;
|
||||||
Error err = ERROR_INIT;
|
Error err = ERROR_INIT;
|
||||||
|
|
||||||
HASHTAB_ITER(&func_hashtab, hi, {
|
HASHTAB_ITER(func_tbl_get(), hi, {
|
||||||
const char_u *const name = hi->hi_key;
|
const char_u *const name = hi->hi_key;
|
||||||
bool islambda = (STRNCMP(name, "<lambda>", 8) == 0);
|
bool islambda = (STRNCMP(name, "<lambda>", 8) == 0);
|
||||||
bool isscript = (name[0] == K_SPECIAL);
|
bool isscript = (name[0] == K_SPECIAL);
|
||||||
|
260
src/nvim/eval.c
260
src/nvim/eval.c
@ -44,6 +44,7 @@
|
|||||||
#include "nvim/os/input.h"
|
#include "nvim/os/input.h"
|
||||||
#include "nvim/os/shell.h"
|
#include "nvim/os/shell.h"
|
||||||
#include "nvim/path.h"
|
#include "nvim/path.h"
|
||||||
|
#include "nvim/profile.h"
|
||||||
#include "nvim/quickfix.h"
|
#include "nvim/quickfix.h"
|
||||||
#include "nvim/regexp.h"
|
#include "nvim/regexp.h"
|
||||||
#include "nvim/screen.h"
|
#include "nvim/screen.h"
|
||||||
@ -1192,42 +1193,6 @@ void *call_func_retlist(const char *func, int argc, typval_T *argv)
|
|||||||
return rettv.vval.v_list;
|
return rettv.vval.v_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepare profiling for entering a child or something else that is not
|
|
||||||
/// counted for the script/function itself.
|
|
||||||
/// Should always be called in pair with prof_child_exit().
|
|
||||||
///
|
|
||||||
/// @param tm place to store waittime
|
|
||||||
void prof_child_enter(proftime_T *tm)
|
|
||||||
{
|
|
||||||
funccall_T *fc = get_current_funccal();
|
|
||||||
|
|
||||||
if (fc != NULL && fc->func->uf_profiling) {
|
|
||||||
fc->prof_child = profile_start();
|
|
||||||
}
|
|
||||||
|
|
||||||
script_prof_save(tm);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Take care of time spent in a child.
|
|
||||||
/// Should always be called after prof_child_enter().
|
|
||||||
///
|
|
||||||
/// @param tm where waittime was stored
|
|
||||||
void prof_child_exit(proftime_T *tm)
|
|
||||||
{
|
|
||||||
funccall_T *fc = get_current_funccal();
|
|
||||||
|
|
||||||
if (fc != NULL && fc->func->uf_profiling) {
|
|
||||||
fc->prof_child = profile_end(fc->prof_child);
|
|
||||||
// don't count waiting time
|
|
||||||
fc->prof_child = profile_sub_wait(*tm, fc->prof_child);
|
|
||||||
fc->func->uf_tm_children =
|
|
||||||
profile_add(fc->func->uf_tm_children, fc->prof_child);
|
|
||||||
fc->func->uf_tml_children =
|
|
||||||
profile_add(fc->func->uf_tml_children, fc->prof_child);
|
|
||||||
}
|
|
||||||
script_prof_restore(tm);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Evaluate 'foldexpr'. Returns the foldlevel, and any character preceding
|
/// Evaluate 'foldexpr'. Returns the foldlevel, and any character preceding
|
||||||
/// it in "*cp". Doesn't give error messages.
|
/// it in "*cp". Doesn't give error messages.
|
||||||
int eval_foldexpr(char *arg, int *cp)
|
int eval_foldexpr(char *arg, int *cp)
|
||||||
@ -7941,174 +7906,6 @@ const char *find_option_end(const char **const arg, int *const opt_flags)
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start profiling function "fp".
|
|
||||||
void func_do_profile(ufunc_T *fp)
|
|
||||||
{
|
|
||||||
int len = fp->uf_lines.ga_len;
|
|
||||||
|
|
||||||
if (!fp->uf_prof_initialized) {
|
|
||||||
if (len == 0) {
|
|
||||||
len = 1; // avoid getting error for allocating zero bytes
|
|
||||||
}
|
|
||||||
fp->uf_tm_count = 0;
|
|
||||||
fp->uf_tm_self = profile_zero();
|
|
||||||
fp->uf_tm_total = profile_zero();
|
|
||||||
|
|
||||||
if (fp->uf_tml_count == NULL) {
|
|
||||||
fp->uf_tml_count = xcalloc((size_t)len, sizeof(int));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fp->uf_tml_total == NULL) {
|
|
||||||
fp->uf_tml_total = xcalloc((size_t)len, sizeof(proftime_T));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fp->uf_tml_self == NULL) {
|
|
||||||
fp->uf_tml_self = xcalloc((size_t)len, sizeof(proftime_T));
|
|
||||||
}
|
|
||||||
|
|
||||||
fp->uf_tml_idx = -1;
|
|
||||||
fp->uf_prof_initialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fp->uf_profiling = TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dump the profiling results for all functions in file "fd".
|
|
||||||
void func_dump_profile(FILE *fd)
|
|
||||||
{
|
|
||||||
hashitem_T *hi;
|
|
||||||
int todo;
|
|
||||||
ufunc_T *fp;
|
|
||||||
ufunc_T **sorttab;
|
|
||||||
int st_len = 0;
|
|
||||||
|
|
||||||
todo = (int)func_hashtab.ht_used;
|
|
||||||
if (todo == 0) {
|
|
||||||
return; // nothing to dump
|
|
||||||
}
|
|
||||||
|
|
||||||
sorttab = xmalloc(sizeof(ufunc_T *) * (size_t)todo);
|
|
||||||
|
|
||||||
for (hi = func_hashtab.ht_array; todo > 0; ++hi) {
|
|
||||||
if (!HASHITEM_EMPTY(hi)) {
|
|
||||||
--todo;
|
|
||||||
fp = HI2UF(hi);
|
|
||||||
if (fp->uf_prof_initialized) {
|
|
||||||
sorttab[st_len++] = fp;
|
|
||||||
|
|
||||||
if (fp->uf_name[0] == K_SPECIAL) {
|
|
||||||
fprintf(fd, "FUNCTION <SNR>%s()\n", fp->uf_name + 3);
|
|
||||||
} else {
|
|
||||||
fprintf(fd, "FUNCTION %s()\n", fp->uf_name);
|
|
||||||
}
|
|
||||||
if (fp->uf_script_ctx.sc_sid != 0) {
|
|
||||||
bool should_free;
|
|
||||||
const LastSet last_set = (LastSet){
|
|
||||||
.script_ctx = fp->uf_script_ctx,
|
|
||||||
.channel_id = 0,
|
|
||||||
};
|
|
||||||
char *p = (char *)get_scriptname(last_set, &should_free);
|
|
||||||
fprintf(fd, " Defined: %s:%" PRIdLINENR "\n",
|
|
||||||
p, fp->uf_script_ctx.sc_lnum);
|
|
||||||
if (should_free) {
|
|
||||||
xfree(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (fp->uf_tm_count == 1) {
|
|
||||||
fprintf(fd, "Called 1 time\n");
|
|
||||||
} else {
|
|
||||||
fprintf(fd, "Called %d times\n", fp->uf_tm_count);
|
|
||||||
}
|
|
||||||
fprintf(fd, "Total time: %s\n", profile_msg(fp->uf_tm_total));
|
|
||||||
fprintf(fd, " Self time: %s\n", profile_msg(fp->uf_tm_self));
|
|
||||||
fprintf(fd, "\n");
|
|
||||||
fprintf(fd, "count total (s) self (s)\n");
|
|
||||||
|
|
||||||
for (int i = 0; i < fp->uf_lines.ga_len; ++i) {
|
|
||||||
if (FUNCLINE(fp, i) == NULL) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
prof_func_line(fd, fp->uf_tml_count[i],
|
|
||||||
&fp->uf_tml_total[i], &fp->uf_tml_self[i], TRUE);
|
|
||||||
fprintf(fd, "%s\n", FUNCLINE(fp, i));
|
|
||||||
}
|
|
||||||
fprintf(fd, "\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (st_len > 0) {
|
|
||||||
qsort((void *)sorttab, (size_t)st_len, sizeof(ufunc_T *),
|
|
||||||
prof_total_cmp);
|
|
||||||
prof_sort_list(fd, sorttab, st_len, "TOTAL", FALSE);
|
|
||||||
qsort((void *)sorttab, (size_t)st_len, sizeof(ufunc_T *),
|
|
||||||
prof_self_cmp);
|
|
||||||
prof_sort_list(fd, sorttab, st_len, "SELF", TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
xfree(sorttab);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @param prefer_self when equal print only self time
|
|
||||||
static void prof_sort_list(FILE *fd, ufunc_T **sorttab, int st_len, char *title, int prefer_self)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
ufunc_T *fp;
|
|
||||||
|
|
||||||
fprintf(fd, "FUNCTIONS SORTED ON %s TIME\n", title);
|
|
||||||
fprintf(fd, "count total (s) self (s) function\n");
|
|
||||||
for (i = 0; i < 20 && i < st_len; ++i) {
|
|
||||||
fp = sorttab[i];
|
|
||||||
prof_func_line(fd, fp->uf_tm_count, &fp->uf_tm_total, &fp->uf_tm_self,
|
|
||||||
prefer_self);
|
|
||||||
if (fp->uf_name[0] == K_SPECIAL) {
|
|
||||||
fprintf(fd, " <SNR>%s()\n", fp->uf_name + 3);
|
|
||||||
} else {
|
|
||||||
fprintf(fd, " %s()\n", fp->uf_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fprintf(fd, "\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Print the count and times for one function or function line.
|
|
||||||
///
|
|
||||||
/// @param prefer_self when equal print only self time
|
|
||||||
static void prof_func_line(FILE *fd, int count, proftime_T *total, proftime_T *self,
|
|
||||||
int prefer_self)
|
|
||||||
{
|
|
||||||
if (count > 0) {
|
|
||||||
fprintf(fd, "%5d ", count);
|
|
||||||
if (prefer_self && profile_equal(*total, *self)) {
|
|
||||||
fprintf(fd, " ");
|
|
||||||
} else {
|
|
||||||
fprintf(fd, "%s ", profile_msg(*total));
|
|
||||||
}
|
|
||||||
if (!prefer_self && profile_equal(*total, *self)) {
|
|
||||||
fprintf(fd, " ");
|
|
||||||
} else {
|
|
||||||
fprintf(fd, "%s ", profile_msg(*self));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fprintf(fd, " ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compare function for total time sorting.
|
|
||||||
static int prof_total_cmp(const void *s1, const void *s2)
|
|
||||||
{
|
|
||||||
ufunc_T *p1 = *(ufunc_T **)s1;
|
|
||||||
ufunc_T *p2 = *(ufunc_T **)s2;
|
|
||||||
return profile_cmp(p1->uf_tm_total, p2->uf_tm_total);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compare function for self time sorting.
|
|
||||||
static int prof_self_cmp(const void *s1, const void *s2)
|
|
||||||
{
|
|
||||||
ufunc_T *p1 = *(ufunc_T **)s1;
|
|
||||||
ufunc_T *p2 = *(ufunc_T **)s2;
|
|
||||||
return profile_cmp(p1->uf_tm_self, p2->uf_tm_self);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the autoload script name for a function or variable name
|
/// Return the autoload script name for a function or variable name
|
||||||
/// Caller must make sure that "name" contains AUTOLOAD_CHAR.
|
/// Caller must make sure that "name" contains AUTOLOAD_CHAR.
|
||||||
///
|
///
|
||||||
@ -8183,61 +7980,6 @@ bool script_autoload(const char *const name, const size_t name_len, const bool r
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when starting to read a function line.
|
|
||||||
/// "sourcing_lnum" must be correct!
|
|
||||||
/// When skipping lines it may not actually be executed, but we won't find out
|
|
||||||
/// until later and we need to store the time now.
|
|
||||||
void func_line_start(void *cookie)
|
|
||||||
{
|
|
||||||
funccall_T *fcp = (funccall_T *)cookie;
|
|
||||||
ufunc_T *fp = fcp->func;
|
|
||||||
|
|
||||||
if (fp->uf_profiling && sourcing_lnum >= 1
|
|
||||||
&& sourcing_lnum <= fp->uf_lines.ga_len) {
|
|
||||||
fp->uf_tml_idx = sourcing_lnum - 1;
|
|
||||||
// Skip continuation lines.
|
|
||||||
while (fp->uf_tml_idx > 0 && FUNCLINE(fp, fp->uf_tml_idx) == NULL) {
|
|
||||||
fp->uf_tml_idx--;
|
|
||||||
}
|
|
||||||
fp->uf_tml_execed = false;
|
|
||||||
fp->uf_tml_start = profile_start();
|
|
||||||
fp->uf_tml_children = profile_zero();
|
|
||||||
fp->uf_tml_wait = profile_get_wait();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called when actually executing a function line.
|
|
||||||
void func_line_exec(void *cookie)
|
|
||||||
{
|
|
||||||
funccall_T *fcp = (funccall_T *)cookie;
|
|
||||||
ufunc_T *fp = fcp->func;
|
|
||||||
|
|
||||||
if (fp->uf_profiling && fp->uf_tml_idx >= 0) {
|
|
||||||
fp->uf_tml_execed = TRUE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called when done with a function line.
|
|
||||||
void func_line_end(void *cookie)
|
|
||||||
{
|
|
||||||
funccall_T *fcp = (funccall_T *)cookie;
|
|
||||||
ufunc_T *fp = fcp->func;
|
|
||||||
|
|
||||||
if (fp->uf_profiling && fp->uf_tml_idx >= 0) {
|
|
||||||
if (fp->uf_tml_execed) {
|
|
||||||
++fp->uf_tml_count[fp->uf_tml_idx];
|
|
||||||
fp->uf_tml_start = profile_end(fp->uf_tml_start);
|
|
||||||
fp->uf_tml_start = profile_sub_wait(fp->uf_tml_wait, fp->uf_tml_start);
|
|
||||||
fp->uf_tml_total[fp->uf_tml_idx] =
|
|
||||||
profile_add(fp->uf_tml_total[fp->uf_tml_idx], fp->uf_tml_start);
|
|
||||||
fp->uf_tml_self[fp->uf_tml_idx] =
|
|
||||||
profile_self(fp->uf_tml_self[fp->uf_tml_idx], fp->uf_tml_start,
|
|
||||||
fp->uf_tml_children);
|
|
||||||
}
|
|
||||||
fp->uf_tml_idx = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static var_flavour_T var_flavour(char *varname)
|
static var_flavour_T var_flavour(char *varname)
|
||||||
FUNC_ATTR_PURE
|
FUNC_ATTR_PURE
|
||||||
{
|
{
|
||||||
|
@ -11,15 +11,6 @@
|
|||||||
#define COPYID_INC 2
|
#define COPYID_INC 2
|
||||||
#define COPYID_MASK (~0x1)
|
#define COPYID_MASK (~0x1)
|
||||||
|
|
||||||
// All user-defined functions are found in this hashtable.
|
|
||||||
extern hashtab_T func_hashtab;
|
|
||||||
|
|
||||||
// From user function to hashitem and back.
|
|
||||||
EXTERN ufunc_T dumuf;
|
|
||||||
#define UF2HIKEY(fp) ((fp)->uf_name)
|
|
||||||
#define HIKEY2UF(p) ((ufunc_T *)(p - offsetof(ufunc_T, uf_name)))
|
|
||||||
#define HI2UF(hi) HIKEY2UF((hi)->hi_key)
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Structure returned by get_lval() and used by set_var_lval().
|
* Structure returned by get_lval() and used by set_var_lval().
|
||||||
* For a plain name:
|
* For a plain name:
|
||||||
|
@ -58,6 +58,7 @@
|
|||||||
#include "nvim/path.h"
|
#include "nvim/path.h"
|
||||||
#include "nvim/plines.h"
|
#include "nvim/plines.h"
|
||||||
#include "nvim/popupmnu.h"
|
#include "nvim/popupmnu.h"
|
||||||
|
#include "nvim/profile.h"
|
||||||
#include "nvim/quickfix.h"
|
#include "nvim/quickfix.h"
|
||||||
#include "nvim/regexp.h"
|
#include "nvim/regexp.h"
|
||||||
#include "nvim/screen.h"
|
#include "nvim/screen.h"
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
#include "nvim/mbyte_defs.h"
|
#include "nvim/mbyte_defs.h"
|
||||||
#include "nvim/message.h"
|
#include "nvim/message.h"
|
||||||
#include "nvim/pos.h" // for linenr_T
|
#include "nvim/pos.h" // for linenr_T
|
||||||
#include "nvim/profile.h" // for proftime_T
|
|
||||||
#include "nvim/types.h"
|
#include "nvim/types.h"
|
||||||
#ifdef LOG_LIST_ACTIONS
|
#ifdef LOG_LIST_ACTIONS
|
||||||
# include "nvim/memory.h"
|
# include "nvim/memory.h"
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include "nvim/insexpand.h"
|
#include "nvim/insexpand.h"
|
||||||
#include "nvim/lua/executor.h"
|
#include "nvim/lua/executor.h"
|
||||||
#include "nvim/os/input.h"
|
#include "nvim/os/input.h"
|
||||||
|
#include "nvim/profile.h"
|
||||||
#include "nvim/regexp.h"
|
#include "nvim/regexp.h"
|
||||||
#include "nvim/search.h"
|
#include "nvim/search.h"
|
||||||
#include "nvim/ui.h"
|
#include "nvim/ui.h"
|
||||||
@ -44,7 +45,7 @@
|
|||||||
# include "eval/userfunc.c.generated.h"
|
# include "eval/userfunc.c.generated.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
hashtab_T func_hashtab;
|
static hashtab_T func_hashtab;
|
||||||
|
|
||||||
// Used by get_func_tv()
|
// Used by get_func_tv()
|
||||||
static garray_T funcargs = GA_EMPTY_INIT_VALUE;
|
static garray_T funcargs = GA_EMPTY_INIT_VALUE;
|
||||||
@ -66,6 +67,12 @@ void func_init(void)
|
|||||||
hash_init(&func_hashtab);
|
hash_init(&func_hashtab);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the function hash table
|
||||||
|
hashtab_T *func_tbl_get(void)
|
||||||
|
{
|
||||||
|
return &func_hashtab;
|
||||||
|
}
|
||||||
|
|
||||||
/// Get function arguments.
|
/// Get function arguments.
|
||||||
static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, int *varargs,
|
static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, int *varargs,
|
||||||
garray_T *default_args, bool skip)
|
garray_T *default_args, bool skip)
|
||||||
|
@ -4,6 +4,11 @@
|
|||||||
#include "nvim/eval/typval.h"
|
#include "nvim/eval/typval.h"
|
||||||
#include "nvim/ex_cmds_defs.h"
|
#include "nvim/ex_cmds_defs.h"
|
||||||
|
|
||||||
|
// From user function to hashitem and back.
|
||||||
|
#define UF2HIKEY(fp) ((fp)->uf_name)
|
||||||
|
#define HIKEY2UF(p) ((ufunc_T *)(p - offsetof(ufunc_T, uf_name)))
|
||||||
|
#define HI2UF(hi) HIKEY2UF((hi)->hi_key)
|
||||||
|
|
||||||
///< Structure used by trans_function_name()
|
///< Structure used by trans_function_name()
|
||||||
typedef struct {
|
typedef struct {
|
||||||
dict_T *fd_dict; ///< Dictionary used.
|
dict_T *fd_dict; ///< Dictionary used.
|
||||||
|
@ -60,6 +60,7 @@
|
|||||||
#include "nvim/os_unix.h"
|
#include "nvim/os_unix.h"
|
||||||
#include "nvim/path.h"
|
#include "nvim/path.h"
|
||||||
#include "nvim/plines.h"
|
#include "nvim/plines.h"
|
||||||
|
#include "nvim/profile.h"
|
||||||
#include "nvim/quickfix.h"
|
#include "nvim/quickfix.h"
|
||||||
#include "nvim/regexp.h"
|
#include "nvim/regexp.h"
|
||||||
#include "nvim/screen.h"
|
#include "nvim/screen.h"
|
||||||
|
@ -54,17 +54,6 @@
|
|||||||
#include "nvim/version.h"
|
#include "nvim/version.h"
|
||||||
#include "nvim/window.h"
|
#include "nvim/window.h"
|
||||||
|
|
||||||
/// Growarray to store info about already sourced scripts.
|
|
||||||
static garray_T script_items = { 0, 0, sizeof(scriptitem_T), 4, NULL };
|
|
||||||
#define SCRIPT_ITEM(id) (((scriptitem_T *)script_items.ga_data)[(id) - 1])
|
|
||||||
|
|
||||||
// Struct used in sn_prl_ga for every line of a script.
|
|
||||||
typedef struct sn_prl_S {
|
|
||||||
int snp_count; ///< nr of times line was executed
|
|
||||||
proftime_T sn_prl_total; ///< time spent in a line + children
|
|
||||||
proftime_T sn_prl_self; ///< time spent in a line itself
|
|
||||||
} sn_prl_T;
|
|
||||||
|
|
||||||
/// Structure used to store info for each sourced file.
|
/// Structure used to store info for each sourced file.
|
||||||
/// It is shared between do_source() and getsourceline().
|
/// It is shared between do_source() and getsourceline().
|
||||||
/// This is required, because it needs to be handed to do_cmdline() and
|
/// This is required, because it needs to be handed to do_cmdline() and
|
||||||
@ -85,58 +74,10 @@ struct source_cookie {
|
|||||||
vimconv_T conv; ///< type of conversion
|
vimconv_T conv; ///< type of conversion
|
||||||
};
|
};
|
||||||
|
|
||||||
#define PRL_ITEM(si, idx) (((sn_prl_T *)(si)->sn_prl_ga.ga_data)[(idx)])
|
|
||||||
|
|
||||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
# include "ex_cmds2.c.generated.h"
|
# include "ex_cmds2.c.generated.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static char *profile_fname = NULL;
|
|
||||||
|
|
||||||
/// ":profile cmd args"
|
|
||||||
void ex_profile(exarg_T *eap)
|
|
||||||
{
|
|
||||||
static proftime_T pause_time;
|
|
||||||
|
|
||||||
char *e;
|
|
||||||
int len;
|
|
||||||
|
|
||||||
e = (char *)skiptowhite((char_u *)eap->arg);
|
|
||||||
len = (int)(e - eap->arg);
|
|
||||||
e = skipwhite(e);
|
|
||||||
|
|
||||||
if (len == 5 && STRNCMP(eap->arg, "start", 5) == 0 && *e != NUL) {
|
|
||||||
xfree(profile_fname);
|
|
||||||
profile_fname = (char *)expand_env_save_opt((char_u *)e, true);
|
|
||||||
do_profiling = PROF_YES;
|
|
||||||
profile_set_wait(profile_zero());
|
|
||||||
set_vim_var_nr(VV_PROFILING, 1L);
|
|
||||||
} else if (do_profiling == PROF_NONE) {
|
|
||||||
emsg(_("E750: First use \":profile start {fname}\""));
|
|
||||||
} else if (STRCMP(eap->arg, "stop") == 0) {
|
|
||||||
profile_dump();
|
|
||||||
do_profiling = PROF_NONE;
|
|
||||||
set_vim_var_nr(VV_PROFILING, 0L);
|
|
||||||
profile_reset();
|
|
||||||
} else if (STRCMP(eap->arg, "pause") == 0) {
|
|
||||||
if (do_profiling == PROF_YES) {
|
|
||||||
pause_time = profile_start();
|
|
||||||
}
|
|
||||||
do_profiling = PROF_PAUSED;
|
|
||||||
} else if (STRCMP(eap->arg, "continue") == 0) {
|
|
||||||
if (do_profiling == PROF_PAUSED) {
|
|
||||||
pause_time = profile_end(pause_time);
|
|
||||||
profile_set_wait(profile_add(profile_get_wait(), pause_time));
|
|
||||||
}
|
|
||||||
do_profiling = PROF_YES;
|
|
||||||
} else if (STRCMP(eap->arg, "dump") == 0) {
|
|
||||||
profile_dump();
|
|
||||||
} else {
|
|
||||||
// The rest is similar to ":breakadd".
|
|
||||||
ex_breakadd(eap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ex_ruby(exarg_T *eap)
|
void ex_ruby(exarg_T *eap)
|
||||||
{
|
{
|
||||||
script_host_execute("ruby", eap);
|
script_host_execute("ruby", eap);
|
||||||
@ -182,273 +123,6 @@ void ex_perldo(exarg_T *eap)
|
|||||||
script_host_do_range("perl", eap);
|
script_host_do_range("perl", eap);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Command line expansion for :profile.
|
|
||||||
static enum {
|
|
||||||
PEXP_SUBCMD, ///< expand :profile sub-commands
|
|
||||||
PEXP_FUNC, ///< expand :profile func {funcname}
|
|
||||||
} pexpand_what;
|
|
||||||
|
|
||||||
static char *pexpand_cmds[] = {
|
|
||||||
"continue",
|
|
||||||
"dump",
|
|
||||||
"file",
|
|
||||||
"func",
|
|
||||||
"pause",
|
|
||||||
"start",
|
|
||||||
"stop",
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Function given to ExpandGeneric() to obtain the profile command
|
|
||||||
/// specific expansion.
|
|
||||||
char *get_profile_name(expand_T *xp, int idx)
|
|
||||||
FUNC_ATTR_PURE
|
|
||||||
{
|
|
||||||
switch (pexpand_what) {
|
|
||||||
case PEXP_SUBCMD:
|
|
||||||
return pexpand_cmds[idx];
|
|
||||||
// case PEXP_FUNC: TODO
|
|
||||||
default:
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle command line completion for :profile command.
|
|
||||||
void set_context_in_profile_cmd(expand_T *xp, const char *arg)
|
|
||||||
{
|
|
||||||
// Default: expand subcommands.
|
|
||||||
xp->xp_context = EXPAND_PROFILE;
|
|
||||||
pexpand_what = PEXP_SUBCMD;
|
|
||||||
xp->xp_pattern = (char *)arg;
|
|
||||||
|
|
||||||
char_u *const end_subcmd = skiptowhite((const char_u *)arg);
|
|
||||||
if (*end_subcmd == NUL) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((const char *)end_subcmd - arg == 5 && strncmp(arg, "start", 5) == 0) {
|
|
||||||
xp->xp_context = EXPAND_FILES;
|
|
||||||
xp->xp_pattern = skipwhite((char *)end_subcmd);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(tarruda): expand function names after "func"
|
|
||||||
xp->xp_context = EXPAND_NOTHING;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dump the profiling info.
|
|
||||||
void profile_dump(void)
|
|
||||||
{
|
|
||||||
FILE *fd;
|
|
||||||
|
|
||||||
if (profile_fname != NULL) {
|
|
||||||
fd = os_fopen(profile_fname, "w");
|
|
||||||
if (fd == NULL) {
|
|
||||||
semsg(_(e_notopen), profile_fname);
|
|
||||||
} else {
|
|
||||||
script_dump_profile(fd);
|
|
||||||
func_dump_profile(fd);
|
|
||||||
fclose(fd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reset all profiling information.
|
|
||||||
static void profile_reset(void)
|
|
||||||
{
|
|
||||||
// Reset sourced files.
|
|
||||||
for (int id = 1; id <= script_items.ga_len; id++) {
|
|
||||||
scriptitem_T *si = &SCRIPT_ITEM(id);
|
|
||||||
if (si->sn_prof_on) {
|
|
||||||
si->sn_prof_on = false;
|
|
||||||
si->sn_pr_force = false;
|
|
||||||
si->sn_pr_child = profile_zero();
|
|
||||||
si->sn_pr_nest = 0;
|
|
||||||
si->sn_pr_count = 0;
|
|
||||||
si->sn_pr_total = profile_zero();
|
|
||||||
si->sn_pr_self = profile_zero();
|
|
||||||
si->sn_pr_start = profile_zero();
|
|
||||||
si->sn_pr_children = profile_zero();
|
|
||||||
ga_clear(&si->sn_prl_ga);
|
|
||||||
si->sn_prl_start = profile_zero();
|
|
||||||
si->sn_prl_children = profile_zero();
|
|
||||||
si->sn_prl_wait = profile_zero();
|
|
||||||
si->sn_prl_idx = -1;
|
|
||||||
si->sn_prl_execed = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset functions.
|
|
||||||
size_t n = func_hashtab.ht_used;
|
|
||||||
hashitem_T *hi = func_hashtab.ht_array;
|
|
||||||
|
|
||||||
for (; n > (size_t)0; hi++) {
|
|
||||||
if (!HASHITEM_EMPTY(hi)) {
|
|
||||||
n--;
|
|
||||||
ufunc_T *uf = HI2UF(hi);
|
|
||||||
if (uf->uf_prof_initialized) {
|
|
||||||
uf->uf_profiling = 0;
|
|
||||||
uf->uf_tm_count = 0;
|
|
||||||
uf->uf_tm_total = profile_zero();
|
|
||||||
uf->uf_tm_self = profile_zero();
|
|
||||||
uf->uf_tm_children = profile_zero();
|
|
||||||
|
|
||||||
for (int i = 0; i < uf->uf_lines.ga_len; i++) {
|
|
||||||
uf->uf_tml_count[i] = 0;
|
|
||||||
uf->uf_tml_total[i] = uf->uf_tml_self[i] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uf->uf_tml_start = profile_zero();
|
|
||||||
uf->uf_tml_children = profile_zero();
|
|
||||||
uf->uf_tml_wait = profile_zero();
|
|
||||||
uf->uf_tml_idx = -1;
|
|
||||||
uf->uf_tml_execed = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
XFREE_CLEAR(profile_fname);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Start profiling a script.
|
|
||||||
static void profile_init(scriptitem_T *si)
|
|
||||||
{
|
|
||||||
si->sn_pr_count = 0;
|
|
||||||
si->sn_pr_total = profile_zero();
|
|
||||||
si->sn_pr_self = profile_zero();
|
|
||||||
|
|
||||||
ga_init(&si->sn_prl_ga, sizeof(sn_prl_T), 100);
|
|
||||||
si->sn_prl_idx = -1;
|
|
||||||
si->sn_prof_on = true;
|
|
||||||
si->sn_pr_nest = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Save time when starting to invoke another script or function.
|
|
||||||
///
|
|
||||||
/// @param tm place to store wait time
|
|
||||||
void script_prof_save(proftime_T *tm)
|
|
||||||
{
|
|
||||||
scriptitem_T *si;
|
|
||||||
|
|
||||||
if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= script_items.ga_len) {
|
|
||||||
si = &SCRIPT_ITEM(current_sctx.sc_sid);
|
|
||||||
if (si->sn_prof_on && si->sn_pr_nest++ == 0) {
|
|
||||||
si->sn_pr_child = profile_start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*tm = profile_get_wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Count time spent in children after invoking another script or function.
|
|
||||||
void script_prof_restore(proftime_T *tm)
|
|
||||||
{
|
|
||||||
scriptitem_T *si;
|
|
||||||
|
|
||||||
if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= script_items.ga_len) {
|
|
||||||
si = &SCRIPT_ITEM(current_sctx.sc_sid);
|
|
||||||
if (si->sn_prof_on && --si->sn_pr_nest == 0) {
|
|
||||||
si->sn_pr_child = profile_end(si->sn_pr_child);
|
|
||||||
// don't count wait time
|
|
||||||
si->sn_pr_child = profile_sub_wait(*tm, si->sn_pr_child);
|
|
||||||
si->sn_pr_children = profile_add(si->sn_pr_children, si->sn_pr_child);
|
|
||||||
si->sn_prl_children = profile_add(si->sn_prl_children, si->sn_pr_child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static proftime_T inchar_time;
|
|
||||||
|
|
||||||
/// Called when starting to wait for the user to type a character.
|
|
||||||
void prof_inchar_enter(void)
|
|
||||||
{
|
|
||||||
inchar_time = profile_start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called when finished waiting for the user to type a character.
|
|
||||||
void prof_inchar_exit(void)
|
|
||||||
{
|
|
||||||
inchar_time = profile_end(inchar_time);
|
|
||||||
profile_set_wait(profile_add(profile_get_wait(), inchar_time));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dump the profiling results for all scripts in file "fd".
|
|
||||||
static void script_dump_profile(FILE *fd)
|
|
||||||
{
|
|
||||||
scriptitem_T *si;
|
|
||||||
FILE *sfd;
|
|
||||||
sn_prl_T *pp;
|
|
||||||
|
|
||||||
for (int id = 1; id <= script_items.ga_len; id++) {
|
|
||||||
si = &SCRIPT_ITEM(id);
|
|
||||||
if (si->sn_prof_on) {
|
|
||||||
fprintf(fd, "SCRIPT %s\n", si->sn_name);
|
|
||||||
if (si->sn_pr_count == 1) {
|
|
||||||
fprintf(fd, "Sourced 1 time\n");
|
|
||||||
} else {
|
|
||||||
fprintf(fd, "Sourced %d times\n", si->sn_pr_count);
|
|
||||||
}
|
|
||||||
fprintf(fd, "Total time: %s\n", profile_msg(si->sn_pr_total));
|
|
||||||
fprintf(fd, " Self time: %s\n", profile_msg(si->sn_pr_self));
|
|
||||||
fprintf(fd, "\n");
|
|
||||||
fprintf(fd, "count total (s) self (s)\n");
|
|
||||||
|
|
||||||
sfd = os_fopen((char *)si->sn_name, "r");
|
|
||||||
if (sfd == NULL) {
|
|
||||||
fprintf(fd, "Cannot open file!\n");
|
|
||||||
} else {
|
|
||||||
// Keep going till the end of file, so that trailing
|
|
||||||
// continuation lines are listed.
|
|
||||||
for (int i = 0;; i++) {
|
|
||||||
if (vim_fgets(IObuff, IOSIZE, sfd)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// When a line has been truncated, append NL, taking care
|
|
||||||
// of multi-byte characters .
|
|
||||||
if (IObuff[IOSIZE - 2] != NUL && IObuff[IOSIZE - 2] != NL) {
|
|
||||||
int n = IOSIZE - 2;
|
|
||||||
|
|
||||||
// Move to the first byte of this char.
|
|
||||||
// utf_head_off() doesn't work, because it checks
|
|
||||||
// for a truncated character.
|
|
||||||
while (n > 0 && (IObuff[n] & 0xc0) == 0x80) {
|
|
||||||
n--;
|
|
||||||
}
|
|
||||||
|
|
||||||
IObuff[n] = NL;
|
|
||||||
IObuff[n + 1] = NUL;
|
|
||||||
}
|
|
||||||
if (i < si->sn_prl_ga.ga_len
|
|
||||||
&& (pp = &PRL_ITEM(si, i))->snp_count > 0) {
|
|
||||||
fprintf(fd, "%5d ", pp->snp_count);
|
|
||||||
if (profile_equal(pp->sn_prl_total, pp->sn_prl_self)) {
|
|
||||||
fprintf(fd, " ");
|
|
||||||
} else {
|
|
||||||
fprintf(fd, "%s ", profile_msg(pp->sn_prl_total));
|
|
||||||
}
|
|
||||||
fprintf(fd, "%s ", profile_msg(pp->sn_prl_self));
|
|
||||||
} else {
|
|
||||||
fprintf(fd, " ");
|
|
||||||
}
|
|
||||||
fprintf(fd, "%s", IObuff);
|
|
||||||
}
|
|
||||||
fclose(sfd);
|
|
||||||
}
|
|
||||||
fprintf(fd, "\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @return true when a function defined in the current script should be
|
|
||||||
/// profiled.
|
|
||||||
bool prof_def_func(void)
|
|
||||||
FUNC_ATTR_PURE
|
|
||||||
{
|
|
||||||
if (current_sctx.sc_sid > 0) {
|
|
||||||
return SCRIPT_ITEM(current_sctx.sc_sid).sn_pr_force;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If 'autowrite' option set, try to write the file.
|
/// If 'autowrite' option set, try to write the file.
|
||||||
/// Careful: autocommands may make "buf" invalid!
|
/// Careful: autocommands may make "buf" invalid!
|
||||||
///
|
///
|
||||||
@ -2468,79 +2142,6 @@ retry:
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when starting to read a script line.
|
|
||||||
/// "sourcing_lnum" must be correct!
|
|
||||||
/// When skipping lines it may not actually be executed, but we won't find out
|
|
||||||
/// until later and we need to store the time now.
|
|
||||||
void script_line_start(void)
|
|
||||||
{
|
|
||||||
scriptitem_T *si;
|
|
||||||
sn_prl_T *pp;
|
|
||||||
|
|
||||||
if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
si = &SCRIPT_ITEM(current_sctx.sc_sid);
|
|
||||||
if (si->sn_prof_on && sourcing_lnum >= 1) {
|
|
||||||
// Grow the array before starting the timer, so that the time spent
|
|
||||||
// here isn't counted.
|
|
||||||
(void)ga_grow(&si->sn_prl_ga, sourcing_lnum - si->sn_prl_ga.ga_len);
|
|
||||||
si->sn_prl_idx = sourcing_lnum - 1;
|
|
||||||
while (si->sn_prl_ga.ga_len <= si->sn_prl_idx
|
|
||||||
&& si->sn_prl_ga.ga_len < si->sn_prl_ga.ga_maxlen) {
|
|
||||||
// Zero counters for a line that was not used before.
|
|
||||||
pp = &PRL_ITEM(si, si->sn_prl_ga.ga_len);
|
|
||||||
pp->snp_count = 0;
|
|
||||||
pp->sn_prl_total = profile_zero();
|
|
||||||
pp->sn_prl_self = profile_zero();
|
|
||||||
si->sn_prl_ga.ga_len++;
|
|
||||||
}
|
|
||||||
si->sn_prl_execed = false;
|
|
||||||
si->sn_prl_start = profile_start();
|
|
||||||
si->sn_prl_children = profile_zero();
|
|
||||||
si->sn_prl_wait = profile_get_wait();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called when actually executing a function line.
|
|
||||||
void script_line_exec(void)
|
|
||||||
{
|
|
||||||
scriptitem_T *si;
|
|
||||||
|
|
||||||
if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
si = &SCRIPT_ITEM(current_sctx.sc_sid);
|
|
||||||
if (si->sn_prof_on && si->sn_prl_idx >= 0) {
|
|
||||||
si->sn_prl_execed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called when done with a function line.
|
|
||||||
void script_line_end(void)
|
|
||||||
{
|
|
||||||
scriptitem_T *si;
|
|
||||||
sn_prl_T *pp;
|
|
||||||
|
|
||||||
if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
si = &SCRIPT_ITEM(current_sctx.sc_sid);
|
|
||||||
if (si->sn_prof_on && si->sn_prl_idx >= 0
|
|
||||||
&& si->sn_prl_idx < si->sn_prl_ga.ga_len) {
|
|
||||||
if (si->sn_prl_execed) {
|
|
||||||
pp = &PRL_ITEM(si, si->sn_prl_idx);
|
|
||||||
pp->snp_count++;
|
|
||||||
si->sn_prl_start = profile_end(si->sn_prl_start);
|
|
||||||
si->sn_prl_start = profile_sub_wait(si->sn_prl_wait, si->sn_prl_start);
|
|
||||||
pp->sn_prl_total = profile_add(pp->sn_prl_total, si->sn_prl_start);
|
|
||||||
pp->sn_prl_self = profile_self(pp->sn_prl_self, si->sn_prl_start,
|
|
||||||
si->sn_prl_children);
|
|
||||||
}
|
|
||||||
si->sn_prl_idx = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ":scriptencoding": Set encoding conversion for a sourced script.
|
/// ":scriptencoding": Set encoding conversion for a sourced script.
|
||||||
/// Without the multi-byte feature it's simply ignored.
|
/// Without the multi-byte feature it's simply ignored.
|
||||||
void ex_scriptencoding(exarg_T *eap)
|
void ex_scriptencoding(exarg_T *eap)
|
||||||
|
@ -15,27 +15,6 @@
|
|||||||
#define CCGD_ALLBUF 8 // may write all buffers
|
#define CCGD_ALLBUF 8 // may write all buffers
|
||||||
#define CCGD_EXCMD 16 // may suggest using !
|
#define CCGD_EXCMD 16 // may suggest using !
|
||||||
|
|
||||||
typedef struct scriptitem_S {
|
|
||||||
char_u *sn_name;
|
|
||||||
bool sn_prof_on; ///< true when script is/was profiled
|
|
||||||
bool sn_pr_force; ///< forceit: profile functions in this script
|
|
||||||
proftime_T sn_pr_child; ///< time set when going into first child
|
|
||||||
int sn_pr_nest; ///< nesting for sn_pr_child
|
|
||||||
// profiling the script as a whole
|
|
||||||
int sn_pr_count; ///< nr of times sourced
|
|
||||||
proftime_T sn_pr_total; ///< time spent in script + children
|
|
||||||
proftime_T sn_pr_self; ///< time spent in script itself
|
|
||||||
proftime_T sn_pr_start; ///< time at script start
|
|
||||||
proftime_T sn_pr_children; ///< time in children after script start
|
|
||||||
// profiling the script per line
|
|
||||||
garray_T sn_prl_ga; ///< things stored for every line
|
|
||||||
proftime_T sn_prl_start; ///< start time for current line
|
|
||||||
proftime_T sn_prl_children; ///< time spent in children for this line
|
|
||||||
proftime_T sn_prl_wait; ///< wait start time for current line
|
|
||||||
linenr_T sn_prl_idx; ///< index of line being timed; -1 if none
|
|
||||||
int sn_prl_execed; ///< line being timed was executed
|
|
||||||
} scriptitem_T;
|
|
||||||
|
|
||||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
# include "ex_cmds2.h.generated.h"
|
# include "ex_cmds2.h.generated.h"
|
||||||
#endif
|
#endif
|
||||||
|
@ -64,6 +64,7 @@
|
|||||||
#include "nvim/os_unix.h"
|
#include "nvim/os_unix.h"
|
||||||
#include "nvim/path.h"
|
#include "nvim/path.h"
|
||||||
#include "nvim/popupmnu.h"
|
#include "nvim/popupmnu.h"
|
||||||
|
#include "nvim/profile.h"
|
||||||
#include "nvim/quickfix.h"
|
#include "nvim/quickfix.h"
|
||||||
#include "nvim/regexp.h"
|
#include "nvim/regexp.h"
|
||||||
#include "nvim/screen.h"
|
#include "nvim/screen.h"
|
||||||
|
@ -65,6 +65,7 @@
|
|||||||
#include "nvim/os_unix.h"
|
#include "nvim/os_unix.h"
|
||||||
#include "nvim/path.h"
|
#include "nvim/path.h"
|
||||||
#include "nvim/popupmnu.h"
|
#include "nvim/popupmnu.h"
|
||||||
|
#include "nvim/profile.h"
|
||||||
#include "nvim/regexp.h"
|
#include "nvim/regexp.h"
|
||||||
#include "nvim/screen.h"
|
#include "nvim/screen.h"
|
||||||
#include "nvim/search.h"
|
#include "nvim/search.h"
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include "nvim/highlight_group.h"
|
#include "nvim/highlight_group.h"
|
||||||
#include "nvim/match.h"
|
#include "nvim/match.h"
|
||||||
#include "nvim/memline.h"
|
#include "nvim/memline.h"
|
||||||
|
#include "nvim/profile.h"
|
||||||
#include "nvim/regexp.h"
|
#include "nvim/regexp.h"
|
||||||
#include "nvim/runtime.h"
|
#include "nvim/runtime.h"
|
||||||
#include "nvim/screen.h"
|
#include "nvim/screen.h"
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
#include "nvim/os/input.h"
|
#include "nvim/os/input.h"
|
||||||
#include "nvim/os/time.h"
|
#include "nvim/os/time.h"
|
||||||
#include "nvim/plines.h"
|
#include "nvim/plines.h"
|
||||||
|
#include "nvim/profile.h"
|
||||||
#include "nvim/quickfix.h"
|
#include "nvim/quickfix.h"
|
||||||
#include "nvim/screen.h"
|
#include "nvim/screen.h"
|
||||||
#include "nvim/search.h"
|
#include "nvim/search.h"
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "nvim/memory.h"
|
#include "nvim/memory.h"
|
||||||
#include "nvim/msgpack_rpc/channel.h"
|
#include "nvim/msgpack_rpc/channel.h"
|
||||||
#include "nvim/os/input.h"
|
#include "nvim/os/input.h"
|
||||||
|
#include "nvim/profile.h"
|
||||||
#include "nvim/screen.h"
|
#include "nvim/screen.h"
|
||||||
#include "nvim/state.h"
|
#include "nvim/state.h"
|
||||||
#include "nvim/ui.h"
|
#include "nvim/ui.h"
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include "nvim/os/shell.h"
|
#include "nvim/os/shell.h"
|
||||||
#include "nvim/os/signal.h"
|
#include "nvim/os/signal.h"
|
||||||
#include "nvim/path.h"
|
#include "nvim/path.h"
|
||||||
|
#include "nvim/profile.h"
|
||||||
#include "nvim/screen.h"
|
#include "nvim/screen.h"
|
||||||
#include "nvim/strings.h"
|
#include "nvim/strings.h"
|
||||||
#include "nvim/tag.h"
|
#include "nvim/tag.h"
|
||||||
|
@ -6,16 +6,33 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "nvim/assert.h"
|
#include "nvim/assert.h"
|
||||||
|
#include "nvim/charset.h"
|
||||||
|
#include "nvim/debugger.h"
|
||||||
|
#include "nvim/eval.h"
|
||||||
|
#include "nvim/eval/userfunc.h"
|
||||||
|
#include "nvim/ex_cmds2.h"
|
||||||
|
#include "nvim/fileio.h"
|
||||||
#include "nvim/func_attr.h"
|
#include "nvim/func_attr.h"
|
||||||
#include "nvim/globals.h" // for the global `time_fd` (startuptime)
|
#include "nvim/globals.h" // for the global `time_fd` (startuptime)
|
||||||
#include "nvim/os/os_defs.h"
|
#include "nvim/os/os.h"
|
||||||
#include "nvim/os/time.h"
|
#include "nvim/os/time.h"
|
||||||
#include "nvim/profile.h"
|
#include "nvim/profile.h"
|
||||||
|
#include "nvim/runtime.h"
|
||||||
|
#include "nvim/vim.h"
|
||||||
|
|
||||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
# include "profile.c.generated.h"
|
# include "profile.c.generated.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// Struct used in sn_prl_ga for every line of a script.
|
||||||
|
typedef struct sn_prl_S {
|
||||||
|
int snp_count; ///< nr of times line was executed
|
||||||
|
proftime_T sn_prl_total; ///< time spent in a line + children
|
||||||
|
proftime_T sn_prl_self; ///< time spent in a line itself
|
||||||
|
} sn_prl_T;
|
||||||
|
|
||||||
|
#define PRL_ITEM(si, idx) (((sn_prl_T *)(si)->sn_prl_ga.ga_data)[(idx)])
|
||||||
|
|
||||||
static proftime_T prof_wait_time;
|
static proftime_T prof_wait_time;
|
||||||
|
|
||||||
/// Gets the current time.
|
/// Gets the current time.
|
||||||
@ -195,6 +212,653 @@ int profile_cmp(proftime_T tm1, proftime_T tm2) FUNC_ATTR_CONST
|
|||||||
return profile_signed(tm2 - tm1) < 0 ? -1 : 1;
|
return profile_signed(tm2 - tm1) < 0 ? -1 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *profile_fname = NULL;
|
||||||
|
|
||||||
|
/// Reset all profiling information.
|
||||||
|
void profile_reset(void)
|
||||||
|
{
|
||||||
|
// Reset sourced files.
|
||||||
|
for (int id = 1; id <= script_items.ga_len; id++) {
|
||||||
|
scriptitem_T *si = &SCRIPT_ITEM(id);
|
||||||
|
if (si->sn_prof_on) {
|
||||||
|
si->sn_prof_on = false;
|
||||||
|
si->sn_pr_force = false;
|
||||||
|
si->sn_pr_child = profile_zero();
|
||||||
|
si->sn_pr_nest = 0;
|
||||||
|
si->sn_pr_count = 0;
|
||||||
|
si->sn_pr_total = profile_zero();
|
||||||
|
si->sn_pr_self = profile_zero();
|
||||||
|
si->sn_pr_start = profile_zero();
|
||||||
|
si->sn_pr_children = profile_zero();
|
||||||
|
ga_clear(&si->sn_prl_ga);
|
||||||
|
si->sn_prl_start = profile_zero();
|
||||||
|
si->sn_prl_children = profile_zero();
|
||||||
|
si->sn_prl_wait = profile_zero();
|
||||||
|
si->sn_prl_idx = -1;
|
||||||
|
si->sn_prl_execed = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset functions.
|
||||||
|
hashtab_T *const functbl = func_tbl_get();
|
||||||
|
size_t todo = functbl->ht_used;
|
||||||
|
hashitem_T *hi = functbl->ht_array;
|
||||||
|
|
||||||
|
for (; todo > (size_t)0; hi++) {
|
||||||
|
if (!HASHITEM_EMPTY(hi)) {
|
||||||
|
todo--;
|
||||||
|
ufunc_T *uf = HI2UF(hi);
|
||||||
|
if (uf->uf_prof_initialized) {
|
||||||
|
uf->uf_profiling = 0;
|
||||||
|
uf->uf_tm_count = 0;
|
||||||
|
uf->uf_tm_total = profile_zero();
|
||||||
|
uf->uf_tm_self = profile_zero();
|
||||||
|
uf->uf_tm_children = profile_zero();
|
||||||
|
|
||||||
|
for (int i = 0; i < uf->uf_lines.ga_len; i++) {
|
||||||
|
uf->uf_tml_count[i] = 0;
|
||||||
|
uf->uf_tml_total[i] = uf->uf_tml_self[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uf->uf_tml_start = profile_zero();
|
||||||
|
uf->uf_tml_children = profile_zero();
|
||||||
|
uf->uf_tml_wait = profile_zero();
|
||||||
|
uf->uf_tml_idx = -1;
|
||||||
|
uf->uf_tml_execed = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XFREE_CLEAR(profile_fname);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ":profile cmd args"
|
||||||
|
void ex_profile(exarg_T *eap)
|
||||||
|
{
|
||||||
|
static proftime_T pause_time;
|
||||||
|
|
||||||
|
char *e;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
e = (char *)skiptowhite((char_u *)eap->arg);
|
||||||
|
len = (int)(e - eap->arg);
|
||||||
|
e = skipwhite(e);
|
||||||
|
|
||||||
|
if (len == 5 && STRNCMP(eap->arg, "start", 5) == 0 && *e != NUL) {
|
||||||
|
xfree(profile_fname);
|
||||||
|
profile_fname = (char *)expand_env_save_opt((char_u *)e, true);
|
||||||
|
do_profiling = PROF_YES;
|
||||||
|
profile_set_wait(profile_zero());
|
||||||
|
set_vim_var_nr(VV_PROFILING, 1L);
|
||||||
|
} else if (do_profiling == PROF_NONE) {
|
||||||
|
emsg(_("E750: First use \":profile start {fname}\""));
|
||||||
|
} else if (STRCMP(eap->arg, "stop") == 0) {
|
||||||
|
profile_dump();
|
||||||
|
do_profiling = PROF_NONE;
|
||||||
|
set_vim_var_nr(VV_PROFILING, 0L);
|
||||||
|
profile_reset();
|
||||||
|
} else if (STRCMP(eap->arg, "pause") == 0) {
|
||||||
|
if (do_profiling == PROF_YES) {
|
||||||
|
pause_time = profile_start();
|
||||||
|
}
|
||||||
|
do_profiling = PROF_PAUSED;
|
||||||
|
} else if (STRCMP(eap->arg, "continue") == 0) {
|
||||||
|
if (do_profiling == PROF_PAUSED) {
|
||||||
|
pause_time = profile_end(pause_time);
|
||||||
|
profile_set_wait(profile_add(profile_get_wait(), pause_time));
|
||||||
|
}
|
||||||
|
do_profiling = PROF_YES;
|
||||||
|
} else if (STRCMP(eap->arg, "dump") == 0) {
|
||||||
|
profile_dump();
|
||||||
|
} else {
|
||||||
|
// The rest is similar to ":breakadd".
|
||||||
|
ex_breakadd(eap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Command line expansion for :profile.
|
||||||
|
static enum {
|
||||||
|
PEXP_SUBCMD, ///< expand :profile sub-commands
|
||||||
|
PEXP_FUNC, ///< expand :profile func {funcname}
|
||||||
|
} pexpand_what;
|
||||||
|
|
||||||
|
static char *pexpand_cmds[] = {
|
||||||
|
"continue",
|
||||||
|
"dump",
|
||||||
|
"file",
|
||||||
|
"func",
|
||||||
|
"pause",
|
||||||
|
"start",
|
||||||
|
"stop",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Function given to ExpandGeneric() to obtain the profile command
|
||||||
|
/// specific expansion.
|
||||||
|
char *get_profile_name(expand_T *xp, int idx)
|
||||||
|
FUNC_ATTR_PURE
|
||||||
|
{
|
||||||
|
switch (pexpand_what) {
|
||||||
|
case PEXP_SUBCMD:
|
||||||
|
return pexpand_cmds[idx];
|
||||||
|
// case PEXP_FUNC: TODO
|
||||||
|
default:
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle command line completion for :profile command.
|
||||||
|
void set_context_in_profile_cmd(expand_T *xp, const char *arg)
|
||||||
|
{
|
||||||
|
// Default: expand subcommands.
|
||||||
|
xp->xp_context = EXPAND_PROFILE;
|
||||||
|
pexpand_what = PEXP_SUBCMD;
|
||||||
|
xp->xp_pattern = (char *)arg;
|
||||||
|
|
||||||
|
char_u *const end_subcmd = skiptowhite((const char_u *)arg);
|
||||||
|
if (*end_subcmd == NUL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((const char *)end_subcmd - arg == 5 && strncmp(arg, "start", 5) == 0) {
|
||||||
|
xp->xp_context = EXPAND_FILES;
|
||||||
|
xp->xp_pattern = skipwhite((char *)end_subcmd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(tarruda): expand function names after "func"
|
||||||
|
xp->xp_context = EXPAND_NOTHING;
|
||||||
|
}
|
||||||
|
|
||||||
|
static proftime_T inchar_time;
|
||||||
|
|
||||||
|
/// Called when starting to wait for the user to type a character.
|
||||||
|
void prof_inchar_enter(void)
|
||||||
|
{
|
||||||
|
inchar_time = profile_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when finished waiting for the user to type a character.
|
||||||
|
void prof_inchar_exit(void)
|
||||||
|
{
|
||||||
|
inchar_time = profile_end(inchar_time);
|
||||||
|
profile_set_wait(profile_add(profile_get_wait(), inchar_time));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @return true when a function defined in the current script should be
|
||||||
|
/// profiled.
|
||||||
|
bool prof_def_func(void)
|
||||||
|
FUNC_ATTR_PURE
|
||||||
|
{
|
||||||
|
if (current_sctx.sc_sid > 0) {
|
||||||
|
return SCRIPT_ITEM(current_sctx.sc_sid).sn_pr_force;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Print the count and times for one function or function line.
|
||||||
|
///
|
||||||
|
/// @param prefer_self when equal print only self time
|
||||||
|
static void prof_func_line(FILE *fd, int count, proftime_T *total, proftime_T *self,
|
||||||
|
bool prefer_self)
|
||||||
|
{
|
||||||
|
if (count > 0) {
|
||||||
|
fprintf(fd, "%5d ", count);
|
||||||
|
if (prefer_self && profile_equal(*total, *self)) {
|
||||||
|
fprintf(fd, " ");
|
||||||
|
} else {
|
||||||
|
fprintf(fd, "%s ", profile_msg(*total));
|
||||||
|
}
|
||||||
|
if (!prefer_self && profile_equal(*total, *self)) {
|
||||||
|
fprintf(fd, " ");
|
||||||
|
} else {
|
||||||
|
fprintf(fd, "%s ", profile_msg(*self));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fprintf(fd, " ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @param prefer_self when equal print only self time
|
||||||
|
static void prof_sort_list(FILE *fd, ufunc_T **sorttab, int st_len, char *title, bool prefer_self)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
ufunc_T *fp;
|
||||||
|
|
||||||
|
fprintf(fd, "FUNCTIONS SORTED ON %s TIME\n", title);
|
||||||
|
fprintf(fd, "count total (s) self (s) function\n");
|
||||||
|
for (i = 0; i < 20 && i < st_len; i++) {
|
||||||
|
fp = sorttab[i];
|
||||||
|
prof_func_line(fd, fp->uf_tm_count, &fp->uf_tm_total, &fp->uf_tm_self,
|
||||||
|
prefer_self);
|
||||||
|
if (fp->uf_name[0] == K_SPECIAL) {
|
||||||
|
fprintf(fd, " <SNR>%s()\n", fp->uf_name + 3);
|
||||||
|
} else {
|
||||||
|
fprintf(fd, " %s()\n", fp->uf_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprintf(fd, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compare function for total time sorting.
|
||||||
|
static int prof_total_cmp(const void *s1, const void *s2)
|
||||||
|
{
|
||||||
|
ufunc_T *p1 = *(ufunc_T **)s1;
|
||||||
|
ufunc_T *p2 = *(ufunc_T **)s2;
|
||||||
|
return profile_cmp(p1->uf_tm_total, p2->uf_tm_total);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compare function for self time sorting.
|
||||||
|
static int prof_self_cmp(const void *s1, const void *s2)
|
||||||
|
{
|
||||||
|
ufunc_T *p1 = *(ufunc_T **)s1;
|
||||||
|
ufunc_T *p2 = *(ufunc_T **)s2;
|
||||||
|
return profile_cmp(p1->uf_tm_self, p2->uf_tm_self);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start profiling function "fp".
|
||||||
|
void func_do_profile(ufunc_T *fp)
|
||||||
|
{
|
||||||
|
int len = fp->uf_lines.ga_len;
|
||||||
|
|
||||||
|
if (!fp->uf_prof_initialized) {
|
||||||
|
if (len == 0) {
|
||||||
|
len = 1; // avoid getting error for allocating zero bytes
|
||||||
|
}
|
||||||
|
fp->uf_tm_count = 0;
|
||||||
|
fp->uf_tm_self = profile_zero();
|
||||||
|
fp->uf_tm_total = profile_zero();
|
||||||
|
|
||||||
|
if (fp->uf_tml_count == NULL) {
|
||||||
|
fp->uf_tml_count = xcalloc((size_t)len, sizeof(int));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fp->uf_tml_total == NULL) {
|
||||||
|
fp->uf_tml_total = xcalloc((size_t)len, sizeof(proftime_T));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fp->uf_tml_self == NULL) {
|
||||||
|
fp->uf_tml_self = xcalloc((size_t)len, sizeof(proftime_T));
|
||||||
|
}
|
||||||
|
|
||||||
|
fp->uf_tml_idx = -1;
|
||||||
|
fp->uf_prof_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fp->uf_profiling = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepare profiling for entering a child or something else that is not
|
||||||
|
/// counted for the script/function itself.
|
||||||
|
/// Should always be called in pair with prof_child_exit().
|
||||||
|
///
|
||||||
|
/// @param tm place to store waittime
|
||||||
|
void prof_child_enter(proftime_T *tm)
|
||||||
|
{
|
||||||
|
funccall_T *fc = get_current_funccal();
|
||||||
|
|
||||||
|
if (fc != NULL && fc->func->uf_profiling) {
|
||||||
|
fc->prof_child = profile_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
script_prof_save(tm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Take care of time spent in a child.
|
||||||
|
/// Should always be called after prof_child_enter().
|
||||||
|
///
|
||||||
|
/// @param tm where waittime was stored
|
||||||
|
void prof_child_exit(proftime_T *tm)
|
||||||
|
{
|
||||||
|
funccall_T *fc = get_current_funccal();
|
||||||
|
|
||||||
|
if (fc != NULL && fc->func->uf_profiling) {
|
||||||
|
fc->prof_child = profile_end(fc->prof_child);
|
||||||
|
// don't count waiting time
|
||||||
|
fc->prof_child = profile_sub_wait(*tm, fc->prof_child);
|
||||||
|
fc->func->uf_tm_children =
|
||||||
|
profile_add(fc->func->uf_tm_children, fc->prof_child);
|
||||||
|
fc->func->uf_tml_children =
|
||||||
|
profile_add(fc->func->uf_tml_children, fc->prof_child);
|
||||||
|
}
|
||||||
|
script_prof_restore(tm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when starting to read a function line.
|
||||||
|
/// "sourcing_lnum" must be correct!
|
||||||
|
/// When skipping lines it may not actually be executed, but we won't find out
|
||||||
|
/// until later and we need to store the time now.
|
||||||
|
void func_line_start(void *cookie)
|
||||||
|
{
|
||||||
|
funccall_T *fcp = (funccall_T *)cookie;
|
||||||
|
ufunc_T *fp = fcp->func;
|
||||||
|
|
||||||
|
if (fp->uf_profiling && sourcing_lnum >= 1
|
||||||
|
&& sourcing_lnum <= fp->uf_lines.ga_len) {
|
||||||
|
fp->uf_tml_idx = sourcing_lnum - 1;
|
||||||
|
// Skip continuation lines.
|
||||||
|
while (fp->uf_tml_idx > 0 && FUNCLINE(fp, fp->uf_tml_idx) == NULL) {
|
||||||
|
fp->uf_tml_idx--;
|
||||||
|
}
|
||||||
|
fp->uf_tml_execed = false;
|
||||||
|
fp->uf_tml_start = profile_start();
|
||||||
|
fp->uf_tml_children = profile_zero();
|
||||||
|
fp->uf_tml_wait = profile_get_wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when actually executing a function line.
|
||||||
|
void func_line_exec(void *cookie)
|
||||||
|
{
|
||||||
|
funccall_T *fcp = (funccall_T *)cookie;
|
||||||
|
ufunc_T *fp = fcp->func;
|
||||||
|
|
||||||
|
if (fp->uf_profiling && fp->uf_tml_idx >= 0) {
|
||||||
|
fp->uf_tml_execed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when done with a function line.
|
||||||
|
void func_line_end(void *cookie)
|
||||||
|
{
|
||||||
|
funccall_T *fcp = (funccall_T *)cookie;
|
||||||
|
ufunc_T *fp = fcp->func;
|
||||||
|
|
||||||
|
if (fp->uf_profiling && fp->uf_tml_idx >= 0) {
|
||||||
|
if (fp->uf_tml_execed) {
|
||||||
|
fp->uf_tml_count[fp->uf_tml_idx]++;
|
||||||
|
fp->uf_tml_start = profile_end(fp->uf_tml_start);
|
||||||
|
fp->uf_tml_start = profile_sub_wait(fp->uf_tml_wait, fp->uf_tml_start);
|
||||||
|
fp->uf_tml_total[fp->uf_tml_idx] =
|
||||||
|
profile_add(fp->uf_tml_total[fp->uf_tml_idx], fp->uf_tml_start);
|
||||||
|
fp->uf_tml_self[fp->uf_tml_idx] =
|
||||||
|
profile_self(fp->uf_tml_self[fp->uf_tml_idx], fp->uf_tml_start,
|
||||||
|
fp->uf_tml_children);
|
||||||
|
}
|
||||||
|
fp->uf_tml_idx = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dump the profiling results for all functions in file "fd".
|
||||||
|
static void func_dump_profile(FILE *fd)
|
||||||
|
{
|
||||||
|
hashtab_T *const functbl = func_tbl_get();
|
||||||
|
hashitem_T *hi;
|
||||||
|
int todo;
|
||||||
|
ufunc_T *fp;
|
||||||
|
ufunc_T **sorttab;
|
||||||
|
int st_len = 0;
|
||||||
|
|
||||||
|
todo = (int)functbl->ht_used;
|
||||||
|
if (todo == 0) {
|
||||||
|
return; // nothing to dump
|
||||||
|
}
|
||||||
|
|
||||||
|
sorttab = xmalloc(sizeof(ufunc_T *) * (size_t)todo);
|
||||||
|
|
||||||
|
for (hi = functbl->ht_array; todo > 0; hi++) {
|
||||||
|
if (!HASHITEM_EMPTY(hi)) {
|
||||||
|
todo--;
|
||||||
|
fp = HI2UF(hi);
|
||||||
|
if (fp->uf_prof_initialized) {
|
||||||
|
sorttab[st_len++] = fp;
|
||||||
|
|
||||||
|
if (fp->uf_name[0] == K_SPECIAL) {
|
||||||
|
fprintf(fd, "FUNCTION <SNR>%s()\n", fp->uf_name + 3);
|
||||||
|
} else {
|
||||||
|
fprintf(fd, "FUNCTION %s()\n", fp->uf_name);
|
||||||
|
}
|
||||||
|
if (fp->uf_script_ctx.sc_sid != 0) {
|
||||||
|
bool should_free;
|
||||||
|
const LastSet last_set = (LastSet){
|
||||||
|
.script_ctx = fp->uf_script_ctx,
|
||||||
|
.channel_id = 0,
|
||||||
|
};
|
||||||
|
char *p = (char *)get_scriptname(last_set, &should_free);
|
||||||
|
fprintf(fd, " Defined: %s:%" PRIdLINENR "\n",
|
||||||
|
p, fp->uf_script_ctx.sc_lnum);
|
||||||
|
if (should_free) {
|
||||||
|
xfree(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fp->uf_tm_count == 1) {
|
||||||
|
fprintf(fd, "Called 1 time\n");
|
||||||
|
} else {
|
||||||
|
fprintf(fd, "Called %d times\n", fp->uf_tm_count);
|
||||||
|
}
|
||||||
|
fprintf(fd, "Total time: %s\n", profile_msg(fp->uf_tm_total));
|
||||||
|
fprintf(fd, " Self time: %s\n", profile_msg(fp->uf_tm_self));
|
||||||
|
fprintf(fd, "\n");
|
||||||
|
fprintf(fd, "count total (s) self (s)\n");
|
||||||
|
|
||||||
|
for (int i = 0; i < fp->uf_lines.ga_len; i++) {
|
||||||
|
if (FUNCLINE(fp, i) == NULL) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
prof_func_line(fd, fp->uf_tml_count[i],
|
||||||
|
&fp->uf_tml_total[i], &fp->uf_tml_self[i], true);
|
||||||
|
fprintf(fd, "%s\n", FUNCLINE(fp, i));
|
||||||
|
}
|
||||||
|
fprintf(fd, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (st_len > 0) {
|
||||||
|
qsort((void *)sorttab, (size_t)st_len, sizeof(ufunc_T *),
|
||||||
|
prof_total_cmp);
|
||||||
|
prof_sort_list(fd, sorttab, st_len, "TOTAL", false);
|
||||||
|
qsort((void *)sorttab, (size_t)st_len, sizeof(ufunc_T *),
|
||||||
|
prof_self_cmp);
|
||||||
|
prof_sort_list(fd, sorttab, st_len, "SELF", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
xfree(sorttab);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start profiling a script.
|
||||||
|
void profile_init(scriptitem_T *si)
|
||||||
|
{
|
||||||
|
si->sn_pr_count = 0;
|
||||||
|
si->sn_pr_total = profile_zero();
|
||||||
|
si->sn_pr_self = profile_zero();
|
||||||
|
|
||||||
|
ga_init(&si->sn_prl_ga, sizeof(sn_prl_T), 100);
|
||||||
|
si->sn_prl_idx = -1;
|
||||||
|
si->sn_prof_on = true;
|
||||||
|
si->sn_pr_nest = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save time when starting to invoke another script or function.
|
||||||
|
///
|
||||||
|
/// @param tm place to store wait time
|
||||||
|
void script_prof_save(proftime_T *tm)
|
||||||
|
{
|
||||||
|
scriptitem_T *si;
|
||||||
|
|
||||||
|
if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= script_items.ga_len) {
|
||||||
|
si = &SCRIPT_ITEM(current_sctx.sc_sid);
|
||||||
|
if (si->sn_prof_on && si->sn_pr_nest++ == 0) {
|
||||||
|
si->sn_pr_child = profile_start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*tm = profile_get_wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Count time spent in children after invoking another script or function.
|
||||||
|
void script_prof_restore(proftime_T *tm)
|
||||||
|
{
|
||||||
|
scriptitem_T *si;
|
||||||
|
|
||||||
|
if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= script_items.ga_len) {
|
||||||
|
si = &SCRIPT_ITEM(current_sctx.sc_sid);
|
||||||
|
if (si->sn_prof_on && --si->sn_pr_nest == 0) {
|
||||||
|
si->sn_pr_child = profile_end(si->sn_pr_child);
|
||||||
|
// don't count wait time
|
||||||
|
si->sn_pr_child = profile_sub_wait(*tm, si->sn_pr_child);
|
||||||
|
si->sn_pr_children = profile_add(si->sn_pr_children, si->sn_pr_child);
|
||||||
|
si->sn_prl_children = profile_add(si->sn_prl_children, si->sn_pr_child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dump the profiling results for all scripts in file "fd".
|
||||||
|
static void script_dump_profile(FILE *fd)
|
||||||
|
{
|
||||||
|
scriptitem_T *si;
|
||||||
|
FILE *sfd;
|
||||||
|
sn_prl_T *pp;
|
||||||
|
|
||||||
|
for (int id = 1; id <= script_items.ga_len; id++) {
|
||||||
|
si = &SCRIPT_ITEM(id);
|
||||||
|
if (si->sn_prof_on) {
|
||||||
|
fprintf(fd, "SCRIPT %s\n", si->sn_name);
|
||||||
|
if (si->sn_pr_count == 1) {
|
||||||
|
fprintf(fd, "Sourced 1 time\n");
|
||||||
|
} else {
|
||||||
|
fprintf(fd, "Sourced %d times\n", si->sn_pr_count);
|
||||||
|
}
|
||||||
|
fprintf(fd, "Total time: %s\n", profile_msg(si->sn_pr_total));
|
||||||
|
fprintf(fd, " Self time: %s\n", profile_msg(si->sn_pr_self));
|
||||||
|
fprintf(fd, "\n");
|
||||||
|
fprintf(fd, "count total (s) self (s)\n");
|
||||||
|
|
||||||
|
sfd = os_fopen((char *)si->sn_name, "r");
|
||||||
|
if (sfd == NULL) {
|
||||||
|
fprintf(fd, "Cannot open file!\n");
|
||||||
|
} else {
|
||||||
|
// Keep going till the end of file, so that trailing
|
||||||
|
// continuation lines are listed.
|
||||||
|
for (int i = 0;; i++) {
|
||||||
|
if (vim_fgets(IObuff, IOSIZE, sfd)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// When a line has been truncated, append NL, taking care
|
||||||
|
// of multi-byte characters .
|
||||||
|
if (IObuff[IOSIZE - 2] != NUL && IObuff[IOSIZE - 2] != NL) {
|
||||||
|
int n = IOSIZE - 2;
|
||||||
|
|
||||||
|
// Move to the first byte of this char.
|
||||||
|
// utf_head_off() doesn't work, because it checks
|
||||||
|
// for a truncated character.
|
||||||
|
while (n > 0 && (IObuff[n] & 0xc0) == 0x80) {
|
||||||
|
n--;
|
||||||
|
}
|
||||||
|
|
||||||
|
IObuff[n] = NL;
|
||||||
|
IObuff[n + 1] = NUL;
|
||||||
|
}
|
||||||
|
if (i < si->sn_prl_ga.ga_len
|
||||||
|
&& (pp = &PRL_ITEM(si, i))->snp_count > 0) {
|
||||||
|
fprintf(fd, "%5d ", pp->snp_count);
|
||||||
|
if (profile_equal(pp->sn_prl_total, pp->sn_prl_self)) {
|
||||||
|
fprintf(fd, " ");
|
||||||
|
} else {
|
||||||
|
fprintf(fd, "%s ", profile_msg(pp->sn_prl_total));
|
||||||
|
}
|
||||||
|
fprintf(fd, "%s ", profile_msg(pp->sn_prl_self));
|
||||||
|
} else {
|
||||||
|
fprintf(fd, " ");
|
||||||
|
}
|
||||||
|
fprintf(fd, "%s", IObuff);
|
||||||
|
}
|
||||||
|
fclose(sfd);
|
||||||
|
}
|
||||||
|
fprintf(fd, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dump the profiling info.
|
||||||
|
void profile_dump(void)
|
||||||
|
{
|
||||||
|
FILE *fd;
|
||||||
|
|
||||||
|
if (profile_fname != NULL) {
|
||||||
|
fd = os_fopen(profile_fname, "w");
|
||||||
|
if (fd == NULL) {
|
||||||
|
semsg(_(e_notopen), profile_fname);
|
||||||
|
} else {
|
||||||
|
script_dump_profile(fd);
|
||||||
|
func_dump_profile(fd);
|
||||||
|
fclose(fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when starting to read a script line.
|
||||||
|
/// "sourcing_lnum" must be correct!
|
||||||
|
/// When skipping lines it may not actually be executed, but we won't find out
|
||||||
|
/// until later and we need to store the time now.
|
||||||
|
void script_line_start(void)
|
||||||
|
{
|
||||||
|
scriptitem_T *si;
|
||||||
|
sn_prl_T *pp;
|
||||||
|
|
||||||
|
if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
si = &SCRIPT_ITEM(current_sctx.sc_sid);
|
||||||
|
if (si->sn_prof_on && sourcing_lnum >= 1) {
|
||||||
|
// Grow the array before starting the timer, so that the time spent
|
||||||
|
// here isn't counted.
|
||||||
|
(void)ga_grow(&si->sn_prl_ga, sourcing_lnum - si->sn_prl_ga.ga_len);
|
||||||
|
si->sn_prl_idx = sourcing_lnum - 1;
|
||||||
|
while (si->sn_prl_ga.ga_len <= si->sn_prl_idx
|
||||||
|
&& si->sn_prl_ga.ga_len < si->sn_prl_ga.ga_maxlen) {
|
||||||
|
// Zero counters for a line that was not used before.
|
||||||
|
pp = &PRL_ITEM(si, si->sn_prl_ga.ga_len);
|
||||||
|
pp->snp_count = 0;
|
||||||
|
pp->sn_prl_total = profile_zero();
|
||||||
|
pp->sn_prl_self = profile_zero();
|
||||||
|
si->sn_prl_ga.ga_len++;
|
||||||
|
}
|
||||||
|
si->sn_prl_execed = false;
|
||||||
|
si->sn_prl_start = profile_start();
|
||||||
|
si->sn_prl_children = profile_zero();
|
||||||
|
si->sn_prl_wait = profile_get_wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when actually executing a function line.
|
||||||
|
void script_line_exec(void)
|
||||||
|
{
|
||||||
|
scriptitem_T *si;
|
||||||
|
|
||||||
|
if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
si = &SCRIPT_ITEM(current_sctx.sc_sid);
|
||||||
|
if (si->sn_prof_on && si->sn_prl_idx >= 0) {
|
||||||
|
si->sn_prl_execed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when done with a function line.
|
||||||
|
void script_line_end(void)
|
||||||
|
{
|
||||||
|
scriptitem_T *si;
|
||||||
|
sn_prl_T *pp;
|
||||||
|
|
||||||
|
if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
si = &SCRIPT_ITEM(current_sctx.sc_sid);
|
||||||
|
if (si->sn_prof_on && si->sn_prl_idx >= 0
|
||||||
|
&& si->sn_prl_idx < si->sn_prl_ga.ga_len) {
|
||||||
|
if (si->sn_prl_execed) {
|
||||||
|
pp = &PRL_ITEM(si, si->sn_prl_idx);
|
||||||
|
pp->snp_count++;
|
||||||
|
si->sn_prl_start = profile_end(si->sn_prl_start);
|
||||||
|
si->sn_prl_start = profile_sub_wait(si->sn_prl_wait, si->sn_prl_start);
|
||||||
|
pp->sn_prl_total = profile_add(pp->sn_prl_total, si->sn_prl_start);
|
||||||
|
pp->sn_prl_self = profile_self(pp->sn_prl_self, si->sn_prl_start,
|
||||||
|
si->sn_prl_children);
|
||||||
|
}
|
||||||
|
si->sn_prl_idx = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// globals for use in the startuptime related functionality (time_*).
|
/// globals for use in the startuptime related functionality (time_*).
|
||||||
static proftime_T g_start_time;
|
static proftime_T g_start_time;
|
||||||
static proftime_T g_prev_time;
|
static proftime_T g_prev_time;
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
typedef uint64_t proftime_T;
|
#include "nvim/ex_cmds_defs.h"
|
||||||
|
#include "nvim/runtime.h"
|
||||||
|
|
||||||
#define TIME_MSG(s) do { \
|
#define TIME_MSG(s) do { \
|
||||||
if (time_fd != NULL) time_msg(s, NULL); \
|
if (time_fd != NULL) time_msg(s, NULL); \
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
#include "nvim/message.h"
|
#include "nvim/message.h"
|
||||||
#include "nvim/os/input.h"
|
#include "nvim/os/input.h"
|
||||||
#include "nvim/plines.h"
|
#include "nvim/plines.h"
|
||||||
|
#include "nvim/profile.h"
|
||||||
#include "nvim/regexp.h"
|
#include "nvim/regexp.h"
|
||||||
#include "nvim/strings.h"
|
#include "nvim/strings.h"
|
||||||
#include "nvim/vim.h"
|
#include "nvim/vim.h"
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "nvim/pos.h"
|
#include "nvim/pos.h"
|
||||||
#include "nvim/profile.h"
|
|
||||||
#include "nvim/types.h"
|
#include "nvim/types.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
#include "nvim/lua/executor.h"
|
#include "nvim/lua/executor.h"
|
||||||
#include "nvim/option.h"
|
#include "nvim/option.h"
|
||||||
#include "nvim/os/os.h"
|
#include "nvim/os/os.h"
|
||||||
|
#include "nvim/profile.h"
|
||||||
#include "nvim/runtime.h"
|
#include "nvim/runtime.h"
|
||||||
#include "nvim/vim.h"
|
#include "nvim/vim.h"
|
||||||
|
|
||||||
@ -21,6 +22,8 @@
|
|||||||
# include "runtime.c.generated.h"
|
# include "runtime.c.generated.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
garray_T script_items = { 0, 0, sizeof(scriptitem_T), 4, NULL };
|
||||||
|
|
||||||
static bool runtime_search_path_valid = false;
|
static bool runtime_search_path_valid = false;
|
||||||
static int *runtime_search_path_ref = NULL;
|
static int *runtime_search_path_ref = NULL;
|
||||||
static RuntimeSearchPath runtime_search_path;
|
static RuntimeSearchPath runtime_search_path;
|
||||||
|
@ -5,6 +5,31 @@
|
|||||||
|
|
||||||
#include "nvim/ex_docmd.h"
|
#include "nvim/ex_docmd.h"
|
||||||
|
|
||||||
|
typedef struct scriptitem_S {
|
||||||
|
char_u *sn_name;
|
||||||
|
bool sn_prof_on; ///< true when script is/was profiled
|
||||||
|
bool sn_pr_force; ///< forceit: profile functions in this script
|
||||||
|
proftime_T sn_pr_child; ///< time set when going into first child
|
||||||
|
int sn_pr_nest; ///< nesting for sn_pr_child
|
||||||
|
// profiling the script as a whole
|
||||||
|
int sn_pr_count; ///< nr of times sourced
|
||||||
|
proftime_T sn_pr_total; ///< time spent in script + children
|
||||||
|
proftime_T sn_pr_self; ///< time spent in script itself
|
||||||
|
proftime_T sn_pr_start; ///< time at script start
|
||||||
|
proftime_T sn_pr_children; ///< time in children after script start
|
||||||
|
// profiling the script per line
|
||||||
|
garray_T sn_prl_ga; ///< things stored for every line
|
||||||
|
proftime_T sn_prl_start; ///< start time for current line
|
||||||
|
proftime_T sn_prl_children; ///< time spent in children for this line
|
||||||
|
proftime_T sn_prl_wait; ///< wait start time for current line
|
||||||
|
linenr_T sn_prl_idx; ///< index of line being timed; -1 if none
|
||||||
|
int sn_prl_execed; ///< line being timed was executed
|
||||||
|
} scriptitem_T;
|
||||||
|
|
||||||
|
/// Growarray to store info about already sourced scripts.
|
||||||
|
extern garray_T script_items;
|
||||||
|
#define SCRIPT_ITEM(id) (((scriptitem_T *)script_items.ga_data)[(id) - 1])
|
||||||
|
|
||||||
typedef void (*DoInRuntimepathCB)(char *, void *);
|
typedef void (*DoInRuntimepathCB)(char *, void *);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -112,6 +112,7 @@
|
|||||||
#include "nvim/path.h"
|
#include "nvim/path.h"
|
||||||
#include "nvim/plines.h"
|
#include "nvim/plines.h"
|
||||||
#include "nvim/popupmnu.h"
|
#include "nvim/popupmnu.h"
|
||||||
|
#include "nvim/profile.h"
|
||||||
#include "nvim/quickfix.h"
|
#include "nvim/quickfix.h"
|
||||||
#include "nvim/regexp.h"
|
#include "nvim/regexp.h"
|
||||||
#include "nvim/screen.h"
|
#include "nvim/screen.h"
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
#include "nvim/os/input.h"
|
#include "nvim/os/input.h"
|
||||||
#include "nvim/os/time.h"
|
#include "nvim/os/time.h"
|
||||||
#include "nvim/path.h"
|
#include "nvim/path.h"
|
||||||
|
#include "nvim/profile.h"
|
||||||
#include "nvim/regexp.h"
|
#include "nvim/regexp.h"
|
||||||
#include "nvim/screen.h"
|
#include "nvim/screen.h"
|
||||||
#include "nvim/search.h"
|
#include "nvim/search.h"
|
||||||
|
@ -107,6 +107,7 @@
|
|||||||
#include "nvim/os/os.h"
|
#include "nvim/os/os.h"
|
||||||
#include "nvim/os_unix.h"
|
#include "nvim/os_unix.h"
|
||||||
#include "nvim/path.h"
|
#include "nvim/path.h"
|
||||||
|
#include "nvim/profile.h"
|
||||||
#include "nvim/regexp.h"
|
#include "nvim/regexp.h"
|
||||||
#include "nvim/screen.h"
|
#include "nvim/screen.h"
|
||||||
#include "nvim/search.h"
|
#include "nvim/search.h"
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
#include "nvim/os/time.h"
|
#include "nvim/os/time.h"
|
||||||
#include "nvim/os_unix.h"
|
#include "nvim/os_unix.h"
|
||||||
#include "nvim/path.h"
|
#include "nvim/path.h"
|
||||||
|
#include "nvim/profile.h"
|
||||||
#include "nvim/regexp.h"
|
#include "nvim/regexp.h"
|
||||||
#include "nvim/screen.h"
|
#include "nvim/screen.h"
|
||||||
#include "nvim/sign.h"
|
#include "nvim/sign.h"
|
||||||
|
@ -28,6 +28,8 @@ typedef handle_T NS;
|
|||||||
|
|
||||||
typedef struct expand expand_T;
|
typedef struct expand expand_T;
|
||||||
|
|
||||||
|
typedef uint64_t proftime_T;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
kNone = -1,
|
kNone = -1,
|
||||||
kFalse = 0,
|
kFalse = 0,
|
||||||
|
Loading…
Reference in New Issue
Block a user