trace2: create new combined trace facility

Create a new unified tracing facility for git. The eventual intent is to replace the current trace_printf* and trace_performance* routines with a unified set of git_trace2* routines. In addition to the usual printf-style API, trace2 provides higer-level event verbs with fixed-fields allowing structured data to be written. This makes post-processing and analysis easier for external tools. Trace2 defines 3 output targets. These are set using the environment variables "GIT_TR2", "GIT_TR2_PERF", and "GIT_TR2_EVENT". These may be set to "1" or to an absolute pathname (just like the current GIT_TRACE). * GIT_TR2 is intended to be a replacement for GIT_TRACE and logs command summary data. * GIT_TR2_PERF is intended as a replacement for GIT_TRACE_PERFORMANCE. It extends the output with columns for the command process, thread, repo, absolute and relative elapsed times. It reports events for child process start/stop, thread start/stop, and per-thread function nesting. * GIT_TR2_EVENT is a new structured format. It writes event data as a series of JSON records. Calls to trace2 functions log to any of the 3 output targets enabled without the need to call different trace_printf* or trace_performance* routines. Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff Hostetler committed Feb 22, 2019 at 14:25 UTC ee4512ed481a126dadd33334186e0e759d7f2f47
39 files changed +3806 -18
Makefile
+13 -1
@@ -1017,6 +1017,16 @@ LIB_OBJS += tempfile.o
1017 LIB_OBJS += thread-utils.o
1018 LIB_OBJS += tmp-objdir.o
1019 LIB_OBJS += trace.o
1020 +LIB_OBJS += trace2.o
1021 +LIB_OBJS += trace2/tr2_cfg.o
1022 +LIB_OBJS += trace2/tr2_cmd_name.o
1023 +LIB_OBJS += trace2/tr2_dst.o
1024 +LIB_OBJS += trace2/tr2_sid.o
1025 +LIB_OBJS += trace2/tr2_tbuf.o
1026 +LIB_OBJS += trace2/tr2_tgt_event.o
1027 +LIB_OBJS += trace2/tr2_tgt_normal.o
1028 +LIB_OBJS += trace2/tr2_tgt_perf.o
1029 +LIB_OBJS += trace2/tr2_tls.o
1030 LIB_OBJS += trailer.o
1031 LIB_OBJS += transport.o
1032 LIB_OBJS += transport-helper.o
@@ -1596,7 +1606,9 @@ ifdef NO_INET_PTON
1606 LIB_OBJS += compat/inet_pton.o
1607 BASIC_CFLAGS += -DNO_INET_PTON
1608 endif
1599 -ifndef NO_UNIX_SOCKETS
1609 +ifdef NO_UNIX_SOCKETS
1610 + BASIC_CFLAGS += -DNO_UNIX_SOCKETS
1611 +else
1612 LIB_OBJS += unix-socket.o
1613 PROGRAM_OBJS += credential-cache.o
1614 PROGRAM_OBJS += credential-cache--daemon.o
builtin/submodule--helper.c
+4 -5
@@ -1816,11 +1816,10 @@ static int update_submodules(struct submodule_update_clone *suc)
1816 {
1817 int i;
1818
1819 - run_processes_parallel(suc->max_jobs,
1820 - update_clone_get_next_task,
1821 - update_clone_start_failure,
1822 - update_clone_task_finished,
1823 - suc);
1819 + run_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task,
1820 + update_clone_start_failure,
1821 + update_clone_task_finished, suc, "submodule",
1822 + "parallel/update");
1823
1824 /*
1825 * We saved the output and put it out all at once now.
cache.h
+1
@@ -9,6 +9,7 @@
9 #include "gettext.h"
10 #include "convert.h"
11 #include "trace.h"
12 +#include "trace2.h"
13 #include "string-list.h"
14 #include "pack-revindex.h"
15 #include "hash.h"
common-main.c
+10 -2
@@ -25,12 +25,18 @@ static void restore_sigpipe_to_default(void)
25
26 int main(int argc, const char **argv)
27 {
28 + int result;
29 +
30 /*
31 * Always open file descriptors 0/1/2 to avoid clobbering files
32 * in die(). It also avoids messing up when the pipes are dup'ed
33 * onto stdin/stdout/stderr in the child processes we spawn.
34 */
35 sanitize_stdfds();
36 + restore_sigpipe_to_default();
37 +
38 + trace2_initialize();
39 + trace2_cmd_start(argv);
40
41 git_resolve_executable_dir(argv[0]);
42
@@ -40,7 +46,9 @@ int main(int argc, const char **argv)
46
47 attr_start();
48
43 - restore_sigpipe_to_default();
49 + result = cmd_main(argc, argv);
50 +
51 + trace2_cmd_exit(result);
52
45 - return cmd_main(argc, argv);
53 + return result;
54 }
compat/mingw.c
+10 -1
@@ -1551,19 +1551,23 @@ static int try_shell_exec(const char *cmd, char *const *argv)
1551 return 0;
1552 prog = path_lookup(interpr, 1);
1553 if (prog) {
1554 + int exec_id;
1555 int argc = 0;
1556 const char **argv2;
1557 while (argv[argc]) argc++;
1558 ALLOC_ARRAY(argv2, argc + 1);
1559 argv2[0] = (char *)cmd; /* full path to the script file */
1560 memcpy(&argv2[1], &argv[1], sizeof(*argv) * argc);
1561 + exec_id = trace2_exec(prog, argv2);
1562 pid = mingw_spawnv(prog, argv2, 1);
1563 if (pid >= 0) {
1564 int status;
1565 if (waitpid(pid, &status, 0) < 0)
1566 status = 255;
1567 + trace2_exec_result(exec_id, status);
1568 exit(status);
1569 }
1570 + trace2_exec_result(exec_id, -1);
1571 pid = 1; /* indicate that we tried but failed */
1572 free(prog);
1573 free(argv2);
@@ -1576,12 +1580,17 @@ int mingw_execv(const char *cmd, char *const *argv)
1580 /* check if git_command is a shell script */
1581 if (!try_shell_exec(cmd, argv)) {
1582 int pid, status;
1583 + int exec_id;
1584
1585 + exec_id = trace2_exec(cmd, (const char **)argv);
1586 pid = mingw_spawnv(cmd, (const char **)argv, 0);
1581 - if (pid < 0)
1587 + if (pid < 0) {
1588 + trace2_exec_result(exec_id, -1);
1589 return -1;
1590 + }
1591 if (waitpid(pid, &status, 0) < 0)
1592 status = 255;
1593 + trace2_exec_result(exec_id, status);
1594 exit(status);
1595 }
1596 return -1;
compat/mingw.h
+1 -2
@@ -147,8 +147,7 @@ static inline int fcntl(int fd, int cmd, ...)
147 errno = EINVAL;
148 return -1;
149 }
150 -/* bash cannot reliably detect negative return codes as failure */
151 -#define exit(code) exit((code) & 0xff)
150 +
151 #define sigemptyset(x) (void)0
152 static inline int sigaddset(sigset_t *set, int signum)
153 { return 0; }
config.c
+2
@@ -2655,6 +2655,8 @@ int git_config_set_gently(const char *key, const char *value)
2655 void git_config_set(const char *key, const char *value)
2656 {
2657 git_config_set_multivar(key, value, NULL, 0);
2658 +
2659 + trace2_cmd_set_config(key, value);
2660 }
2661
2662 /*
exec-cmd.c
+2
@@ -209,6 +209,8 @@ static int git_get_exec_path(struct strbuf *buf, const char *argv0)
209 return -1;
210 }
211
212 + trace2_cmd_path(buf->buf);
213 +
214 return 0;
215 }
216
git-compat-util.h
+7
@@ -1259,6 +1259,13 @@ static inline int is_missing_file_error(int errno_)
1259
1260 extern int cmd_main(int, const char **);
1261
1262 +/*
1263 + * Intercept all calls to exit() and route them to trace2 to
1264 + * optionally emit a message before calling the real exit().
1265 + */
1266 +int trace2_cmd_exit_fl(const char *file, int line, int code);
1267 +#define exit(code) exit(trace2_cmd_exit_fl(__FILE__, __LINE__, (code)))
1268 +
1269 /*
1270 * You can mark a stack variable with UNLEAK(var) to avoid it being
1271 * reported as a leak by tools like LSAN or valgrind. The argument
git.c
+65
@@ -147,16 +147,20 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
147 git_set_exec_path(cmd + 1);
148 else {
149 puts(git_exec_path());
150 + trace2_cmd_name("_query_");
151 exit(0);
152 }
153 } else if (!strcmp(cmd, "--html-path")) {
154 puts(system_path(GIT_HTML_PATH));
155 + trace2_cmd_name("_query_");
156 exit(0);
157 } else if (!strcmp(cmd, "--man-path")) {
158 puts(system_path(GIT_MAN_PATH));
159 + trace2_cmd_name("_query_");
160 exit(0);
161 } else if (!strcmp(cmd, "--info-path")) {
162 puts(system_path(GIT_INFO_PATH));
163 + trace2_cmd_name("_query_");
164 exit(0);
165 } else if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) {
166 use_pager = 1;
@@ -285,6 +289,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
289 (*argv)++;
290 (*argc)--;
291 } else if (skip_prefix(cmd, "--list-cmds=", &cmd)) {
292 + trace2_cmd_name("_query_");
293 if (!strcmp(cmd, "parseopt")) {
294 struct string_list list = STRING_LIST_INIT_DUP;
295 int i;
@@ -332,9 +337,14 @@ static int handle_alias(int *argcp, const char ***argv)
337 commit_pager_choice();
338
339 child.use_shell = 1;
340 + child.trace2_child_class = "shell_alias";
341 argv_array_push(&child.args, alias_string + 1);
342 argv_array_pushv(&child.args, (*argv) + 1);
343
344 + trace2_cmd_alias(alias_command, child.args.argv);
345 + trace2_cmd_list_config();
346 + trace2_cmd_name("_run_shell_alias_");
347 +
348 ret = run_command(&child);
349 if (ret >= 0) /* normal exit */
350 exit(ret);
@@ -369,6 +379,9 @@ static int handle_alias(int *argcp, const char ***argv)
379 /* insert after command name */
380 memcpy(new_argv + count, *argv + 1, sizeof(char *) * *argcp);
381
382 + trace2_cmd_alias(alias_command, new_argv);
383 + trace2_cmd_list_config();
384 +
385 *argv = new_argv;
386 *argcp += count - 1;
387
@@ -417,6 +430,8 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
430 setup_work_tree();
431
432 trace_argv_printf(argv, "trace: built-in: git");
433 + trace2_cmd_name(p->cmd);
434 + trace2_cmd_list_config();
435
436 validate_cache_entries(the_repository->index);
437 status = p->fn(argc, argv, prefix);
@@ -666,7 +681,14 @@ static void execv_dashed_external(const char **argv)
681 cmd.clean_on_exit = 1;
682 cmd.wait_after_clean = 1;
683 cmd.silent_exec_failure = 1;
684 + cmd.trace2_child_class = "dashed";
685
686 + trace2_cmd_name("_run_dashed_");
687 +
688 + /*
689 + * The code in run_command() logs trace2 child_start/child_exit
690 + * events, so we do not need to report exec/exec_result events here.
691 + */
692 trace_argv_printf(cmd.args.argv, "trace: exec:");
693
694 /*
@@ -676,6 +698,12 @@ static void execv_dashed_external(const char **argv)
698 * the program.
699 */
700 status = run_command(&cmd);
701 +
702 + /*
703 + * If the child process ran and we are now going to exit, emit a
704 + * generic string as our trace2 command verb to indicate that we
705 + * launched a dashed command.
706 + */
707 if (status >= 0)
708 exit(status);
709 else if (errno != ENOENT)
@@ -701,6 +729,43 @@ static int run_argv(int *argcp, const char ***argv)
729 if (!done_alias)
730 handle_builtin(*argcp, *argv);
731
732 +#if 0 // TODO In GFW, need to amend a7924b655e940b06cb547c235d6bed9767929673 to include trace2_ and _tr2 lines.
733 + else if (get_builtin(**argv)) {
734 + struct argv_array args = ARGV_ARRAY_INIT;
735 + int i;
736 +
737 + /*
738 + * The current process is committed to launching a
739 + * child process to run the command named in (**argv)
740 + * and exiting. Log a generic string as the trace2
741 + * command verb to indicate this. Note that the child
742 + * process will log the actual verb when it runs.
743 + */
744 + trace2_cmd_name("_run_git_alias_");
745 +
746 + if (get_super_prefix())
747 + die("%s doesn't support --super-prefix", **argv);
748 +
749 + commit_pager_choice();
750 +
751 + argv_array_push(&args, "git");
752 + for (i = 0; i < *argcp; i++)
753 + argv_array_push(&args, (*argv)[i]);
754 +
755 + trace_argv_printf(args.argv, "trace: exec:");
756 +
757 + /*
758 + * if we fail because the command is not found, it is
759 + * OK to return. Otherwise, we just pass along the status code.
760 + */
761 + i = run_command_v_opt_tr2(args.argv, RUN_SILENT_EXEC_FAILURE |
762 + RUN_CLEAN_ON_EXIT, "git_alias");
763 + if (i >= 0 || errno != ENOENT)
764 + exit(i);
765 + die("could not execute builtin %s", **argv);
766 + }
767 +#endif // a7924b655e940b06cb547c235d6bed9767929673
768 +
769 /* .. then try the external ones */
770 execv_dashed_external(*argv);
771
remote-curl.c
+7
@@ -1385,6 +1385,13 @@ int cmd_main(int argc, const char **argv)
1385 string_list_init(&options.deepen_not, 1);
1386 string_list_init(&options.push_options, 1);
1387
1388 + /*
1389 + * Just report "remote-curl" here (folding all the various aliases
1390 + * ("git-remote-http", "git-remote-https", and etc.) here since they
1391 + * are all just copies of the same actual executable.
1392 + */
1393 + trace2_cmd_name("remote-curl");
1394 +
1395 remote = remote_get(argv[1]);
1396
1397 if (argc > 2) {
repository.c
+2
@@ -126,6 +126,8 @@ out:
126 void repo_set_worktree(struct repository *repo, const char *path)
127 {
128 repo->worktree = real_pathdup(path, 1);
129 +
130 + trace2_def_repo(repo);
131 }
132
133 static int read_and_verify_repository_format(struct repository_format *format,
repository.h
+3
@@ -92,6 +92,9 @@ struct repository {
92 /* Repository's current hash algorithm, as serialized on disk. */
93 const struct git_hash_algo *hash_algo;
94
95 + /* A unique-id for tracing purposes. */
96 + int trace2_repo_id;
97 +
98 /* Configurations */
99
100 /* Indicate if a repository has a different 'commondir' from 'gitdir' */
run-command.c
+58 -1
@@ -219,9 +219,29 @@ static int exists_in_PATH(const char *file)
219
220 int sane_execvp(const char *file, char * const argv[])
221 {
222 +#ifndef GIT_WINDOWS_NATIVE
223 + /*
224 + * execvp() doesn't return, so we all we can do is tell trace2
225 + * what we are about to do and let it leave a hint in the log
226 + * (unless of course the execvp() fails).
227 + *
228 + * we skip this for Windows because the compat layer already
229 + * has to emulate the execvp() call anyway.
230 + */
231 + int exec_id = trace2_exec(file, (const char **)argv);
232 +#endif
233 +
234 if (!execvp(file, argv))
235 return 0; /* cannot happen ;-) */
236
237 +#ifndef GIT_WINDOWS_NATIVE
238 + {
239 + int ec = errno;
240 + trace2_exec_result(exec_id, ec);
241 + errno = ec;
242 + }
243 +#endif
244 +
245 /*
246 * When a command can't be found because one of the directories
247 * listed in $PATH is unsearchable, execvp reports EACCES, but
@@ -712,6 +732,7 @@ fail_pipe:
732 cmd->err = fderr[0];
733 }
734
735 + trace2_child_start(cmd);
736 trace_run_command(cmd);
737
738 fflush(NULL);
@@ -926,6 +947,8 @@ end_of_spawn:
947 #endif
948
949 if (cmd->pid < 0) {
950 + trace2_child_exit(cmd, -1);
951 +
952 if (need_in)
953 close_pair(fdin);
954 else if (cmd->in)
@@ -964,13 +987,16 @@ end_of_spawn:
987 int finish_command(struct child_process *cmd)
988 {
989 int ret = wait_or_whine(cmd->pid, cmd->argv[0], 0);
990 + trace2_child_exit(cmd, ret);
991 child_process_clear(cmd);
992 return ret;
993 }
994
995 int finish_command_in_signal(struct child_process *cmd)
996 {
973 - return wait_or_whine(cmd->pid, cmd->argv[0], 1);
997 + int ret = wait_or_whine(cmd->pid, cmd->argv[0], 1);
998 + trace2_child_exit(cmd, ret);
999 + return ret;
1000 }
1001
1002
@@ -992,7 +1018,18 @@ int run_command_v_opt(const char **argv, int opt)
1018 return run_command_v_opt_cd_env(argv, opt, NULL, NULL);
1019 }
1020
1021 +int run_command_v_opt_tr2(const char **argv, int opt, const char *tr2_class)
1022 +{
1023 + return run_command_v_opt_cd_env_tr2(argv, opt, NULL, NULL, tr2_class);
1024 +}
1025 +
1026 int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env)
1027 +{
1028 + return run_command_v_opt_cd_env_tr2(argv, opt, dir, env, NULL);
1029 +}
1030 +
1031 +int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,
1032 + const char *const *env, const char *tr2_class)
1033 {
1034 struct child_process cmd = CHILD_PROCESS_INIT;
1035 cmd.argv = argv;
@@ -1004,6 +1041,7 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const
1041 cmd.clean_on_exit = opt & RUN_CLEAN_ON_EXIT ? 1 : 0;
1042 cmd.dir = dir;
1043 cmd.env = env;
1044 + cmd.trace2_child_class = tr2_class;
1045 return run_command(&cmd);
1046 }
1047
@@ -1319,6 +1357,7 @@ int run_hook_ve(const char *const *env, const char *name, va_list args)
1357 hook.env = env;
1358 hook.no_stdin = 1;
1359 hook.stdout_to_stderr = 1;
1360 + hook.trace2_hook_name = name;
1361
1362 return run_command(&hook);
1363 }
@@ -1807,3 +1846,21 @@ int run_processes_parallel(int n,
1846 pp_cleanup(&pp);
1847 return 0;
1848 }
1849 +
1850 +int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task,
1851 + start_failure_fn start_failure,
1852 + task_finished_fn task_finished, void *pp_cb,
1853 + const char *tr2_category, const char *tr2_label)
1854 +{
1855 + int result;
1856 +
1857 + trace2_region_enter_printf(tr2_category, tr2_label, NULL, "max:%d",
1858 + ((n < 1) ? online_cpus() : n));
1859 +
1860 + result = run_processes_parallel(n, get_next_task, start_failure,
1861 + task_finished, pp_cb);
1862 +
1863 + trace2_region_leave(tr2_category, tr2_label, NULL);
1864 +
1865 + return result;
1866 +}
run-command.h
+12 -1
@@ -10,6 +10,12 @@ struct child_process {
10 struct argv_array args;
11 struct argv_array env_array;
12 pid_t pid;
13 +
14 + int trace2_child_id;
15 + uint64_t trace2_child_us_start;
16 + const char *trace2_child_class;
17 + const char *trace2_hook_name;
18 +
19 /*
20 * Using .in, .out, .err:
21 * - Specify 0 for no redirections (child inherits stdin, stdout,
@@ -73,12 +79,14 @@ extern int run_hook_ve(const char *const *env, const char *name, va_list args);
79 #define RUN_USING_SHELL 16
80 #define RUN_CLEAN_ON_EXIT 32
81 int run_command_v_opt(const char **argv, int opt);
76 -
82 +int run_command_v_opt_tr2(const char **argv, int opt, const char *tr2_class);
83 /*
84 * env (the environment) is to be formatted like environ: "VAR=VALUE".
85 * To unset an environment variable use just "VAR".
86 */
87 int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env);
88 +int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,
89 + const char *const *env, const char *tr2_class);
90
91 /**
92 * Execute the given command, sending "in" to its stdin, and capturing its
@@ -220,5 +228,8 @@ int run_processes_parallel(int n,
228 start_failure_fn,
229 task_finished_fn,
230 void *pp_cb);
231 +int run_processes_parallel_tr2(int n, get_next_task_fn, start_failure_fn,
232 + task_finished_fn, void *pp_cb,
233 + const char *tr2_category, const char *tr2_label);
234
235 #endif
sh-i18n--envsubst.c
+3
@@ -14,6 +14,7 @@
14 */
15
16 #include "git-compat-util.h"
17 +#include "trace2.h"
18
19 /* Substitution of environment variables in shell format strings.
20 Copyright (C) 2003-2007 Free Software Foundation, Inc.
@@ -67,6 +68,8 @@ cmd_main (int argc, const char *argv[])
68 /* Default values for command line options. */
69 /* unsigned short int show_variables = 0; */
70
71 + trace2_cmd_name("sh-i18n--envsubst");
72 +
73 switch (argc)
74 {
75 case 1:
submodule.c
+6 -5
@@ -1609,11 +1609,12 @@ int fetch_populated_submodules(struct repository *r,
1609
1610 calculate_changed_submodule_paths(r, &spf.changed_submodule_names);
1611 string_list_sort(&spf.changed_submodule_names);
1612 - run_processes_parallel(max_parallel_jobs,
1613 - get_next_submodule,
1614 - fetch_start_failure,
1615 - fetch_finish,
1616 - &spf);
1612 + run_processes_parallel_tr2(max_parallel_jobs,
1613 + get_next_submodule,
1614 + fetch_start_failure,
1615 + fetch_finish,
1616 + &spf,
1617 + "submodule", "parallel/fetch");
1618
1619 argv_array_clear(&spf.args);
1620 out:
t/helper/test-parse-options.c
+3
@@ -2,6 +2,7 @@
2 #include "cache.h"
3 #include "parse-options.h"
4 #include "string-list.h"
5 +#include "trace2.h"
6
7 static int boolean = 0;
8 static int integer = 0;
@@ -153,6 +154,8 @@ int cmd__parse_options(int argc, const char **argv)
154 int i;
155 int ret = 0;
156
157 + trace2_cmd_name("_parse_");
158 +
159 argc = parse_options(argc, (const char **)argv, prefix, options, usage, 0);
160
161 if (length_cb.called) {
t/helper/test-tool.c
+3
@@ -1,5 +1,6 @@
1 #include "git-compat-util.h"
2 #include "test-tool.h"
3 +#include "trace2.h"
4
5 struct test_cmd {
6 const char *name;
@@ -82,6 +83,8 @@ int cmd_main(int argc, const char **argv)
83 if (!strcmp(cmds[i].name, argv[1])) {
84 argv++;
85 argc--;
86 + trace2_cmd_name(cmds[i].name);
87 + trace2_cmd_list_config();
88 return cmds[i].fn(argc, argv);
89 }
90 }
t/t0001-init.sh
+1
@@ -93,6 +93,7 @@ test_expect_success 'No extra GIT_* on alias scripts' '
93 sed -n \
94 -e "/^GIT_PREFIX=/d" \
95 -e "/^GIT_TEXTDOMAINDIR=/d" \
96 + -e "/^GIT_TR2_PARENT/d" \
97 -e "/^GIT_/s/=.*//p" |
98 sort
99 EOF
trace2.c new
+761
@@ -0,0 +1,761 @@
1 +#include "cache.h"
2 +#include "config.h"
3 +#include "json-writer.h"
4 +#include "quote.h"
5 +#include "run-command.h"
6 +#include "sigchain.h"
7 +#include "thread-utils.h"
8 +#include "version.h"
9 +#include "trace2/tr2_cfg.h"
10 +#include "trace2/tr2_cmd_name.h"
11 +#include "trace2/tr2_dst.h"
12 +#include "trace2/tr2_sid.h"
13 +#include "trace2/tr2_tgt.h"
14 +#include "trace2/tr2_tls.h"
15 +
16 +static int trace2_enabled;
17 +
18 +static int tr2_next_child_id; /* modify under lock */
19 +static int tr2_next_exec_id; /* modify under lock */
20 +static int tr2_next_repo_id = 1; /* modify under lock. zero is reserved */
21 +
22 +/*
23 + * A table of the builtin TRACE2 targets. Each of these may be independently
24 + * enabled or disabled. Each TRACE2 API method will try to write an event to
25 + * *each* of the enabled targets.
26 + */
27 +/* clang-format off */
28 +static struct tr2_tgt *tr2_tgt_builtins[] =
29 +{
30 + &tr2_tgt_normal,
31 + &tr2_tgt_perf,
32 + &tr2_tgt_event,
33 + NULL
34 +};
35 +/* clang-format on */
36 +
37 +/* clang-format off */
38 +#define for_each_builtin(j, tgt_j) \
39 + for (j = 0, tgt_j = tr2_tgt_builtins[j]; \
40 + tgt_j; \
41 + j++, tgt_j = tr2_tgt_builtins[j])
42 +/* clang-format on */
43 +
44 +/* clang-format off */
45 +#define for_each_wanted_builtin(j, tgt_j) \
46 + for_each_builtin(j, tgt_j) \
47 + if (tr2_dst_trace_want(tgt_j->pdst))
48 +/* clang-format on */
49 +
50 +/*
51 + * Force (rather than lazily) initialize any of the requested
52 + * builtin TRACE2 targets at startup (and before we've seen an
53 + * actual TRACE2 event call) so we can see if we need to setup
54 + * the TR2 and TLS machinery.
55 + *
56 + * Return the number of builtin targets enabled.
57 + */
58 +static int tr2_tgt_want_builtins(void)
59 +{
60 + struct tr2_tgt *tgt_j;
61 + int j;
62 + int sum = 0;
63 +
64 + for_each_builtin (j, tgt_j)
65 + if (tgt_j->pfn_init())
66 + sum++;
67 +
68 + return sum;
69 +}
70 +
71 +/*
72 + * Properly terminate each builtin target. Give each target
73 + * a chance to write a summary event and/or flush if necessary
74 + * and then close the fd.
75 + */
76 +static void tr2_tgt_disable_builtins(void)
77 +{
78 + struct tr2_tgt *tgt_j;
79 + int j;
80 +
81 + for_each_builtin (j, tgt_j)
82 + tgt_j->pfn_term();
83 +}
84 +
85 +static int tr2main_exit_code;
86 +
87 +/*
88 + * Our atexit routine should run after everything has finished.
89 + *
90 + * Note that events generated here might not actually appear if
91 + * we are writing to fd 1 or 2 and our atexit routine runs after
92 + * the pager's atexit routine (since it closes them to shutdown
93 + * the pipes).
94 + */
95 +static void tr2main_atexit_handler(void)
96 +{
97 + struct tr2_tgt *tgt_j;
98 + int j;
99 + uint64_t us_now;
100 + uint64_t us_elapsed_absolute;
101 +
102 + us_now = getnanotime() / 1000;
103 + us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
104 +
105 + /*
106 + * Clear any unbalanced regions so that our atexit message
107 + * does not appear nested. This improves the appearance of
108 + * the trace output if someone calls die(), for example.
109 + */
110 + tr2tls_pop_unwind_self();
111 +
112 + for_each_wanted_builtin (j, tgt_j)
113 + if (tgt_j->pfn_atexit)
114 + tgt_j->pfn_atexit(us_elapsed_absolute,
115 + tr2main_exit_code);
116 +
117 + tr2_tgt_disable_builtins();
118 +
119 + tr2tls_release();
120 + tr2_sid_release();
121 + tr2_cmd_name_release();
122 + tr2_cfg_free_patterns();
123 +
124 + trace2_enabled = 0;
125 +}
126 +
127 +static void tr2main_signal_handler(int signo)
128 +{
129 + struct tr2_tgt *tgt_j;
130 + int j;
131 + uint64_t us_now;
132 + uint64_t us_elapsed_absolute;
133 +
134 + us_now = getnanotime() / 1000;
135 + us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
136 +
137 + for_each_wanted_builtin (j, tgt_j)
138 + if (tgt_j->pfn_signal)
139 + tgt_j->pfn_signal(us_elapsed_absolute, signo);
140 +
141 + sigchain_pop(signo);
142 + raise(signo);
143 +}
144 +
145 +void trace2_initialize_fl(const char *file, int line)
146 +{
147 + struct tr2_tgt *tgt_j;
148 + int j;
149 +
150 + if (trace2_enabled)
151 + return;
152 +
153 + if (!tr2_tgt_want_builtins())
154 + return;
155 + trace2_enabled = 1;
156 +
157 + tr2_sid_get();
158 +
159 + atexit(tr2main_atexit_handler);
160 + sigchain_push(SIGPIPE, tr2main_signal_handler);
161 + tr2tls_init();
162 +
163 + /*
164 + * Emit 'version' message on each active builtin target.
165 + */
166 + for_each_wanted_builtin (j, tgt_j)
167 + if (tgt_j->pfn_version_fl)
168 + tgt_j->pfn_version_fl(file, line);
169 +}
170 +
171 +int trace2_is_enabled(void)
172 +{
173 + return trace2_enabled;
174 +}
175 +
176 +void trace2_cmd_start_fl(const char *file, int line, const char **argv)
177 +{
178 + struct tr2_tgt *tgt_j;
179 + int j;
180 +
181 + if (!trace2_enabled)
182 + return;
183 +
184 + for_each_wanted_builtin (j, tgt_j)
185 + if (tgt_j->pfn_start_fl)
186 + tgt_j->pfn_start_fl(file, line, argv);
187 +}
188 +
189 +int trace2_cmd_exit_fl(const char *file, int line, int code)
190 +{
191 + struct tr2_tgt *tgt_j;
192 + int j;
193 + uint64_t us_now;
194 + uint64_t us_elapsed_absolute;
195 +
196 + code &= 0xff;
197 +
198 + if (!trace2_enabled)
199 + return code;
200 +
201 + tr2main_exit_code = code;
202 +
203 + us_now = getnanotime() / 1000;
204 + us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
205 +
206 + for_each_wanted_builtin (j, tgt_j)
207 + if (tgt_j->pfn_exit_fl)
208 + tgt_j->pfn_exit_fl(file, line, us_elapsed_absolute,
209 + code);
210 +
211 + return code;
212 +}
213 +
214 +void trace2_cmd_error_va_fl(const char *file, int line, const char *fmt,
215 + va_list ap)
216 +{
217 + struct tr2_tgt *tgt_j;
218 + int j;
219 +
220 + if (!trace2_enabled)
221 + return;
222 +
223 + /*
224 + * We expect each target function to treat 'ap' as constant
225 + * and use va_copy (because an 'ap' can only be walked once).
226 + */
227 + for_each_wanted_builtin (j, tgt_j)
228 + if (tgt_j->pfn_error_va_fl)
229 + tgt_j->pfn_error_va_fl(file, line, fmt, ap);
230 +}
231 +
232 +void trace2_cmd_path_fl(const char *file, int line, const char *pathname)
233 +{
234 + struct tr2_tgt *tgt_j;
235 + int j;
236 +
237 + if (!trace2_enabled)
238 + return;
239 +
240 + for_each_wanted_builtin (j, tgt_j)
241 + if (tgt_j->pfn_command_path_fl)
242 + tgt_j->pfn_command_path_fl(file, line, pathname);
243 +}
244 +
245 +void trace2_cmd_name_fl(const char *file, int line, const char *name)
246 +{
247 + struct tr2_tgt *tgt_j;
248 + const char *hierarchy;
249 + int j;
250 +
251 + if (!trace2_enabled)
252 + return;
253 +
254 + tr2_cmd_name_append_hierarchy(name);
255 + hierarchy = tr2_cmd_name_get_hierarchy();
256 +
257 + for_each_wanted_builtin (j, tgt_j)
258 + if (tgt_j->pfn_command_name_fl)
259 + tgt_j->pfn_command_name_fl(file, line, name, hierarchy);
260 +}
261 +
262 +void trace2_cmd_mode_fl(const char *file, int line, const char *mode)
263 +{
264 + struct tr2_tgt *tgt_j;
265 + int j;
266 +
267 + if (!trace2_enabled)
268 + return;
269 +
270 + for_each_wanted_builtin (j, tgt_j)
271 + if (tgt_j->pfn_command_mode_fl)
272 + tgt_j->pfn_command_mode_fl(file, line, mode);
273 +}
274 +
275 +void trace2_cmd_alias_fl(const char *file, int line, const char *alias,
276 + const char **argv)
277 +{
278 + struct tr2_tgt *tgt_j;
279 + int j;
280 +
281 + if (!trace2_enabled)
282 + return;
283 +
284 + for_each_wanted_builtin (j, tgt_j)
285 + if (tgt_j->pfn_alias_fl)
286 + tgt_j->pfn_alias_fl(file, line, alias, argv);
287 +}
288 +
289 +void trace2_cmd_list_config_fl(const char *file, int line)
290 +{
291 + if (!trace2_enabled)
292 + return;
293 +
294 + tr2_cfg_list_config_fl(file, line);
295 +}
296 +
297 +void trace2_cmd_set_config_fl(const char *file, int line, const char *key,
298 + const char *value)
299 +{
300 + if (!trace2_enabled)
301 + return;
302 +
303 + tr2_cfg_set_fl(file, line, key, value);
304 +}
305 +
306 +void trace2_child_start_fl(const char *file, int line,
307 + struct child_process *cmd)
308 +{
309 + struct tr2_tgt *tgt_j;
310 + int j;
311 + uint64_t us_now;
312 + uint64_t us_elapsed_absolute;
313 +
314 + if (!trace2_enabled)
315 + return;
316 +
317 + us_now = getnanotime() / 1000;
318 + us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
319 +
320 + cmd->trace2_child_id = tr2tls_locked_increment(&tr2_next_child_id);
321 + cmd->trace2_child_us_start = us_now;
322 +
323 + for_each_wanted_builtin (j, tgt_j)
324 + if (tgt_j->pfn_child_start_fl)
325 + tgt_j->pfn_child_start_fl(file, line,
326 + us_elapsed_absolute, cmd);
327 +}
328 +
329 +void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd,
330 + int child_exit_code)
331 +{
332 + struct tr2_tgt *tgt_j;
333 + int j;
334 + uint64_t us_now;
335 + uint64_t us_elapsed_absolute;
336 + uint64_t us_elapsed_child;
337 +
338 + if (!trace2_enabled)
339 + return;
340 +
341 + us_now = getnanotime() / 1000;
342 + us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
343 +
344 + if (cmd->trace2_child_us_start)
345 + us_elapsed_child = us_now - cmd->trace2_child_us_start;
346 + else
347 + us_elapsed_child = 0;
348 +
349 + for_each_wanted_builtin (j, tgt_j)
350 + if (tgt_j->pfn_child_exit_fl)
351 + tgt_j->pfn_child_exit_fl(file, line,
352 + us_elapsed_absolute,
353 + cmd->trace2_child_id, cmd->pid,
354 + child_exit_code,
355 + us_elapsed_child);
356 +}
357 +
358 +int trace2_exec_fl(const char *file, int line, const char *exe,
359 + const char **argv)
360 +{
361 + struct tr2_tgt *tgt_j;
362 + int j;
363 + int exec_id;
364 + uint64_t us_now;
365 + uint64_t us_elapsed_absolute;
366 +
367 + if (!trace2_enabled)
368 + return -1;
369 +
370 + us_now = getnanotime() / 1000;
371 + us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
372 +
373 + exec_id = tr2tls_locked_increment(&tr2_next_exec_id);
374 +
375 + for_each_wanted_builtin (j, tgt_j)
376 + if (tgt_j->pfn_exec_fl)
377 + tgt_j->pfn_exec_fl(file, line, us_elapsed_absolute,
378 + exec_id, exe, argv);
379 +
380 + return exec_id;
381 +}
382 +
383 +void trace2_exec_result_fl(const char *file, int line, int exec_id, int code)
384 +{
385 + struct tr2_tgt *tgt_j;
386 + int j;
387 + uint64_t us_now;
388 + uint64_t us_elapsed_absolute;
389 +
390 + if (!trace2_enabled)
391 + return;
392 +
393 + us_now = getnanotime() / 1000;
394 + us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
395 +
396 + for_each_wanted_builtin (j, tgt_j)
397 + if (tgt_j->pfn_exec_result_fl)
398 + tgt_j->pfn_exec_result_fl(
399 + file, line, us_elapsed_absolute, exec_id, code);
400 +}
401 +
402 +void trace2_thread_start_fl(const char *file, int line, const char *thread_name)
403 +{
404 + struct tr2_tgt *tgt_j;
405 + int j;
406 + uint64_t us_now;
407 + uint64_t us_elapsed_absolute;
408 +
409 + if (!trace2_enabled)
410 + return;
411 +
412 + if (tr2tls_is_main_thread()) {
413 + /*
414 + * We should only be called from the new thread's thread-proc,
415 + * so this is technically a bug. But in those cases where the
416 + * main thread also runs the thread-proc function (or when we
417 + * are built with threading disabled), we need to allow it.
418 + *
419 + * Convert this call to a region-enter so the nesting looks
420 + * correct.
421 + */
422 + trace2_region_enter_printf_fl(file, line, NULL, NULL, NULL,
423 + "thread-proc on main: %s",
424 + thread_name);
425 + return;
426 + }
427 +
428 + us_now = getnanotime() / 1000;
429 + us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
430 +
431 + tr2tls_create_self(thread_name);
432 +
433 + for_each_wanted_builtin (j, tgt_j)
434 + if (tgt_j->pfn_thread_start_fl)
435 + tgt_j->pfn_thread_start_fl(file, line,
436 + us_elapsed_absolute);
437 +}
438 +
439 +void trace2_thread_exit_fl(const char *file, int line)
440 +{
441 + struct tr2_tgt *tgt_j;
442 + int j;
443 + uint64_t us_now;
444 + uint64_t us_elapsed_absolute;
445 + uint64_t us_elapsed_thread;
446 +
447 + if (!trace2_enabled)
448 + return;
449 +
450 + if (tr2tls_is_main_thread()) {
451 + /*
452 + * We should only be called from the exiting thread's
453 + * thread-proc, so this is technically a bug. But in
454 + * those cases where the main thread also runs the
455 + * thread-proc function (or when we are built with
456 + * threading disabled), we need to allow it.
457 + *
458 + * Convert this call to a region-leave so the nesting
459 + * looks correct.
460 + */
461 + trace2_region_leave_printf_fl(file, line, NULL, NULL, NULL,
462 + "thread-proc on main");
463 + return;
464 + }
465 +
466 + us_now = getnanotime() / 1000;
467 + us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
468 +
469 + /*
470 + * Clear any unbalanced regions and then get the relative time
471 + * for the outer-most region (which we pushed when the thread
472 + * started). This gives us the run time of the thread.
473 + */
474 + tr2tls_pop_unwind_self();
475 + us_elapsed_thread = tr2tls_region_elasped_self(us_now);
476 +
477 + for_each_wanted_builtin (j, tgt_j)
478 + if (tgt_j->pfn_thread_exit_fl)
479 + tgt_j->pfn_thread_exit_fl(file, line,
480 + us_elapsed_absolute,
481 + us_elapsed_thread);
482 +
483 + tr2tls_unset_self();
484 +}
485 +
486 +void trace2_def_param_fl(const char *file, int line, const char *param,
487 + const char *value)
488 +{
489 + struct tr2_tgt *tgt_j;
490 + int j;
491 +
492 + if (!trace2_enabled)
493 + return;
494 +
495 + for_each_wanted_builtin (j, tgt_j)
496 + if (tgt_j->pfn_param_fl)
497 + tgt_j->pfn_param_fl(file, line, param, value);
498 +}
499 +
500 +void trace2_def_repo_fl(const char *file, int line, struct repository *repo)
501 +{
502 + struct tr2_tgt *tgt_j;
503 + int j;
504 +
505 + if (!trace2_enabled)
506 + return;
507 +
508 + if (repo->trace2_repo_id)
509 + return;
510 +
511 + repo->trace2_repo_id = tr2tls_locked_increment(&tr2_next_repo_id);
512 +
513 + for_each_wanted_builtin (j, tgt_j)
514 + if (tgt_j->pfn_repo_fl)
515 + tgt_j->pfn_repo_fl(file, line, repo);
516 +}
517 +
518 +void trace2_region_enter_printf_va_fl(const char *file, int line,
519 + const char *category, const char *label,
520 + const struct repository *repo,
521 + const char *fmt, va_list ap)
522 +{
523 + struct tr2_tgt *tgt_j;
524 + int j;
525 + uint64_t us_now;
526 + uint64_t us_elapsed_absolute;
527 +
528 + if (!trace2_enabled)
529 + return;
530 +
531 + us_now = getnanotime() / 1000;
532 + us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
533 +
534 + /*
535 + * Print the region-enter message at the current nesting
536 + * (indentation) level and then push a new level.
537 + *
538 + * We expect each target function to treat 'ap' as constant
539 + * and use va_copy.
540 + */
541 + for_each_wanted_builtin (j, tgt_j)
542 + if (tgt_j->pfn_region_enter_printf_va_fl)
543 + tgt_j->pfn_region_enter_printf_va_fl(
544 + file, line, us_elapsed_absolute, category,
545 + label, repo, fmt, ap);
546 +
547 + tr2tls_push_self(us_now);
548 +}
549 +
550 +void trace2_region_enter_fl(const char *file, int line, const char *category,
551 + const char *label, const struct repository *repo)
552 +{
553 + trace2_region_enter_printf_va_fl(file, line, category, label, repo,
554 + NULL, NULL);
555 +}
556 +
557 +void trace2_region_enter_printf_fl(const char *file, int line,
558 + const char *category, const char *label,
559 + const struct repository *repo,
560 + const char *fmt, ...)
561 +{
562 + va_list ap;
563 +
564 + va_start(ap, fmt);
565 + trace2_region_enter_printf_va_fl(file, line, category, label, repo, fmt,
566 + ap);
567 + va_end(ap);
568 +}
569 +
570 +#ifndef HAVE_VARIADIC_MACROS
571 +void trace2_region_enter_printf(const char *category, const char *label,
572 + const struct repository *repo, const char *fmt,
573 + ...)
574 +{
575 + va_list ap;
576 +
577 + va_start(ap, fmt);
578 + trace2_region_enter_printf_va_fl(NULL, 0, category, label, repo, fmt,
579 + ap);
580 + va_end(ap);
581 +}
582 +#endif
583 +
584 +void trace2_region_leave_printf_va_fl(const char *file, int line,
585 + const char *category, const char *label,
586 + const struct repository *repo,
587 + const char *fmt, va_list ap)
588 +{
589 + struct tr2_tgt *tgt_j;
590 + int j;
591 + uint64_t us_now;
592 + uint64_t us_elapsed_absolute;
593 + uint64_t us_elapsed_region;
594 +
595 + if (!trace2_enabled)
596 + return;
597 +
598 + us_now = getnanotime() / 1000;
599 + us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
600 +
601 + /*
602 + * Get the elapsed time in the current region before we
603 + * pop it off the stack. Pop the stack. And then print
604 + * the perf message at the new (shallower) level so that
605 + * it lines up with the corresponding push/enter.
606 + */
607 + us_elapsed_region = tr2tls_region_elasped_self(us_now);
608 +
609 + tr2tls_pop_self();
610 +
611 + /*
612 + * We expect each target function to treat 'ap' as constant
613 + * and use va_copy.
614 + */
615 + for_each_wanted_builtin (j, tgt_j)
616 + if (tgt_j->pfn_region_leave_printf_va_fl)
617 + tgt_j->pfn_region_leave_printf_va_fl(
618 + file, line, us_elapsed_absolute,
619 + us_elapsed_region, category, label, repo, fmt,
620 + ap);
621 +}
622 +
623 +void trace2_region_leave_fl(const char *file, int line, const char *category,
624 + const char *label, const struct repository *repo)
625 +{
626 + trace2_region_leave_printf_va_fl(file, line, category, label, repo,
627 + NULL, NULL);
628 +}
629 +
630 +void trace2_region_leave_printf_fl(const char *file, int line,
631 + const char *category, const char *label,
632 + const struct repository *repo,
633 + const char *fmt, ...)
634 +{
635 + va_list ap;
636 +
637 + va_start(ap, fmt);
638 + trace2_region_leave_printf_va_fl(file, line, category, label, repo, fmt,
639 + ap);
640 + va_end(ap);
641 +}
642 +
643 +#ifndef HAVE_VARIADIC_MACROS
644 +void trace2_region_leave_printf(const char *category, const char *label,
645 + const struct repository *repo, const char *fmt,
646 + ...)
647 +{
648 + va_list ap;
649 +
650 + va_start(ap, fmt);
651 + trace2_region_leave_printf_va_fl(NULL, 0, category, label, repo, fmt,
652 + ap);
653 + va_end(ap);
654 +}
655 +#endif
656 +
657 +void trace2_data_string_fl(const char *file, int line, const char *category,
658 + const struct repository *repo, const char *key,
659 + const char *value)
660 +{
661 + struct tr2_tgt *tgt_j;
662 + int j;
663 + uint64_t us_now;
664 + uint64_t us_elapsed_absolute;
665 + uint64_t us_elapsed_region;
666 +
667 + if (!trace2_enabled)
668 + return;
669 +
670 + us_now = getnanotime() / 1000;
671 + us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
672 + us_elapsed_region = tr2tls_region_elasped_self(us_now);
673 +
674 + for_each_wanted_builtin (j, tgt_j)
675 + if (tgt_j->pfn_data_fl)
676 + tgt_j->pfn_data_fl(file, line, us_elapsed_absolute,
677 + us_elapsed_region, category, repo,
678 + key, value);
679 +}
680 +
681 +void trace2_data_intmax_fl(const char *file, int line, const char *category,
682 + const struct repository *repo, const char *key,
683 + intmax_t value)
684 +{
685 + struct strbuf buf_string = STRBUF_INIT;
686 +
687 + if (!trace2_enabled)
688 + return;
689 +
690 + strbuf_addf(&buf_string, "%" PRIdMAX, value);
691 + trace2_data_string_fl(file, line, category, repo, key, buf_string.buf);
692 + strbuf_release(&buf_string);
693 +}
694 +
695 +void trace2_data_json_fl(const char *file, int line, const char *category,
696 + const struct repository *repo, const char *key,
697 + const struct json_writer *value)
698 +{
699 + struct tr2_tgt *tgt_j;
700 + int j;
701 + uint64_t us_now;
702 + uint64_t us_elapsed_absolute;
703 + uint64_t us_elapsed_region;
704 +
705 + if (!trace2_enabled)
706 + return;
707 +
708 + us_now = getnanotime() / 1000;
709 + us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
710 + us_elapsed_region = tr2tls_region_elasped_self(us_now);
711 +
712 + for_each_wanted_builtin (j, tgt_j)
713 + if (tgt_j->pfn_data_fl)
714 + tgt_j->pfn_data_json_fl(file, line, us_elapsed_absolute,
715 + us_elapsed_region, category,
716 + repo, key, value);
717 +}
718 +
719 +void trace2_printf_va_fl(const char *file, int line, const char *fmt,
720 + va_list ap)
721 +{
722 + struct tr2_tgt *tgt_j;
723 + int j;
724 + uint64_t us_now;
725 + uint64_t us_elapsed_absolute;
726 +
727 + if (!trace2_enabled)
728 + return;
729 +
730 + us_now = getnanotime() / 1000;
731 + us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
732 +
733 + /*
734 + * We expect each target function to treat 'ap' as constant
735 + * and use va_copy.
736 + */
737 + for_each_wanted_builtin (j, tgt_j)
738 + if (tgt_j->pfn_printf_va_fl)
739 + tgt_j->pfn_printf_va_fl(file, line, us_elapsed_absolute,
740 + fmt, ap);
741 +}
742 +
743 +void trace2_printf_fl(const char *file, int line, const char *fmt, ...)
744 +{
745 + va_list ap;
746 +
747 + va_start(ap, fmt);
748 + trace2_printf_va_fl(file, line, fmt, ap);
749 + va_end(ap);
750 +}
751 +
752 +#ifndef HAVE_VARIADIC_MACROS
753 +void trace2_printf(const char *fmt, ...)
754 +{
755 + va_list ap;
756 +
757 + va_start(ap, fmt);
758 + trace2_printf_va_fl(NULL, 0, fmt, ap);
759 + va_end(ap);
760 +}
761 +#endif
trace2.h new
+371
@@ -0,0 +1,371 @@
1 +#ifndef TRACE2_H
2 +#define TRACE2_H
3 +
4 +struct child_process;
5 +struct repository;
6 +struct json_writer;
7 +
8 +/*
9 + * The public TRACE2 routines are grouped into the following groups:
10 + *
11 + * [] trace2_initialize -- initialization.
12 + * [] trace2_cmd_* -- emit command/control messages.
13 + * [] trace2_child* -- emit child start/stop messages.
14 + * [] trace2_exec* -- emit exec start/stop messages.
15 + * [] trace2_thread* -- emit thread start/stop messages.
16 + * [] trace2_def* -- emit definition/parameter mesasges.
17 + * [] trace2_region* -- emit region nesting messages.
18 + * [] trace2_data* -- emit region/thread/repo data messages.
19 + * [] trace2_printf* -- legacy trace[1] messages.
20 + */
21 +
22 +/*
23 + * Initialize TRACE2 tracing facility if any of the builtin TRACE2
24 + * targets are enabled in the environment. Emits a 'version' event.
25 + *
26 + * Cleanup/Termination is handled automatically by a registered
27 + * atexit() routine.
28 + */
29 +void trace2_initialize_fl(const char *file, int line);
30 +
31 +#define trace2_initialize() trace2_initialize_fl(__FILE__, __LINE__)
32 +
33 +/*
34 + * Return true if trace2 is enabled.
35 + */
36 +int trace2_is_enabled(void);
37 +
38 +/*
39 + * Emit a 'start' event with the original (unmodified) argv.
40 + */
41 +void trace2_cmd_start_fl(const char *file, int line, const char **argv);
42 +
43 +#define trace2_cmd_start(argv) trace2_cmd_start_fl(__FILE__, __LINE__, (argv))
44 +
45 +/*
46 + * Emit an 'exit' event.
47 + *
48 + * Write the exit-code that will be passed to exit() or returned
49 + * from main().
50 + *
51 + * Use this prior to actually calling exit().
52 + * See "#define exit()" in git-compat-util.h
53 + */
54 +int trace2_cmd_exit_fl(const char *file, int line, int code);
55 +
56 +#define trace2_cmd_exit(code) (trace2_cmd_exit_fl(__FILE__, __LINE__, (code)))
57 +
58 +/*
59 + * Emit an 'error' event.
60 + *
61 + * Write an error message to the TRACE2 targets.
62 + */
63 +void trace2_cmd_error_va_fl(const char *file, int line, const char *fmt,
64 + va_list ap);
65 +
66 +#define trace2_cmd_error_va(fmt, ap) \
67 + trace2_cmd_error_va_fl(__FILE__, __LINE__, (fmt), (ap))
68 +
69 +/*
70 + * Emit a 'pathname' event with the canonical pathname of the current process
71 + * This gives post-processors a simple field to identify the command without
72 + * having to parse the argv. For example, to distinguish invocations from
73 + * installed versus debug executables.
74 + */
75 +void trace2_cmd_path_fl(const char *file, int line, const char *pathname);
76 +
77 +#define trace2_cmd_path(p) trace2_cmd_path_fl(__FILE__, __LINE__, (p))
78 +
79 +/*
80 + * Emit a 'cmd_name' event with the canonical name of the command.
81 + * This gives post-processors a simple field to identify the command
82 + * without having to parse the argv.
83 + */
84 +void trace2_cmd_name_fl(const char *file, int line, const char *name);
85 +
86 +#define trace2_cmd_name(v) trace2_cmd_name_fl(__FILE__, __LINE__, (v))
87 +
88 +/*
89 + * Emit a 'cmd_mode' event to further describe the command being run.
90 + * For example, "checkout" can checkout a single file or can checkout a
91 + * different branch. This gives post-processors a simple field to compare
92 + * equivalent commands without having to parse the argv.
93 + */
94 +void trace2_cmd_mode_fl(const char *file, int line, const char *mode);
95 +
96 +#define trace2_cmd_mode(sv) trace2_cmd_mode_fl(__FILE__, __LINE__, (sv))
97 +
98 +/*
99 + * Emit an 'alias' expansion event.
100 + */
101 +void trace2_cmd_alias_fl(const char *file, int line, const char *alias,
102 + const char **argv);
103 +
104 +#define trace2_cmd_alias(alias, argv) \
105 + trace2_cmd_alias_fl(__FILE__, __LINE__, (alias), (argv))
106 +
107 +/*
108 + * Emit one or more 'def_param' events for "interesting" configuration
109 + * settings.
110 + *
111 + * The environment variable "GIT_TR2_CONFIG_PARAMS" can be set to a
112 + * list of patterns considered important. For example:
113 + *
114 + * GIT_TR2_CONFIG_PARAMS="core.*,remote.*.url"
115 + *
116 + * Note: this routine does a read-only iteration on the config data
117 + * (using read_early_config()), so it must not be called until enough
118 + * of the process environment has been established. This includes the
119 + * location of the git and worktree directories, expansion of any "-c"
120 + * and "-C" command line options, and etc.
121 + */
122 +void trace2_cmd_list_config_fl(const char *file, int line);
123 +
124 +#define trace2_cmd_list_config() trace2_cmd_list_config_fl(__FILE__, __LINE__)
125 +
126 +/*
127 + * Emit a "def_param" event for the given config key/value pair IF
128 + * we consider the key to be "interesting".
129 + *
130 + * Use this for new/updated config settings created/updated after
131 + * trace2_cmd_list_config() is called.
132 + */
133 +void trace2_cmd_set_config_fl(const char *file, int line, const char *key,
134 + const char *value);
135 +
136 +#define trace2_cmd_set_config(k, v) \
137 + trace2_cmd_set_config_fl(__FILE__, __LINE__, (k), (v))
138 +
139 +/*
140 + * Emit a 'child_start' event prior to spawning a child process.
141 + *
142 + * Before calling optionally set "cmd->trace2_child_class" to a string
143 + * describing the type of the child process. For example, "editor" or
144 + * "pager".
145 + */
146 +void trace2_child_start_fl(const char *file, int line,
147 + struct child_process *cmd);
148 +
149 +#define trace2_child_start(cmd) trace2_child_start_fl(__FILE__, __LINE__, (cmd))
150 +
151 +/*
152 + * Emit a 'child_exit' event after the child process completes.
153 + */
154 +void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd,
155 + int child_exit_code);
156 +
157 +#define trace2_child_exit(cmd, code) \
158 + trace2_child_exit_fl(__FILE__, __LINE__, (cmd), (code))
159 +
160 +/*
161 + * Emit an 'exec' event prior to calling one of exec(), execv(),
162 + * execvp(), and etc. On Unix-derived systems, this will be the
163 + * last event emitted for the current process, unless the exec
164 + * fails. On Windows, exec() behaves like 'child_start' and a
165 + * waitpid(), so additional events may be emitted.
166 + *
167 + * Returns the "exec_id".
168 + */
169 +int trace2_exec_fl(const char *file, int line, const char *exe,
170 + const char **argv);
171 +
172 +#define trace2_exec(exe, argv) trace2_exec_fl(__FILE__, __LINE__, (exe), (argv))
173 +
174 +/*
175 + * Emit an 'exec_result' when possible. On Unix-derived systems,
176 + * this should be called after exec() returns (which only happens
177 + * when there is an error starting the new process). On Windows,
178 + * this should be called after the waitpid().
179 + *
180 + * The "exec_id" should be the value returned from trace2_exec().
181 + */
182 +void trace2_exec_result_fl(const char *file, int line, int exec_id, int code);
183 +
184 +#define trace2_exec_result(id, code) \
185 + trace2_exec_result_fl(__FILE__, __LINE__, (id), (code))
186 +
187 +/*
188 + * Emit a 'thread_start' event. This must be called from inside the
189 + * thread-proc to set up the trace2 TLS data for the thread.
190 + *
191 + * Thread names should be descriptive, like "preload_index".
192 + * Thread names will be decorated with an instance number automatically.
193 + */
194 +void trace2_thread_start_fl(const char *file, int line,
195 + const char *thread_name);
196 +
197 +#define trace2_thread_start(thread_name) \
198 + trace2_thread_start_fl(__FILE__, __LINE__, (thread_name))
199 +
200 +/*
201 + * Emit a 'thread_exit' event. This must be called from inside the
202 + * thread-proc to report thread-specific data and cleanup TLS data
203 + * for the thread.
204 + */
205 +void trace2_thread_exit_fl(const char *file, int line);
206 +
207 +#define trace2_thread_exit() trace2_thread_exit_fl(__FILE__, __LINE__)
208 +
209 +/*
210 + * Emit a 'param' event.
211 + *
212 + * Write a "<param> = <value>" pair describing some aspect of the
213 + * run such as an important configuration setting or command line
214 + * option that significantly changes command behavior.
215 + */
216 +void trace2_def_param_fl(const char *file, int line, const char *param,
217 + const char *value);
218 +
219 +#define trace2_def_param(param, value) \
220 + trace2_def_param_fl(__FILE__, __LINE__, (param), (value))
221 +
222 +/*
223 + * Tell trace2 about a newly instantiated repo object and assign
224 + * a trace2-repo-id to be used in subsequent activity events.
225 + *
226 + * Emits a 'worktree' event for this repo instance.
227 + */
228 +void trace2_def_repo_fl(const char *file, int line, struct repository *repo);
229 +
230 +#define trace2_def_repo(repo) trace2_def_repo_fl(__FILE__, __LINE__, repo)
231 +
232 +/*
233 + * Emit a 'region_enter' event for <category>.<label> with optional
234 + * repo-id and printf message.
235 + *
236 + * Enter a new nesting level on the current thread and remember the
237 + * current time. This controls the indenting of all subsequent events
238 + * on this thread.
239 + */
240 +void trace2_region_enter_fl(const char *file, int line, const char *category,
241 + const char *label, const struct repository *repo);
242 +
243 +#define trace2_region_enter(category, label, repo) \
244 + trace2_region_enter_fl(__FILE__, __LINE__, (category), (label), (repo))
245 +
246 +void trace2_region_enter_printf_va_fl(const char *file, int line,
247 + const char *category, const char *label,
248 + const struct repository *repo,
249 + const char *fmt, va_list ap);
250 +
251 +#define trace2_region_enter_printf_va(category, label, repo, fmt, ap) \
252 + trace2_region_enter_printf_va_fl(__FILE__, __LINE__, (category), \
253 + (label), (repo), (fmt), (ap))
254 +
255 +void trace2_region_enter_printf_fl(const char *file, int line,
256 + const char *category, const char *label,
257 + const struct repository *repo,
258 + const char *fmt, ...);
259 +
260 +#ifdef HAVE_VARIADIC_MACROS
261 +#define trace2_region_enter_printf(category, label, repo, ...) \
262 + trace2_region_enter_printf_fl(__FILE__, __LINE__, (category), (label), \
263 + (repo), __VA_ARGS__)
264 +#else
265 +/* clang-format off */
266 +__attribute__((format (region_enter_printf, 4, 5)))
267 +void trace2_region_enter_printf(const char *category, const char *label,
268 + const struct repository *repo, const char *fmt,
269 + ...);
270 +/* clang-format on */
271 +#endif
272 +
273 +/*
274 + * Emit a 'region_leave' event for <category>.<label> with optional
275 + * repo-id and printf message.
276 + *
277 + * Leave current nesting level and report the elapsed time spent
278 + * in this nesting level.
279 + */
280 +void trace2_region_leave_fl(const char *file, int line, const char *category,
281 + const char *label, const struct repository *repo);
282 +
283 +#define trace2_region_leave(category, label, repo) \
284 + trace2_region_leave_fl(__FILE__, __LINE__, (category), (label), (repo))
285 +
286 +void trace2_region_leave_printf_va_fl(const char *file, int line,
287 + const char *category, const char *label,
288 + const struct repository *repo,
289 + const char *fmt, va_list ap);
290 +
291 +#define trace2_region_leave_printf_va(category, label, repo, fmt, ap) \
292 + trace2_region_leave_printf_va_fl(__FILE__, __LINE__, (category), \
293 + (label), (repo), (fmt), (ap))
294 +
295 +void trace2_region_leave_printf_fl(const char *file, int line,
296 + const char *category, const char *label,
297 + const struct repository *repo,
298 + const char *fmt, ...);
299 +
300 +#ifdef HAVE_VARIADIC_MACROS
301 +#define trace2_region_leave_printf(category, label, repo, ...) \
302 + trace2_region_leave_printf_fl(__FILE__, __LINE__, (category), (label), \
303 + (repo), __VA_ARGS__)
304 +#else
305 +/* clang-format off */
306 +__attribute__((format (region_leave_printf, 4, 5)))
307 +void trace2_region_leave_printf(const char *category, const char *label,
308 + const struct repository *repo, const char *fmt,
309 + ...);
310 +/* clang-format on */
311 +#endif
312 +
313 +/*
314 + * Emit a key-value pair 'data' event of the form <category>.<key> = <value>.
315 + * This event implicitly contains information about thread, nesting region,
316 + * and optional repo-id.
317 + *
318 + * On event-based TRACE2 targets, this generates a 'data' event suitable
319 + * for post-processing. On printf-based TRACE2 targets, this is converted
320 + * into a fixed-format printf message.
321 + */
322 +void trace2_data_string_fl(const char *file, int line, const char *category,
323 + const struct repository *repo, const char *key,
324 + const char *value);
325 +
326 +#define trace2_data_string(category, repo, key, value) \
327 + trace2_data_string_fl(__FILE__, __LINE__, (category), (repo), (key), \
328 + (value))
329 +
330 +void trace2_data_intmax_fl(const char *file, int line, const char *category,
331 + const struct repository *repo, const char *key,
332 + intmax_t value);
333 +
334 +#define trace2_data_intmax(category, repo, key, value) \
335 + trace2_data_intmax_fl(__FILE__, __LINE__, (category), (repo), (key), \
336 + (value))
337 +
338 +void trace2_data_json_fl(const char *file, int line, const char *category,
339 + const struct repository *repo, const char *key,
340 + const struct json_writer *jw);
341 +
342 +#define trace2_data_json(category, repo, key, value) \
343 + trace2_data_json_fl(__FILE__, __LINE__, (category), (repo), (key), \
344 + (value))
345 +
346 +/*
347 + * Emit a 'printf' event.
348 + *
349 + * Write an arbitrary formatted message to the TRACE2 targets. These
350 + * text messages should be considered as human-readable strings without
351 + * any formatting guidelines. Post-processors may choose to ignore
352 + * them.
353 + */
354 +void trace2_printf_va_fl(const char *file, int line, const char *fmt,
355 + va_list ap);
356 +
357 +#define trace2_printf_va(fmt, ap) \
358 + trace2_printf_va_fl(__FILE__, __LINE__, (fmt), (ap))
359 +
360 +void trace2_printf_fl(const char *file, int line, const char *fmt, ...);
361 +
362 +#ifdef HAVE_VARIADIC_MACROS
363 +#define trace2_printf(...) trace2_printf_fl(__FILE__, __LINE__, __VA_ARGS__)
364 +#else
365 +/* clang-format off */
366 +__attribute__((format (printf, 1, 2)))
367 +void trace2_printf(const char *fmt, ...);
368 +/* clang-format on */
369 +#endif
370 +
371 +#endif /* TRACE2_H */
trace2/tr2_cfg.c new
+90
@@ -0,0 +1,90 @@
1 +#include "cache.h"
2 +#include "config.h"
3 +#include "tr2_cfg.h"
4 +
5 +#define TR2_ENVVAR_CFG_PARAM "GIT_TR2_CONFIG_PARAMS"
6 +
7 +static struct strbuf **tr2_cfg_patterns;
8 +static int tr2_cfg_count_patterns;
9 +static int tr2_cfg_loaded;
10 +
11 +/*
12 + * Parse a string containing a comma-delimited list of config keys
13 + * or wildcard patterns into a list of strbufs.
14 + */
15 +static int tr2_cfg_load_patterns(void)
16 +{
17 + struct strbuf **s;
18 + const char *envvar;
19 +
20 + if (tr2_cfg_loaded)
21 + return tr2_cfg_count_patterns;
22 + tr2_cfg_loaded = 1;
23 +
24 + envvar = getenv(TR2_ENVVAR_CFG_PARAM);
25 + if (!envvar || !*envvar)
26 + return tr2_cfg_count_patterns;
27 +
28 + tr2_cfg_patterns = strbuf_split_buf(envvar, strlen(envvar), ',', -1);
29 + for (s = tr2_cfg_patterns; *s; s++) {
30 + struct strbuf *buf = *s;
31 +
32 + if (buf->len && buf->buf[buf->len - 1] == ',')
33 + strbuf_setlen(buf, buf->len - 1);
34 + strbuf_trim_trailing_newline(*s);
35 + strbuf_trim(*s);
36 + }
37 +
38 + tr2_cfg_count_patterns = s - tr2_cfg_patterns;
39 + return tr2_cfg_count_patterns;
40 +}
41 +
42 +void tr2_cfg_free_patterns(void)
43 +{
44 + if (tr2_cfg_patterns)
45 + strbuf_list_free(tr2_cfg_patterns);
46 + tr2_cfg_count_patterns = 0;
47 + tr2_cfg_loaded = 0;
48 +}
49 +
50 +struct tr2_cfg_data {
51 + const char *file;
52 + int line;
53 +};
54 +
55 +/*
56 + * See if the given config key matches any of our patterns of interest.
57 + */
58 +static int tr2_cfg_cb(const char *key, const char *value, void *d)
59 +{
60 + struct strbuf **s;
61 + struct tr2_cfg_data *data = (struct tr2_cfg_data *)d;
62 +
63 + for (s = tr2_cfg_patterns; *s; s++) {
64 + struct strbuf *buf = *s;
65 + int wm = wildmatch(buf->buf, key, WM_CASEFOLD);
66 + if (wm == WM_MATCH) {
67 + trace2_def_param_fl(data->file, data->line, key, value);
68 + return 0;
69 + }
70 + }
71 +
72 + return 0;
73 +}
74 +
75 +void tr2_cfg_list_config_fl(const char *file, int line)
76 +{
77 + struct tr2_cfg_data data = { file, line };
78 +
79 + if (tr2_cfg_load_patterns() > 0)
80 + read_early_config(tr2_cfg_cb, &data);
81 +}
82 +
83 +void tr2_cfg_set_fl(const char *file, int line, const char *key,
84 + const char *value)
85 +{
86 + struct tr2_cfg_data data = { file, line };
87 +
88 + if (tr2_cfg_load_patterns() > 0)
89 + tr2_cfg_cb(key, value, &data);
90 +}
trace2/tr2_cfg.h new
+19
@@ -0,0 +1,19 @@
1 +#ifndef TR2_CFG_H
2 +#define TR2_CFG_H
3 +
4 +/*
5 + * Iterate over all config settings and emit 'def_param' events for the
6 + * "interesting" ones to TRACE2.
7 + */
8 +void tr2_cfg_list_config_fl(const char *file, int line);
9 +
10 +/*
11 + * Emit a "def_param" event for the given key/value pair IF we consider
12 + * the key to be "interesting".
13 + */
14 +void tr2_cfg_set_fl(const char *file, int line, const char *key,
15 + const char *value);
16 +
17 +void tr2_cfg_free_patterns(void);
18 +
19 +#endif /* TR2_CFG_H */
trace2/tr2_cmd_name.c new
+30
@@ -0,0 +1,30 @@
1 +#include "cache.h"
2 +#include "trace2/tr2_cmd_name.h"
3 +
4 +#define TR2_ENVVAR_PARENT_NAME "GIT_TR2_PARENT_NAME"
5 +
6 +static struct strbuf tr2cmdname_hierarchy = STRBUF_INIT;
7 +
8 +void tr2_cmd_name_append_hierarchy(const char *name)
9 +{
10 + const char *parent_name = getenv(TR2_ENVVAR_PARENT_NAME);
11 +
12 + strbuf_reset(&tr2cmdname_hierarchy);
13 + if (parent_name && *parent_name) {
14 + strbuf_addstr(&tr2cmdname_hierarchy, parent_name);
15 + strbuf_addch(&tr2cmdname_hierarchy, '/');
16 + }
17 + strbuf_addstr(&tr2cmdname_hierarchy, name);
18 +
19 + setenv(TR2_ENVVAR_PARENT_NAME, tr2cmdname_hierarchy.buf, 1);
20 +}
21 +
22 +const char *tr2_cmd_name_get_hierarchy(void)
23 +{
24 + return tr2cmdname_hierarchy.buf;
25 +}
26 +
27 +void tr2_cmd_name_release(void)
28 +{
29 + strbuf_release(&tr2cmdname_hierarchy);
30 +}
trace2/tr2_cmd_name.h new
+24
@@ -0,0 +1,24 @@
1 +#ifndef TR2_CMD_NAME_H
2 +#define TR2_CMD_NAME_H
3 +
4 +/*
5 + * Append the current command name to the list being maintained
6 + * in the environment.
7 + *
8 + * The hierarchy for a top-level git command is just the current
9 + * command name. For a child git process, the hierarchy includes the
10 + * names of the parent processes.
11 + *
12 + * The hierarchy for the current process will be exported to the
13 + * environment and inherited by child processes.
14 + */
15 +void tr2_cmd_name_append_hierarchy(const char *name);
16 +
17 +/*
18 + * Get the command name hierarchy for the current process.
19 + */
20 +const char *tr2_cmd_name_get_hierarchy(void);
21 +
22 +void tr2_cmd_name_release(void);
23 +
24 +#endif /* TR2_CMD_NAME_H */
trace2/tr2_dst.c new
+252
@@ -0,0 +1,252 @@
1 +#include "cache.h"
2 +#include "trace2/tr2_dst.h"
3 +
4 +/*
5 + * If a Trace2 target cannot be opened for writing, we should issue a
6 + * warning to stderr, but this is very annoying if the target is a pipe
7 + * or socket and beyond the user's control -- especially since every
8 + * git command (and sub-command) will print the message. So we silently
9 + * eat these warnings and just discard the trace data.
10 + *
11 + * Enable the following environment variable to see these warnings.
12 + */
13 +#define TR2_ENVVAR_DST_DEBUG "GIT_TR2_DST_DEBUG"
14 +
15 +static int tr2_dst_want_warning(void)
16 +{
17 + static int tr2env_dst_debug = -1;
18 +
19 + if (tr2env_dst_debug == -1) {
20 + const char *env_value = getenv(TR2_ENVVAR_DST_DEBUG);
21 + if (!env_value || !*env_value)
22 + tr2env_dst_debug = 0;
23 + else
24 + tr2env_dst_debug = atoi(env_value) > 0;
25 + }
26 +
27 + return tr2env_dst_debug;
28 +}
29 +
30 +void tr2_dst_trace_disable(struct tr2_dst *dst)
31 +{
32 + if (dst->need_close)
33 + close(dst->fd);
34 + dst->fd = 0;
35 + dst->initialized = 1;
36 + dst->need_close = 0;
37 +}
38 +
39 +static int tr2_dst_try_path(struct tr2_dst *dst, const char *tgt_value)
40 +{
41 + int fd = open(tgt_value, O_WRONLY | O_APPEND | O_CREAT, 0666);
42 + if (fd == -1) {
43 + if (tr2_dst_want_warning())
44 + warning("trace2: could not open '%s' for '%s' tracing: %s",
45 + tgt_value, dst->env_var_name, strerror(errno));
46 +
47 + tr2_dst_trace_disable(dst);
48 + return 0;
49 + }
50 +
51 + dst->fd = fd;
52 + dst->need_close = 1;
53 + dst->initialized = 1;
54 +
55 + return dst->fd;
56 +}
57 +
58 +#ifndef NO_UNIX_SOCKETS
59 +#define PREFIX_AF_UNIX "af_unix:"
60 +#define PREFIX_AF_UNIX_STREAM "af_unix:stream:"
61 +#define PREFIX_AF_UNIX_DGRAM "af_unix:dgram:"
62 +
63 +static int tr2_dst_try_uds_connect(const char *path, int sock_type, int *out_fd)
64 +{
65 + int fd;
66 + struct sockaddr_un sa;
67 +
68 + fd = socket(AF_UNIX, sock_type, 0);
69 + if (fd == -1)
70 + return errno;
71 +
72 + sa.sun_family = AF_UNIX;
73 + strlcpy(sa.sun_path, path, sizeof(sa.sun_path));
74 +
75 + if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
76 + int e = errno;
77 + close(fd);
78 + return e;
79 + }
80 +
81 + *out_fd = fd;
82 + return 0;
83 +}
84 +
85 +#define TR2_DST_UDS_TRY_STREAM (1 << 0)
86 +#define TR2_DST_UDS_TRY_DGRAM (1 << 1)
87 +
88 +static int tr2_dst_try_unix_domain_socket(struct tr2_dst *dst,
89 + const char *tgt_value)
90 +{
91 + unsigned int uds_try = 0;
92 + int fd;
93 + int e;
94 + const char *path = NULL;
95 +
96 + /*
97 + * Allow "af_unix:[<type>:]<absolute_path>"
98 + *
99 + * Trace2 always writes complete individual messages (without
100 + * chunking), so we can talk to either DGRAM or STREAM type sockets.
101 + *
102 + * Allow the user to explicitly request the socket type.
103 + *
104 + * If they omit the socket type, try one and then the other.
105 + */
106 +
107 + if (skip_prefix(tgt_value, PREFIX_AF_UNIX_STREAM, &path))
108 + uds_try |= TR2_DST_UDS_TRY_STREAM;
109 +
110 + else if (skip_prefix(tgt_value, PREFIX_AF_UNIX_DGRAM, &path))
111 + uds_try |= TR2_DST_UDS_TRY_DGRAM;
112 +
113 + else if (skip_prefix(tgt_value, PREFIX_AF_UNIX, &path))
114 + uds_try |= TR2_DST_UDS_TRY_STREAM | TR2_DST_UDS_TRY_DGRAM;
115 +
116 + if (!path || !*path) {
117 + if (tr2_dst_want_warning())
118 + warning("trace2: invalid AF_UNIX value '%s' for '%s' tracing",
119 + tgt_value, dst->env_var_name);
120 +
121 + tr2_dst_trace_disable(dst);
122 + return 0;
123 + }
124 +
125 + if (!is_absolute_path(path) ||
126 + strlen(path) >= sizeof(((struct sockaddr_un *)0)->sun_path)) {
127 + if (tr2_dst_want_warning())
128 + warning("trace2: invalid AF_UNIX path '%s' for '%s' tracing",
129 + path, dst->env_var_name);
130 +
131 + tr2_dst_trace_disable(dst);
132 + return 0;
133 + }
134 +
135 + if (uds_try & TR2_DST_UDS_TRY_STREAM) {
136 + e = tr2_dst_try_uds_connect(path, SOCK_STREAM, &fd);
137 + if (!e)
138 + goto connected;
139 + if (e != EPROTOTYPE)
140 + goto error;
141 + }
142 + if (uds_try & TR2_DST_UDS_TRY_DGRAM) {
143 + e = tr2_dst_try_uds_connect(path, SOCK_DGRAM, &fd);
144 + if (!e)
145 + goto connected;
146 + }
147 +
148 +error:
149 + if (tr2_dst_want_warning())
150 + warning("trace2: could not connect to socket '%s' for '%s' tracing: %s",
151 + path, dst->env_var_name, strerror(e));
152 +
153 + tr2_dst_trace_disable(dst);
154 + return 0;
155 +
156 +connected:
157 + dst->fd = fd;
158 + dst->need_close = 1;
159 + dst->initialized = 1;
160 +
161 + return dst->fd;
162 +}
163 +#endif
164 +
165 +static void tr2_dst_malformed_warning(struct tr2_dst *dst,
166 + const char *tgt_value)
167 +{
168 + struct strbuf buf = STRBUF_INIT;
169 +
170 + strbuf_addf(&buf, "trace2: unknown value for '%s': '%s'",
171 + dst->env_var_name, tgt_value);
172 + warning("%s", buf.buf);
173 +
174 + strbuf_release(&buf);
175 +}
176 +
177 +int tr2_dst_get_trace_fd(struct tr2_dst *dst)
178 +{
179 + const char *tgt_value;
180 +
181 + /* don't open twice */
182 + if (dst->initialized)
183 + return dst->fd;
184 +
185 + dst->initialized = 1;
186 +
187 + tgt_value = getenv(dst->env_var_name);
188 +
189 + if (!tgt_value || !strcmp(tgt_value, "") || !strcmp(tgt_value, "0") ||
190 + !strcasecmp(tgt_value, "false")) {
191 + dst->fd = 0;
192 + return dst->fd;
193 + }
194 +
195 + if (!strcmp(tgt_value, "1") || !strcasecmp(tgt_value, "true")) {
196 + dst->fd = STDERR_FILENO;
197 + return dst->fd;
198 + }
199 +
200 + if (strlen(tgt_value) == 1 && isdigit(*tgt_value)) {
201 + dst->fd = atoi(tgt_value);
202 + return dst->fd;
203 + }
204 +
205 + if (is_absolute_path(tgt_value))
206 + return tr2_dst_try_path(dst, tgt_value);
207 +
208 +#ifndef NO_UNIX_SOCKETS
209 + if (starts_with(tgt_value, PREFIX_AF_UNIX))
210 + return tr2_dst_try_unix_domain_socket(dst, tgt_value);
211 +#endif
212 +
213 + /* Always warn about malformed values. */
214 + tr2_dst_malformed_warning(dst, tgt_value);
215 + tr2_dst_trace_disable(dst);
216 + return 0;
217 +}
218 +
219 +int tr2_dst_trace_want(struct tr2_dst *dst)
220 +{
221 + return !!tr2_dst_get_trace_fd(dst);
222 +}
223 +
224 +void tr2_dst_write_line(struct tr2_dst *dst, struct strbuf *buf_line)
225 +{
226 + int fd = tr2_dst_get_trace_fd(dst);
227 +
228 + strbuf_complete_line(buf_line); /* ensure final NL on buffer */
229 +
230 + /*
231 + * We do not use write_in_full() because we do not want
232 + * a short-write to try again. We are using O_APPEND mode
233 + * files and the kernel handles the atomic seek+write. If
234 + * another thread or git process is concurrently writing to
235 + * this fd or file, our remainder-write may not be contiguous
236 + * with our initial write of this message. And that will
237 + * confuse readers. So just don't bother.
238 + *
239 + * It is assumed that TRACE2 messages are short enough that
240 + * the system can write them in 1 attempt and we won't see
241 + * a short-write.
242 + *
243 + * If we get an IO error, just close the trace dst.
244 + */
245 + if (write(fd, buf_line->buf, buf_line->len) >= 0)
246 + return;
247 +
248 + if (tr2_dst_want_warning())
249 + warning("unable to write trace to '%s': %s", dst->env_var_name,
250 + strerror(errno));
251 + tr2_dst_trace_disable(dst);
252 +}
trace2/tr2_dst.h new
+36
@@ -0,0 +1,36 @@
1 +#ifndef TR2_DST_H
2 +#define TR2_DST_H
3 +
4 +struct strbuf;
5 +
6 +struct tr2_dst {
7 + const char *const env_var_name;
8 + int fd;
9 + unsigned int initialized : 1;
10 + unsigned int need_close : 1;
11 +};
12 +
13 +/*
14 + * Disable TRACE2 on the destination. In TRACE2 a destination (DST)
15 + * wraps a file descriptor; it is associated with a TARGET which
16 + * defines the formatting.
17 + */
18 +void tr2_dst_trace_disable(struct tr2_dst *dst);
19 +
20 +/*
21 + * Return the file descriptor for the DST.
22 + * If 0, the dst is closed or disabled.
23 + */
24 +int tr2_dst_get_trace_fd(struct tr2_dst *dst);
25 +
26 +/*
27 + * Return true if the DST is opened for writing.
28 + */
29 +int tr2_dst_trace_want(struct tr2_dst *dst);
30 +
31 +/*
32 + * Write a single line/message to the trace file.
33 + */
34 +void tr2_dst_write_line(struct tr2_dst *dst, struct strbuf *buf_line);
35 +
36 +#endif /* TR2_DST_H */
trace2/tr2_sid.c new
+67
@@ -0,0 +1,67 @@
1 +#include "cache.h"
2 +#include "trace2/tr2_sid.h"
3 +
4 +#define TR2_ENVVAR_PARENT_SID "GIT_TR2_PARENT_SID"
5 +
6 +static struct strbuf tr2sid_buf = STRBUF_INIT;
7 +static int tr2sid_nr_git_parents;
8 +
9 +/*
10 + * Compute a "unique" session id (SID) for the current process. This allows
11 + * all events from this process to have a single label (much like a PID).
12 + *
13 + * Export this into our environment so that all child processes inherit it.
14 + *
15 + * If we were started by another git instance, use our parent's SID as a
16 + * prefix. (This lets us track parent/child relationships even if there
17 + * is an intermediate shell process.)
18 + *
19 + * Additionally, count the number of nested git processes.
20 + */
21 +static void tr2_sid_compute(void)
22 +{
23 + uint64_t us_now;
24 + const char *parent_sid;
25 +
26 + if (tr2sid_buf.len)
27 + return;
28 +
29 + parent_sid = getenv(TR2_ENVVAR_PARENT_SID);
30 + if (parent_sid && *parent_sid) {
31 + const char *p;
32 + for (p = parent_sid; *p; p++)
33 + if (*p == '/')
34 + tr2sid_nr_git_parents++;
35 +
36 + strbuf_addstr(&tr2sid_buf, parent_sid);
37 + strbuf_addch(&tr2sid_buf, '/');
38 + tr2sid_nr_git_parents++;
39 + }
40 +
41 + us_now = getnanotime() / 1000;
42 + strbuf_addf(&tr2sid_buf, "%" PRIuMAX "-%" PRIdMAX, (uintmax_t)us_now,
43 + (intmax_t)getpid());
44 +
45 + setenv(TR2_ENVVAR_PARENT_SID, tr2sid_buf.buf, 1);
46 +}
47 +
48 +const char *tr2_sid_get(void)
49 +{
50 + if (!tr2sid_buf.len)
51 + tr2_sid_compute();
52 +
53 + return tr2sid_buf.buf;
54 +}
55 +
56 +int tr2_sid_depth(void)
57 +{
58 + if (!tr2sid_buf.len)
59 + tr2_sid_compute();
60 +
61 + return tr2sid_nr_git_parents;
62 +}
63 +
64 +void tr2_sid_release(void)
65 +{
66 + strbuf_release(&tr2sid_buf);
67 +}
trace2/tr2_sid.h new
+18
@@ -0,0 +1,18 @@
1 +#ifndef TR2_SID_H
2 +#define TR2_SID_H
3 +
4 +/*
5 + * Get our session id. Compute if necessary.
6 + */
7 +const char *tr2_sid_get(void);
8 +
9 +/*
10 + * Get our process depth. A top-level git process invoked from the
11 + * command line will have depth=0. A child git process will have
12 + * depth=1 and so on.
13 + */
14 +int tr2_sid_depth(void);
15 +
16 +void tr2_sid_release(void);
17 +
18 +#endif /* TR2_SID_H */
trace2/tr2_tbuf.c new
+32
@@ -0,0 +1,32 @@
1 +#include "cache.h"
2 +#include "tr2_tbuf.h"
3 +
4 +void tr2_tbuf_local_time(struct tr2_tbuf *tb)
5 +{
6 + struct timeval tv;
7 + struct tm tm;
8 + time_t secs;
9 +
10 + gettimeofday(&tv, NULL);
11 + secs = tv.tv_sec;
12 + localtime_r(&secs, &tm);
13 +
14 + xsnprintf(tb->buf, sizeof(tb->buf), "%02d:%02d:%02d.%06ld", tm.tm_hour,
15 + tm.tm_min, tm.tm_sec, (long)tv.tv_usec);
16 +}
17 +
18 +void tr2_tbuf_utc_time(struct tr2_tbuf *tb)
19 +{
20 + struct timeval tv;
21 + struct tm tm;
22 + time_t secs;
23 +
24 + gettimeofday(&tv, NULL);
25 + secs = tv.tv_sec;
26 + gmtime_r(&secs, &tm);
27 +
28 + xsnprintf(tb->buf, sizeof(tb->buf),
29 + "%4d-%02d-%02d %02d:%02d:%02d.%06ld", tm.tm_year + 1900,
30 + tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
31 + (long)tv.tv_usec);
32 +}
trace2/tr2_tbuf.h new
+23
@@ -0,0 +1,23 @@
1 +#ifndef TR2_TBUF_H
2 +#define TR2_TBUF_H
3 +
4 +/*
5 + * A simple wrapper around a fixed buffer to avoid C syntax
6 + * quirks and the need to pass around an additional size_t
7 + * argument.
8 + */
9 +struct tr2_tbuf {
10 + char buf[32];
11 +};
12 +
13 +/*
14 + * Fill buffer with formatted local time string.
15 + */
16 +void tr2_tbuf_local_time(struct tr2_tbuf *tb);
17 +
18 +/*
19 + * Fill buffer with formatted UTC time string.
20 + */
21 +void tr2_tbuf_utc_time(struct tr2_tbuf *tb);
22 +
23 +#endif /* TR2_TBUF_H */
trace2/tr2_tgt.h new
+133
@@ -0,0 +1,133 @@
1 +#ifndef TR2_TGT_H
2 +#define TR2_TGT_H
3 +
4 +struct child_process;
5 +struct repository;
6 +struct json_writer;
7 +
8 +/*
9 + * Function prototypes for a TRACE2 "target" vtable.
10 + */
11 +
12 +typedef int(tr2_tgt_init_t)(void);
13 +typedef void(tr2_tgt_term_t)(void);
14 +
15 +typedef void(tr2_tgt_evt_version_fl_t)(const char *file, int line);
16 +
17 +typedef void(tr2_tgt_evt_start_fl_t)(const char *file, int line,
18 + const char **argv);
19 +typedef void(tr2_tgt_evt_exit_fl_t)(const char *file, int line,
20 + uint64_t us_elapsed_absolute, int code);
21 +typedef void(tr2_tgt_evt_signal_t)(uint64_t us_elapsed_absolute, int signo);
22 +typedef void(tr2_tgt_evt_atexit_t)(uint64_t us_elapsed_absolute, int code);
23 +
24 +typedef void(tr2_tgt_evt_error_va_fl_t)(const char *file, int line,
25 + const char *fmt, va_list ap);
26 +
27 +typedef void(tr2_tgt_evt_command_path_fl_t)(const char *file, int line,
28 + const char *command_path);
29 +typedef void(tr2_tgt_evt_command_name_fl_t)(const char *file, int line,
30 + const char *name,
31 + const char *hierarchy);
32 +typedef void(tr2_tgt_evt_command_mode_fl_t)(const char *file, int line,
33 + const char *mode);
34 +
35 +typedef void(tr2_tgt_evt_alias_fl_t)(const char *file, int line,
36 + const char *alias, const char **argv);
37 +
38 +typedef void(tr2_tgt_evt_child_start_fl_t)(const char *file, int line,
39 + uint64_t us_elapsed_absolute,
40 + const struct child_process *cmd);
41 +typedef void(tr2_tgt_evt_child_exit_fl_t)(const char *file, int line,
42 + uint64_t us_elapsed_absolute, int cid,
43 + int pid, int code,
44 + uint64_t us_elapsed_child);
45 +
46 +typedef void(tr2_tgt_evt_thread_start_fl_t)(const char *file, int line,
47 + uint64_t us_elapsed_absolute);
48 +typedef void(tr2_tgt_evt_thread_exit_fl_t)(const char *file, int line,
49 + uint64_t us_elapsed_absolute,
50 + uint64_t us_elapsed_thread);
51 +
52 +typedef void(tr2_tgt_evt_exec_fl_t)(const char *file, int line,
53 + uint64_t us_elapsed_absolute, int exec_id,
54 + const char *exe, const char **argv);
55 +typedef void(tr2_tgt_evt_exec_result_fl_t)(const char *file, int line,
56 + uint64_t us_elapsed_absolute,
57 + int exec_id, int code);
58 +
59 +typedef void(tr2_tgt_evt_param_fl_t)(const char *file, int line,
60 + const char *param, const char *value);
61 +
62 +typedef void(tr2_tgt_evt_repo_fl_t)(const char *file, int line,
63 + const struct repository *repo);
64 +
65 +typedef void(tr2_tgt_evt_region_enter_printf_va_fl_t)(
66 + const char *file, int line, uint64_t us_elapsed_absolute,
67 + const char *category, const char *label, const struct repository *repo,
68 + const char *fmt, va_list ap);
69 +typedef void(tr2_tgt_evt_region_leave_printf_va_fl_t)(
70 + const char *file, int line, uint64_t us_elapsed_absolute,
71 + uint64_t us_elapsed_region, const char *category, const char *label,
72 + const struct repository *repo, const char *fmt, va_list ap);
73 +
74 +typedef void(tr2_tgt_evt_data_fl_t)(const char *file, int line,
75 + uint64_t us_elapsed_absolute,
76 + uint64_t us_elapsed_region,
77 + const char *category,
78 + const struct repository *repo,
79 + const char *key, const char *value);
80 +typedef void(tr2_tgt_evt_data_json_fl_t)(const char *file, int line,
81 + uint64_t us_elapsed_absolute,
82 + uint64_t us_elapsed_region,
83 + const char *category,
84 + const struct repository *repo,
85 + const char *key,
86 + const struct json_writer *value);
87 +
88 +typedef void(tr2_tgt_evt_printf_va_fl_t)(const char *file, int line,
89 + uint64_t us_elapsed_absolute,
90 + const char *fmt, va_list ap);
91 +
92 +/*
93 + * "vtable" for a TRACE2 target. Use NULL if a target does not want
94 + * to emit that message.
95 + */
96 +/* clang-format off */
97 +struct tr2_tgt {
98 + struct tr2_dst *pdst;
99 +
100 + tr2_tgt_init_t *pfn_init;
101 + tr2_tgt_term_t *pfn_term;
102 +
103 + tr2_tgt_evt_version_fl_t *pfn_version_fl;
104 + tr2_tgt_evt_start_fl_t *pfn_start_fl;
105 + tr2_tgt_evt_exit_fl_t *pfn_exit_fl;
106 + tr2_tgt_evt_signal_t *pfn_signal;
107 + tr2_tgt_evt_atexit_t *pfn_atexit;
108 + tr2_tgt_evt_error_va_fl_t *pfn_error_va_fl;
109 + tr2_tgt_evt_command_path_fl_t *pfn_command_path_fl;
110 + tr2_tgt_evt_command_name_fl_t *pfn_command_name_fl;
111 + tr2_tgt_evt_command_mode_fl_t *pfn_command_mode_fl;
112 + tr2_tgt_evt_alias_fl_t *pfn_alias_fl;
113 + tr2_tgt_evt_child_start_fl_t *pfn_child_start_fl;
114 + tr2_tgt_evt_child_exit_fl_t *pfn_child_exit_fl;
115 + tr2_tgt_evt_thread_start_fl_t *pfn_thread_start_fl;
116 + tr2_tgt_evt_thread_exit_fl_t *pfn_thread_exit_fl;
117 + tr2_tgt_evt_exec_fl_t *pfn_exec_fl;
118 + tr2_tgt_evt_exec_result_fl_t *pfn_exec_result_fl;
119 + tr2_tgt_evt_param_fl_t *pfn_param_fl;
120 + tr2_tgt_evt_repo_fl_t *pfn_repo_fl;
121 + tr2_tgt_evt_region_enter_printf_va_fl_t *pfn_region_enter_printf_va_fl;
122 + tr2_tgt_evt_region_leave_printf_va_fl_t *pfn_region_leave_printf_va_fl;
123 + tr2_tgt_evt_data_fl_t *pfn_data_fl;
124 + tr2_tgt_evt_data_json_fl_t *pfn_data_json_fl;
125 + tr2_tgt_evt_printf_va_fl_t *pfn_printf_va_fl;
126 +};
127 +/* clang-format on */
128 +
129 +extern struct tr2_tgt tr2_tgt_event;
130 +extern struct tr2_tgt tr2_tgt_normal;
131 +extern struct tr2_tgt tr2_tgt_perf;
132 +
133 +#endif /* TR2_TGT_H */
trace2/tr2_tgt_event.c new
+588
@@ -0,0 +1,588 @@
1 +#include "cache.h"
2 +#include "config.h"
3 +#include "json-writer.h"
4 +#include "run-command.h"
5 +#include "version.h"
6 +#include "trace2/tr2_dst.h"
7 +#include "trace2/tr2_tbuf.h"
8 +#include "trace2/tr2_sid.h"
9 +#include "trace2/tr2_tgt.h"
10 +#include "trace2/tr2_tls.h"
11 +
12 +static struct tr2_dst tr2dst_event = { "GIT_TR2_EVENT", 0, 0, 0 };
13 +
14 +/*
15 + * The version number of the JSON data generated by the EVENT target
16 + * in this source file. Update this if you make a significant change
17 + * to the JSON fields or message structure. You probably do not need
18 + * to update this if you just add another call to one of the existing
19 + * TRACE2 API methods.
20 + */
21 +#define TR2_EVENT_VERSION "1"
22 +
23 +/*
24 + * Region nesting limit for messages written to the event target.
25 + *
26 + * The "region_enter" and "region_leave" messages (especially recursive
27 + * messages such as those produced while diving the worktree or index)
28 + * are primarily intended for the performance target during debugging.
29 + *
30 + * Some of the outer-most messages, however, may be of interest to the
31 + * event target. Set this environment variable to a larger integer for
32 + * more detail in the event target.
33 + */
34 +#define TR2_ENVVAR_EVENT_NESTING "GIT_TR2_EVENT_NESTING"
35 +static int tr2env_event_nesting_wanted = 2;
36 +
37 +/*
38 + * Set this environment variable to true to omit the <time>, <file>, and
39 + * <line> fields from most events.
40 + */
41 +#define TR2_ENVVAR_EVENT_BRIEF "GIT_TR2_EVENT_BRIEF"
42 +static int tr2env_event_brief;
43 +
44 +static int fn_init(void)
45 +{
46 + int want = tr2_dst_trace_want(&tr2dst_event);
47 + int want_nesting;
48 + int want_brief;
49 + char *nesting;
50 + char *brief;
51 +
52 + if (!want)
53 + return want;
54 +
55 + nesting = getenv(TR2_ENVVAR_EVENT_NESTING);
56 + if (nesting && ((want_nesting = atoi(nesting)) > 0))
57 + tr2env_event_nesting_wanted = want_nesting;
58 +
59 + brief = getenv(TR2_ENVVAR_EVENT_BRIEF);
60 + if (brief && ((want_brief = atoi(brief)) > 0))
61 + tr2env_event_brief = want_brief;
62 +
63 + return want;
64 +}
65 +
66 +static void fn_term(void)
67 +{
68 + tr2_dst_trace_disable(&tr2dst_event);
69 +}
70 +
71 +/*
72 + * Append common key-value pairs to the currently open JSON object.
73 + * "event:"<event_name>"
74 + * "sid":"<sid>"
75 + * "thread":"<thread_name>"
76 + * "time":"<time>"
77 + * "file":"<filename>"
78 + * "line":<line_number>
79 + * "repo":<repo_id>
80 + */
81 +static void event_fmt_prepare(const char *event_name, const char *file,
82 + int line, const struct repository *repo,
83 + struct json_writer *jw)
84 +{
85 + struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
86 + struct tr2_tbuf tb_now;
87 +
88 + jw_object_string(jw, "event", event_name);
89 + jw_object_string(jw, "sid", tr2_sid_get());
90 + jw_object_string(jw, "thread", ctx->thread_name.buf);
91 +
92 + /*
93 + * In brief mode, only emit <time> on these 2 event types.
94 + */
95 + if (!tr2env_event_brief || !strcmp(event_name, "version") ||
96 + !strcmp(event_name, "atexit")) {
97 + tr2_tbuf_utc_time(&tb_now);
98 + jw_object_string(jw, "time", tb_now.buf);
99 + }
100 +
101 + if (!tr2env_event_brief && file && *file) {
102 + jw_object_string(jw, "file", file);
103 + jw_object_intmax(jw, "line", line);
104 + }
105 +
106 + if (repo)
107 + jw_object_intmax(jw, "repo", repo->trace2_repo_id);
108 +}
109 +
110 +static void fn_version_fl(const char *file, int line)
111 +{
112 + const char *event_name = "version";
113 + struct json_writer jw = JSON_WRITER_INIT;
114 +
115 + jw_object_begin(&jw, 0);
116 + event_fmt_prepare(event_name, file, line, NULL, &jw);
117 + jw_object_string(&jw, "evt", TR2_EVENT_VERSION);
118 + jw_object_string(&jw, "exe", git_version_string);
119 + jw_end(&jw);
120 +
121 + tr2_dst_write_line(&tr2dst_event, &jw.json);
122 + jw_release(&jw);
123 +}
124 +
125 +static void fn_start_fl(const char *file, int line, const char **argv)
126 +{
127 + const char *event_name = "start";
128 + struct json_writer jw = JSON_WRITER_INIT;
129 +
130 + jw_object_begin(&jw, 0);
131 + event_fmt_prepare(event_name, file, line, NULL, &jw);
132 + jw_object_inline_begin_array(&jw, "argv");
133 + jw_array_argv(&jw, argv);
134 + jw_end(&jw);
135 + jw_end(&jw);
136 +
137 + tr2_dst_write_line(&tr2dst_event, &jw.json);
138 + jw_release(&jw);
139 +}
140 +
141 +static void fn_exit_fl(const char *file, int line, uint64_t us_elapsed_absolute,
142 + int code)
143 +{
144 + const char *event_name = "exit";
145 + struct json_writer jw = JSON_WRITER_INIT;
146 + double t_abs = (double)us_elapsed_absolute / 1000000.0;
147 +
148 + jw_object_begin(&jw, 0);
149 + event_fmt_prepare(event_name, file, line, NULL, &jw);
150 + jw_object_double(&jw, "t_abs", 6, t_abs);
151 + jw_object_intmax(&jw, "code", code);
152 + jw_end(&jw);
153 +
154 + tr2_dst_write_line(&tr2dst_event, &jw.json);
155 + jw_release(&jw);
156 +}
157 +
158 +static void fn_signal(uint64_t us_elapsed_absolute, int signo)
159 +{
160 + const char *event_name = "signal";
161 + struct json_writer jw = JSON_WRITER_INIT;
162 + double t_abs = (double)us_elapsed_absolute / 1000000.0;
163 +
164 + jw_object_begin(&jw, 0);
165 + event_fmt_prepare(event_name, __FILE__, __LINE__, NULL, &jw);
166 + jw_object_double(&jw, "t_abs", 6, t_abs);
167 + jw_object_intmax(&jw, "signo", signo);
168 + jw_end(&jw);
169 +
170 + tr2_dst_write_line(&tr2dst_event, &jw.json);
171 + jw_release(&jw);
172 +}
173 +
174 +static void fn_atexit(uint64_t us_elapsed_absolute, int code)
175 +{
176 + const char *event_name = "atexit";
177 + struct json_writer jw = JSON_WRITER_INIT;
178 + double t_abs = (double)us_elapsed_absolute / 1000000.0;
179 +
180 + jw_object_begin(&jw, 0);
181 + event_fmt_prepare(event_name, __FILE__, __LINE__, NULL, &jw);
182 + jw_object_double(&jw, "t_abs", 6, t_abs);
183 + jw_object_intmax(&jw, "code", code);
184 + jw_end(&jw);
185 +
186 + tr2_dst_write_line(&tr2dst_event, &jw.json);
187 + jw_release(&jw);
188 +}
189 +
190 +static void maybe_add_string_va(struct json_writer *jw, const char *field_name,
191 + const char *fmt, va_list ap)
192 +{
193 + if (fmt && *fmt && ap) {
194 + va_list copy_ap;
195 + struct strbuf buf = STRBUF_INIT;
196 +
197 + va_copy(copy_ap, ap);
198 + strbuf_vaddf(&buf, fmt, copy_ap);
199 + va_end(copy_ap);
200 +
201 + jw_object_string(jw, field_name, buf.buf);
202 + strbuf_release(&buf);
203 + return;
204 + }
205 +
206 + if (fmt && *fmt) {
207 + jw_object_string(jw, field_name, fmt);
208 + return;
209 + }
210 +}
211 +
212 +static void fn_error_va_fl(const char *file, int line, const char *fmt,
213 + va_list ap)
214 +{
215 + const char *event_name = "error";
216 + struct json_writer jw = JSON_WRITER_INIT;
217 +
218 + jw_object_begin(&jw, 0);
219 + event_fmt_prepare(event_name, file, line, NULL, &jw);
220 + maybe_add_string_va(&jw, "msg", fmt, ap);
221 + /*
222 + * Also emit the format string as a field in case
223 + * post-processors want to aggregate common error
224 + * messages by type without argument fields (such
225 + * as pathnames or branch names) cluttering it up.
226 + */
227 + if (fmt && *fmt)
228 + jw_object_string(&jw, "fmt", fmt);
229 + jw_end(&jw);
230 +
231 + tr2_dst_write_line(&tr2dst_event, &jw.json);
232 + jw_release(&jw);
233 +}
234 +
235 +static void fn_command_path_fl(const char *file, int line, const char *pathname)
236 +{
237 + const char *event_name = "cmd_path";
238 + struct json_writer jw = JSON_WRITER_INIT;
239 +
240 + jw_object_begin(&jw, 0);
241 + event_fmt_prepare(event_name, file, line, NULL, &jw);
242 + jw_object_string(&jw, "path", pathname);
243 + jw_end(&jw);
244 +
245 + tr2_dst_write_line(&tr2dst_event, &jw.json);
246 + jw_release(&jw);
247 +}
248 +
249 +static void fn_command_name_fl(const char *file, int line, const char *name,
250 + const char *hierarchy)
251 +{
252 + const char *event_name = "cmd_name";
253 + struct json_writer jw = JSON_WRITER_INIT;
254 +
255 + jw_object_begin(&jw, 0);
256 + event_fmt_prepare(event_name, file, line, NULL, &jw);
257 + jw_object_string(&jw, "name", name);
258 + if (hierarchy && *hierarchy)
259 + jw_object_string(&jw, "hierarchy", hierarchy);
260 + jw_end(&jw);
261 +
262 + tr2_dst_write_line(&tr2dst_event, &jw.json);
263 + jw_release(&jw);
264 +}
265 +
266 +static void fn_command_mode_fl(const char *file, int line, const char *mode)
267 +{
268 + const char *event_name = "cmd_mode";
269 + struct json_writer jw = JSON_WRITER_INIT;
270 +
271 + jw_object_begin(&jw, 0);
272 + event_fmt_prepare(event_name, file, line, NULL, &jw);
273 + jw_object_string(&jw, "name", mode);
274 + jw_end(&jw);
275 +
276 + tr2_dst_write_line(&tr2dst_event, &jw.json);
277 + jw_release(&jw);
278 +}
279 +
280 +static void fn_alias_fl(const char *file, int line, const char *alias,
281 + const char **argv)
282 +{
283 + const char *event_name = "alias";
284 + struct json_writer jw = JSON_WRITER_INIT;
285 +
286 + jw_object_begin(&jw, 0);
287 + event_fmt_prepare(event_name, file, line, NULL, &jw);
288 + jw_object_string(&jw, "alias", alias);
289 + jw_object_inline_begin_array(&jw, "argv");
290 + jw_array_argv(&jw, argv);
291 + jw_end(&jw);
292 + jw_end(&jw);
293 +
294 + tr2_dst_write_line(&tr2dst_event, &jw.json);
295 + jw_release(&jw);
296 +}
297 +
298 +static void fn_child_start_fl(const char *file, int line,
299 + uint64_t us_elapsed_absolute,
300 + const struct child_process *cmd)
301 +{
302 + const char *event_name = "child_start";
303 + struct json_writer jw = JSON_WRITER_INIT;
304 +
305 + jw_object_begin(&jw, 0);
306 + event_fmt_prepare(event_name, file, line, NULL, &jw);
307 + jw_object_intmax(&jw, "child_id", cmd->trace2_child_id);
308 + if (cmd->trace2_hook_name) {
309 + jw_object_string(&jw, "child_class", "hook");
310 + jw_object_string(&jw, "hook_name", cmd->trace2_hook_name);
311 + } else {
312 + const char *child_class =
313 + cmd->trace2_child_class ? cmd->trace2_child_class : "?";
314 + jw_object_string(&jw, "child_class", child_class);
315 + }
316 + if (cmd->dir)
317 + jw_object_string(&jw, "cd", cmd->dir);
318 + jw_object_bool(&jw, "use_shell", cmd->use_shell);
319 + jw_object_inline_begin_array(&jw, "argv");
320 + if (cmd->git_cmd)
321 + jw_array_string(&jw, "git");
322 + jw_array_argv(&jw, cmd->argv);
323 + jw_end(&jw);
324 + jw_end(&jw);
325 +
326 + tr2_dst_write_line(&tr2dst_event, &jw.json);
327 + jw_release(&jw);
328 +}
329 +
330 +static void fn_child_exit_fl(const char *file, int line,
331 + uint64_t us_elapsed_absolute, int cid, int pid,
332 + int code, uint64_t us_elapsed_child)
333 +{
334 + const char *event_name = "child_exit";
335 + struct json_writer jw = JSON_WRITER_INIT;
336 + double t_rel = (double)us_elapsed_child / 1000000.0;
337 +
338 + jw_object_begin(&jw, 0);
339 + event_fmt_prepare(event_name, file, line, NULL, &jw);
340 + jw_object_intmax(&jw, "child_id", cid);
341 + jw_object_intmax(&jw, "pid", pid);
342 + jw_object_intmax(&jw, "code", code);
343 + jw_object_double(&jw, "t_rel", 6, t_rel);
344 + jw_end(&jw);
345 +
346 + tr2_dst_write_line(&tr2dst_event, &jw.json);
347 +
348 + jw_release(&jw);
349 +}
350 +
351 +static void fn_thread_start_fl(const char *file, int line,
352 + uint64_t us_elapsed_absolute)
353 +{
354 + const char *event_name = "thread_start";
355 + struct json_writer jw = JSON_WRITER_INIT;
356 +
357 + jw_object_begin(&jw, 0);
358 + event_fmt_prepare(event_name, file, line, NULL, &jw);
359 + jw_end(&jw);
360 +
361 + tr2_dst_write_line(&tr2dst_event, &jw.json);
362 + jw_release(&jw);
363 +}
364 +
365 +static void fn_thread_exit_fl(const char *file, int line,
366 + uint64_t us_elapsed_absolute,
367 + uint64_t us_elapsed_thread)
368 +{
369 + const char *event_name = "thread_exit";
370 + struct json_writer jw = JSON_WRITER_INIT;
371 + double t_rel = (double)us_elapsed_thread / 1000000.0;
372 +
373 + jw_object_begin(&jw, 0);
374 + event_fmt_prepare(event_name, file, line, NULL, &jw);
375 + jw_object_double(&jw, "t_rel", 6, t_rel);
376 + jw_end(&jw);
377 +
378 + tr2_dst_write_line(&tr2dst_event, &jw.json);
379 + jw_release(&jw);
380 +}
381 +
382 +static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,
383 + int exec_id, const char *exe, const char **argv)
384 +{
385 + const char *event_name = "exec";
386 + struct json_writer jw = JSON_WRITER_INIT;
387 +
388 + jw_object_begin(&jw, 0);
389 + event_fmt_prepare(event_name, file, line, NULL, &jw);
390 + jw_object_intmax(&jw, "exec_id", exec_id);
391 + if (exe)
392 + jw_object_string(&jw, "exe", exe);
393 + jw_object_inline_begin_array(&jw, "argv");
394 + jw_array_argv(&jw, argv);
395 + jw_end(&jw);
396 + jw_end(&jw);
397 +
398 + tr2_dst_write_line(&tr2dst_event, &jw.json);
399 + jw_release(&jw);
400 +}
401 +
402 +static void fn_exec_result_fl(const char *file, int line,
403 + uint64_t us_elapsed_absolute, int exec_id,
404 + int code)
405 +{
406 + const char *event_name = "exec_result";
407 + struct json_writer jw = JSON_WRITER_INIT;
408 +
409 + jw_object_begin(&jw, 0);
410 + event_fmt_prepare(event_name, file, line, NULL, &jw);
411 + jw_object_intmax(&jw, "exec_id", exec_id);
412 + jw_object_intmax(&jw, "code", code);
413 + jw_end(&jw);
414 +
415 + tr2_dst_write_line(&tr2dst_event, &jw.json);
416 + jw_release(&jw);
417 +}
418 +
419 +static void fn_param_fl(const char *file, int line, const char *param,
420 + const char *value)
421 +{
422 + const char *event_name = "def_param";
423 + struct json_writer jw = JSON_WRITER_INIT;
424 +
425 + jw_object_begin(&jw, 0);
426 + event_fmt_prepare(event_name, file, line, NULL, &jw);
427 + jw_object_string(&jw, "param", param);
428 + jw_object_string(&jw, "value", value);
429 + jw_end(&jw);
430 +
431 + tr2_dst_write_line(&tr2dst_event, &jw.json);
432 + jw_release(&jw);
433 +}
434 +
435 +static void fn_repo_fl(const char *file, int line,
436 + const struct repository *repo)
437 +{
438 + const char *event_name = "def_repo";
439 + struct json_writer jw = JSON_WRITER_INIT;
440 +
441 + jw_object_begin(&jw, 0);
442 + event_fmt_prepare(event_name, file, line, repo, &jw);
443 + jw_object_string(&jw, "worktree", repo->worktree);
444 + jw_end(&jw);
445 +
446 + tr2_dst_write_line(&tr2dst_event, &jw.json);
447 + jw_release(&jw);
448 +}
449 +
450 +static void fn_region_enter_printf_va_fl(const char *file, int line,
451 + uint64_t us_elapsed_absolute,
452 + const char *category,
453 + const char *label,
454 + const struct repository *repo,
455 + const char *fmt, va_list ap)
456 +{
457 + const char *event_name = "region_enter";
458 + struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
459 + if (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {
460 + struct json_writer jw = JSON_WRITER_INIT;
461 +
462 + jw_object_begin(&jw, 0);
463 + event_fmt_prepare(event_name, file, line, repo, &jw);
464 + jw_object_intmax(&jw, "nesting", ctx->nr_open_regions);
465 + if (category)
466 + jw_object_string(&jw, "category", category);
467 + if (label)
468 + jw_object_string(&jw, "label", label);
469 + maybe_add_string_va(&jw, "msg", fmt, ap);
470 + jw_end(&jw);
471 +
472 + tr2_dst_write_line(&tr2dst_event, &jw.json);
473 + jw_release(&jw);
474 + }
475 +}
476 +
477 +static void fn_region_leave_printf_va_fl(
478 + const char *file, int line, uint64_t us_elapsed_absolute,
479 + uint64_t us_elapsed_region, const char *category, const char *label,
480 + const struct repository *repo, const char *fmt, va_list ap)
481 +{
482 + const char *event_name = "region_leave";
483 + struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
484 + if (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {
485 + struct json_writer jw = JSON_WRITER_INIT;
486 + double t_rel = (double)us_elapsed_region / 1000000.0;
487 +
488 + jw_object_begin(&jw, 0);
489 + event_fmt_prepare(event_name, file, line, repo, &jw);
490 + jw_object_double(&jw, "t_rel", 6, t_rel);
491 + jw_object_intmax(&jw, "nesting", ctx->nr_open_regions);
492 + if (category)
493 + jw_object_string(&jw, "category", category);
494 + if (label)
495 + jw_object_string(&jw, "label", label);
496 + maybe_add_string_va(&jw, "msg", fmt, ap);
497 + jw_end(&jw);
498 +
499 + tr2_dst_write_line(&tr2dst_event, &jw.json);
500 + jw_release(&jw);
501 + }
502 +}
503 +
504 +static void fn_data_fl(const char *file, int line, uint64_t us_elapsed_absolute,
505 + uint64_t us_elapsed_region, const char *category,
506 + const struct repository *repo, const char *key,
507 + const char *value)
508 +{
509 + const char *event_name = "data";
510 + struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
511 + if (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {
512 + struct json_writer jw = JSON_WRITER_INIT;
513 + double t_abs = (double)us_elapsed_absolute / 1000000.0;
514 + double t_rel = (double)us_elapsed_region / 1000000.0;
515 +
516 + jw_object_begin(&jw, 0);
517 + event_fmt_prepare(event_name, file, line, repo, &jw);
518 + jw_object_double(&jw, "t_abs", 6, t_abs);
519 + jw_object_double(&jw, "t_rel", 6, t_rel);
520 + jw_object_intmax(&jw, "nesting", ctx->nr_open_regions);
521 + jw_object_string(&jw, "category", category);
522 + jw_object_string(&jw, "key", key);
523 + jw_object_string(&jw, "value", value);
524 + jw_end(&jw);
525 +
526 + tr2_dst_write_line(&tr2dst_event, &jw.json);
527 + jw_release(&jw);
528 + }
529 +}
530 +
531 +static void fn_data_json_fl(const char *file, int line,
532 + uint64_t us_elapsed_absolute,
533 + uint64_t us_elapsed_region, const char *category,
534 + const struct repository *repo, const char *key,
535 + const struct json_writer *value)
536 +{
537 + const char *event_name = "data_json";
538 + struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
539 + if (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {
540 + struct json_writer jw = JSON_WRITER_INIT;
541 + double t_abs = (double)us_elapsed_absolute / 1000000.0;
542 + double t_rel = (double)us_elapsed_region / 1000000.0;
543 +
544 + jw_object_begin(&jw, 0);
545 + event_fmt_prepare(event_name, file, line, repo, &jw);
546 + jw_object_double(&jw, "t_abs", 6, t_abs);
547 + jw_object_double(&jw, "t_rel", 6, t_rel);
548 + jw_object_intmax(&jw, "nesting", ctx->nr_open_regions);
549 + jw_object_string(&jw, "category", category);
550 + jw_object_string(&jw, "key", key);
551 + jw_object_sub_jw(&jw, "value", value);
552 + jw_end(&jw);
553 +
554 + tr2_dst_write_line(&tr2dst_event, &jw.json);
555 + jw_release(&jw);
556 + }
557 +}
558 +
559 +struct tr2_tgt tr2_tgt_event = {
560 + &tr2dst_event,
561 +
562 + fn_init,
563 + fn_term,
564 +
565 + fn_version_fl,
566 + fn_start_fl,
567 + fn_exit_fl,
568 + fn_signal,
569 + fn_atexit,
570 + fn_error_va_fl,
571 + fn_command_path_fl,
572 + fn_command_name_fl,
573 + fn_command_mode_fl,
574 + fn_alias_fl,
575 + fn_child_start_fl,
576 + fn_child_exit_fl,
577 + fn_thread_start_fl,
578 + fn_thread_exit_fl,
579 + fn_exec_fl,
580 + fn_exec_result_fl,
581 + fn_param_fl,
582 + fn_repo_fl,
583 + fn_region_enter_printf_va_fl,
584 + fn_region_leave_printf_va_fl,
585 + fn_data_fl,
586 + fn_data_json_fl,
587 + NULL, /* printf */
588 +};
trace2/tr2_tgt_normal.c new
+323
@@ -0,0 +1,323 @@
1 +#include "cache.h"
2 +#include "config.h"
3 +#include "run-command.h"
4 +#include "quote.h"
5 +#include "version.h"
6 +#include "trace2/tr2_dst.h"
7 +#include "trace2/tr2_tbuf.h"
8 +#include "trace2/tr2_tgt.h"
9 +#include "trace2/tr2_tls.h"
10 +
11 +static struct tr2_dst tr2dst_normal = { "GIT_TR2", 0, 0, 0 };
12 +
13 +/*
14 + * Set this environment variable to true to omit the "<time> <file>:<line>"
15 + * fields from each line written to the builtin normal target.
16 + *
17 + * Unit tests may want to use this to help with testing.
18 + */
19 +#define TR2_ENVVAR_NORMAL_BRIEF "GIT_TR2_BRIEF"
20 +static int tr2env_normal_brief;
21 +
22 +#define TR2FMT_NORMAL_FL_WIDTH (50)
23 +
24 +static int fn_init(void)
25 +{
26 + int want = tr2_dst_trace_want(&tr2dst_normal);
27 + int want_brief;
28 + char *brief;
29 +
30 + if (!want)
31 + return want;
32 +
33 + brief = getenv(TR2_ENVVAR_NORMAL_BRIEF);
34 + if (brief && *brief &&
35 + ((want_brief = git_parse_maybe_bool(brief)) != -1))
36 + tr2env_normal_brief = want_brief;
37 +
38 + return want;
39 +}
40 +
41 +static void fn_term(void)
42 +{
43 + tr2_dst_trace_disable(&tr2dst_normal);
44 +}
45 +
46 +static void normal_fmt_prepare(const char *file, int line, struct strbuf *buf)
47 +{
48 + strbuf_setlen(buf, 0);
49 +
50 + if (!tr2env_normal_brief) {
51 + struct tr2_tbuf tb_now;
52 +
53 + tr2_tbuf_local_time(&tb_now);
54 + strbuf_addstr(buf, tb_now.buf);
55 + strbuf_addch(buf, ' ');
56 +
57 + if (file && *file)
58 + strbuf_addf(buf, "%s:%d ", file, line);
59 + while (buf->len < TR2FMT_NORMAL_FL_WIDTH)
60 + strbuf_addch(buf, ' ');
61 + }
62 +}
63 +
64 +static void normal_io_write_fl(const char *file, int line,
65 + const struct strbuf *buf_payload)
66 +{
67 + struct strbuf buf_line = STRBUF_INIT;
68 +
69 + normal_fmt_prepare(file, line, &buf_line);
70 + strbuf_addbuf(&buf_line, buf_payload);
71 + tr2_dst_write_line(&tr2dst_normal, &buf_line);
72 + strbuf_release(&buf_line);
73 +}
74 +
75 +static void fn_version_fl(const char *file, int line)
76 +{
77 + struct strbuf buf_payload = STRBUF_INIT;
78 +
79 + strbuf_addf(&buf_payload, "version %s", git_version_string);
80 + normal_io_write_fl(file, line, &buf_payload);
81 + strbuf_release(&buf_payload);
82 +}
83 +
84 +static void fn_start_fl(const char *file, int line, const char **argv)
85 +{
86 + struct strbuf buf_payload = STRBUF_INIT;
87 +
88 + strbuf_addstr(&buf_payload, "start ");
89 + sq_quote_argv_pretty(&buf_payload, argv);
90 + normal_io_write_fl(file, line, &buf_payload);
91 + strbuf_release(&buf_payload);
92 +}
93 +
94 +static void fn_exit_fl(const char *file, int line, uint64_t us_elapsed_absolute,
95 + int code)
96 +{
97 + struct strbuf buf_payload = STRBUF_INIT;
98 + double elapsed = (double)us_elapsed_absolute / 1000000.0;
99 +
100 + strbuf_addf(&buf_payload, "exit elapsed:%.6f code:%d", elapsed, code);
101 + normal_io_write_fl(file, line, &buf_payload);
102 + strbuf_release(&buf_payload);
103 +}
104 +
105 +static void fn_signal(uint64_t us_elapsed_absolute, int signo)
106 +{
107 + struct strbuf buf_payload = STRBUF_INIT;
108 + double elapsed = (double)us_elapsed_absolute / 1000000.0;
109 +
110 + strbuf_addf(&buf_payload, "signal elapsed:%.6f code:%d", elapsed,
111 + signo);
112 + normal_io_write_fl(__FILE__, __LINE__, &buf_payload);
113 + strbuf_release(&buf_payload);
114 +}
115 +
116 +static void fn_atexit(uint64_t us_elapsed_absolute, int code)
117 +{
118 + struct strbuf buf_payload = STRBUF_INIT;
119 + double elapsed = (double)us_elapsed_absolute / 1000000.0;
120 +
121 + strbuf_addf(&buf_payload, "atexit elapsed:%.6f code:%d", elapsed, code);
122 + normal_io_write_fl(__FILE__, __LINE__, &buf_payload);
123 + strbuf_release(&buf_payload);
124 +}
125 +
126 +static void maybe_append_string_va(struct strbuf *buf, const char *fmt,
127 + va_list ap)
128 +{
129 + if (fmt && *fmt && ap) {
130 + va_list copy_ap;
131 +
132 + va_copy(copy_ap, ap);
133 + strbuf_vaddf(buf, fmt, copy_ap);
134 + va_end(copy_ap);
135 + return;
136 + }
137 +
138 + if (fmt && *fmt) {
139 + strbuf_addstr(buf, fmt);
140 + return;
141 + }
142 +}
143 +
144 +static void fn_error_va_fl(const char *file, int line, const char *fmt,
145 + va_list ap)
146 +{
147 + struct strbuf buf_payload = STRBUF_INIT;
148 +
149 + strbuf_addstr(&buf_payload, "error ");
150 + maybe_append_string_va(&buf_payload, fmt, ap);
151 + normal_io_write_fl(file, line, &buf_payload);
152 + strbuf_release(&buf_payload);
153 +}
154 +
155 +static void fn_command_path_fl(const char *file, int line, const char *pathname)
156 +{
157 + struct strbuf buf_payload = STRBUF_INIT;
158 +
159 + strbuf_addf(&buf_payload, "cmd_path %s", pathname);
160 + normal_io_write_fl(file, line, &buf_payload);
161 + strbuf_release(&buf_payload);
162 +}
163 +
164 +static void fn_command_name_fl(const char *file, int line, const char *name,
165 + const char *hierarchy)
166 +{
167 + struct strbuf buf_payload = STRBUF_INIT;
168 +
169 + strbuf_addf(&buf_payload, "cmd_name %s", name);
170 + if (hierarchy && *hierarchy)
171 + strbuf_addf(&buf_payload, " (%s)", hierarchy);
172 + normal_io_write_fl(file, line, &buf_payload);
173 + strbuf_release(&buf_payload);
174 +}
175 +
176 +static void fn_command_mode_fl(const char *file, int line, const char *mode)
177 +{
178 + struct strbuf buf_payload = STRBUF_INIT;
179 +
180 + strbuf_addf(&buf_payload, "cmd_mode %s", mode);
181 + normal_io_write_fl(file, line, &buf_payload);
182 + strbuf_release(&buf_payload);
183 +}
184 +
185 +static void fn_alias_fl(const char *file, int line, const char *alias,
186 + const char **argv)
187 +{
188 + struct strbuf buf_payload = STRBUF_INIT;
189 +
190 + strbuf_addf(&buf_payload, "alias %s ->", alias);
191 + sq_quote_argv_pretty(&buf_payload, argv);
192 + normal_io_write_fl(file, line, &buf_payload);
193 + strbuf_release(&buf_payload);
194 +}
195 +
196 +static void fn_child_start_fl(const char *file, int line,
197 + uint64_t us_elapsed_absolute,
198 + const struct child_process *cmd)
199 +{
200 + struct strbuf buf_payload = STRBUF_INIT;
201 +
202 + strbuf_addf(&buf_payload, "child_start[%d] ", cmd->trace2_child_id);
203 +
204 + if (cmd->dir) {
205 + strbuf_addstr(&buf_payload, " cd");
206 + sq_quote_buf_pretty(&buf_payload, cmd->dir);
207 + strbuf_addstr(&buf_payload, "; ");
208 + }
209 +
210 + /*
211 + * TODO if (cmd->env) { Consider dumping changes to environment. }
212 + * See trace_add_env() in run-command.c as used by original trace.c
213 + */
214 +
215 + if (cmd->git_cmd)
216 + strbuf_addstr(&buf_payload, "git");
217 + sq_quote_argv_pretty(&buf_payload, cmd->argv);
218 +
219 + normal_io_write_fl(file, line, &buf_payload);
220 + strbuf_release(&buf_payload);
221 +}
222 +
223 +static void fn_child_exit_fl(const char *file, int line,
224 + uint64_t us_elapsed_absolute, int cid, int pid,
225 + int code, uint64_t us_elapsed_child)
226 +{
227 + struct strbuf buf_payload = STRBUF_INIT;
228 + double elapsed = (double)us_elapsed_child / 1000000.0;
229 +
230 + strbuf_addf(&buf_payload, "child_exit[%d] pid:%d code:%d elapsed:%.6f",
231 + cid, pid, code, elapsed);
232 + normal_io_write_fl(file, line, &buf_payload);
233 + strbuf_release(&buf_payload);
234 +}
235 +
236 +static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,
237 + int exec_id, const char *exe, const char **argv)
238 +{
239 + struct strbuf buf_payload = STRBUF_INIT;
240 +
241 + strbuf_addf(&buf_payload, "exec[%d] ", exec_id);
242 + if (exe)
243 + strbuf_addstr(&buf_payload, exe);
244 + sq_quote_argv_pretty(&buf_payload, argv);
245 + normal_io_write_fl(file, line, &buf_payload);
246 + strbuf_release(&buf_payload);
247 +}
248 +
249 +static void fn_exec_result_fl(const char *file, int line,
250 + uint64_t us_elapsed_absolute, int exec_id,
251 + int code)
252 +{
253 + struct strbuf buf_payload = STRBUF_INIT;
254 +
255 + strbuf_addf(&buf_payload, "exec_result[%d] code:%d", exec_id, code);
256 + if (code > 0)
257 + strbuf_addf(&buf_payload, " err:%s", strerror(code));
258 + normal_io_write_fl(file, line, &buf_payload);
259 + strbuf_release(&buf_payload);
260 +}
261 +
262 +static void fn_param_fl(const char *file, int line, const char *param,
263 + const char *value)
264 +{
265 + struct strbuf buf_payload = STRBUF_INIT;
266 +
267 + strbuf_addf(&buf_payload, "def_param %s=%s", param, value);
268 + normal_io_write_fl(file, line, &buf_payload);
269 + strbuf_release(&buf_payload);
270 +}
271 +
272 +static void fn_repo_fl(const char *file, int line,
273 + const struct repository *repo)
274 +{
275 + struct strbuf buf_payload = STRBUF_INIT;
276 +
277 + strbuf_addstr(&buf_payload, "worktree ");
278 + sq_quote_buf_pretty(&buf_payload, repo->worktree);
279 + normal_io_write_fl(file, line, &buf_payload);
280 + strbuf_release(&buf_payload);
281 +}
282 +
283 +static void fn_printf_va_fl(const char *file, int line,
284 + uint64_t us_elapsed_absolute, const char *fmt,
285 + va_list ap)
286 +{
287 + struct strbuf buf_payload = STRBUF_INIT;
288 +
289 + maybe_append_string_va(&buf_payload, fmt, ap);
290 + normal_io_write_fl(file, line, &buf_payload);
291 + strbuf_release(&buf_payload);
292 +}
293 +
294 +struct tr2_tgt tr2_tgt_normal = {
295 + &tr2dst_normal,
296 +
297 + fn_init,
298 + fn_term,
299 +
300 + fn_version_fl,
301 + fn_start_fl,
302 + fn_exit_fl,
303 + fn_signal,
304 + fn_atexit,
305 + fn_error_va_fl,
306 + fn_command_path_fl,
307 + fn_command_name_fl,
308 + fn_command_mode_fl,
309 + fn_alias_fl,
310 + fn_child_start_fl,
311 + fn_child_exit_fl,
312 + NULL, /* thread_start */
313 + NULL, /* thread_exit */
314 + fn_exec_fl,
315 + fn_exec_result_fl,
316 + fn_param_fl,
317 + fn_repo_fl,
318 + NULL, /* region_enter */
319 + NULL, /* region_leave */
320 + NULL, /* data */
321 + NULL, /* data_json */
322 + fn_printf_va_fl,
323 +};
trace2/tr2_tgt_perf.c new
+534
@@ -0,0 +1,534 @@
1 +#include "cache.h"
2 +#include "config.h"
3 +#include "run-command.h"
4 +#include "quote.h"
5 +#include "version.h"
6 +#include "json-writer.h"
7 +#include "trace2/tr2_dst.h"
8 +#include "trace2/tr2_sid.h"
9 +#include "trace2/tr2_tbuf.h"
10 +#include "trace2/tr2_tgt.h"
11 +#include "trace2/tr2_tls.h"
12 +
13 +static struct tr2_dst tr2dst_perf = { "GIT_TR2_PERF", 0, 0, 0 };
14 +
15 +/*
16 + * Set this environment variable to true to omit the "<time> <file>:<line>"
17 + * fields from each line written to the builtin performance target.
18 + *
19 + * Unit tests may want to use this to help with testing.
20 + */
21 +#define TR2_ENVVAR_PERF_BRIEF "GIT_TR2_PERF_BRIEF"
22 +static int tr2env_perf_brief;
23 +
24 +#define TR2FMT_PERF_FL_WIDTH (50)
25 +#define TR2FMT_PERF_MAX_EVENT_NAME (12)
26 +#define TR2FMT_PERF_REPO_WIDTH (4)
27 +#define TR2FMT_PERF_CATEGORY_WIDTH (10)
28 +
29 +#define TR2_DOTS_BUFFER_SIZE (100)
30 +#define TR2_INDENT (2)
31 +#define TR2_INDENT_LENGTH(ctx) (((ctx)->nr_open_regions - 1) * TR2_INDENT)
32 +
33 +static struct strbuf dots = STRBUF_INIT;
34 +
35 +static int fn_init(void)
36 +{
37 + int want = tr2_dst_trace_want(&tr2dst_perf);
38 + int want_brief;
39 + char *brief;
40 +
41 + if (!want)
42 + return want;
43 +
44 + strbuf_addchars(&dots, '.', TR2_DOTS_BUFFER_SIZE);
45 +
46 + brief = getenv(TR2_ENVVAR_PERF_BRIEF);
47 + if (brief && *brief &&
48 + ((want_brief = git_parse_maybe_bool(brief)) != -1))
49 + tr2env_perf_brief = want_brief;
50 +
51 + return want;
52 +}
53 +
54 +static void fn_term(void)
55 +{
56 + tr2_dst_trace_disable(&tr2dst_perf);
57 +
58 + strbuf_release(&dots);
59 +}
60 +
61 +/*
62 + * Format trace line prefix in human-readable classic format for
63 + * the performance target:
64 + * "[<time> [<file>:<line>] <bar>] <nr_parents> <bar>
65 + * <thread_name> <bar> <event_name> <bar> [<repo>] <bar>
66 + * [<elapsed_absolute>] [<elapsed_relative>] <bar>
67 + * [<category>] <bar> [<dots>] "
68 + */
69 +static void perf_fmt_prepare(const char *event_name,
70 + struct tr2tls_thread_ctx *ctx, const char *file,
71 + int line, const struct repository *repo,
72 + uint64_t *p_us_elapsed_absolute,
73 + uint64_t *p_us_elapsed_relative,
74 + const char *category, struct strbuf *buf)
75 +{
76 + int len;
77 +
78 + strbuf_setlen(buf, 0);
79 +
80 + if (!tr2env_perf_brief) {
81 + struct tr2_tbuf tb_now;
82 +
83 + tr2_tbuf_local_time(&tb_now);
84 + strbuf_addstr(buf, tb_now.buf);
85 + strbuf_addch(buf, ' ');
86 +
87 + if (file && *file)
88 + strbuf_addf(buf, "%s:%d ", file, line);
89 + while (buf->len < TR2FMT_PERF_FL_WIDTH)
90 + strbuf_addch(buf, ' ');
91 +
92 + strbuf_addstr(buf, "| ");
93 + }
94 +
95 + strbuf_addf(buf, "d%d | ", tr2_sid_depth());
96 + strbuf_addf(buf, "%-*s | %-*s | ", TR2_MAX_THREAD_NAME,
97 + ctx->thread_name.buf, TR2FMT_PERF_MAX_EVENT_NAME,
98 + event_name);
99 +
100 + len = buf->len + TR2FMT_PERF_REPO_WIDTH;
101 + if (repo)
102 + strbuf_addf(buf, "r%d ", repo->trace2_repo_id);
103 + while (buf->len < len)
104 + strbuf_addch(buf, ' ');
105 + strbuf_addstr(buf, "| ");
106 +
107 + if (p_us_elapsed_absolute)
108 + strbuf_addf(buf, "%9.6f | ",
109 + ((double)(*p_us_elapsed_absolute)) / 1000000.0);
110 + else
111 + strbuf_addf(buf, "%9s | ", " ");
112 +
113 + if (p_us_elapsed_relative)
114 + strbuf_addf(buf, "%9.6f | ",
115 + ((double)(*p_us_elapsed_relative)) / 1000000.0);
116 + else
117 + strbuf_addf(buf, "%9s | ", " ");
118 +
119 + strbuf_addf(buf, "%-*s | ", TR2FMT_PERF_CATEGORY_WIDTH,
120 + (category ? category : ""));
121 +
122 + if (ctx->nr_open_regions > 0) {
123 + int len_indent = TR2_INDENT_LENGTH(ctx);
124 + while (len_indent > dots.len) {
125 + strbuf_addbuf(buf, &dots);
126 + len_indent -= dots.len;
127 + }
128 + strbuf_addf(buf, "%.*s", len_indent, dots.buf);
129 + }
130 +}
131 +
132 +static void perf_io_write_fl(const char *file, int line, const char *event_name,
133 + const struct repository *repo,
134 + uint64_t *p_us_elapsed_absolute,
135 + uint64_t *p_us_elapsed_relative,
136 + const char *category,
137 + const struct strbuf *buf_payload)
138 +{
139 + struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
140 + struct strbuf buf_line = STRBUF_INIT;
141 +
142 + perf_fmt_prepare(event_name, ctx, file, line, repo,
143 + p_us_elapsed_absolute, p_us_elapsed_relative, category,
144 + &buf_line);
145 + strbuf_addbuf(&buf_line, buf_payload);
146 + tr2_dst_write_line(&tr2dst_perf, &buf_line);
147 + strbuf_release(&buf_line);
148 +}
149 +
150 +static void fn_version_fl(const char *file, int line)
151 +{
152 + const char *event_name = "version";
153 + struct strbuf buf_payload = STRBUF_INIT;
154 +
155 + strbuf_addstr(&buf_payload, git_version_string);
156 +
157 + perf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,
158 + &buf_payload);
159 + strbuf_release(&buf_payload);
160 +}
161 +
162 +static void fn_start_fl(const char *file, int line, const char **argv)
163 +{
164 + const char *event_name = "start";
165 + struct strbuf buf_payload = STRBUF_INIT;
166 +
167 + sq_quote_argv_pretty(&buf_payload, argv);
168 +
169 + perf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,
170 + &buf_payload);
171 + strbuf_release(&buf_payload);
172 +}
173 +
174 +static void fn_exit_fl(const char *file, int line, uint64_t us_elapsed_absolute,
175 + int code)
176 +{
177 + const char *event_name = "exit";
178 + struct strbuf buf_payload = STRBUF_INIT;
179 +
180 + strbuf_addf(&buf_payload, "code:%d", code);
181 +
182 + perf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,
183 + NULL, NULL, &buf_payload);
184 + strbuf_release(&buf_payload);
185 +}
186 +
187 +static void fn_signal(uint64_t us_elapsed_absolute, int signo)
188 +{
189 + const char *event_name = "signal";
190 + struct strbuf buf_payload = STRBUF_INIT;
191 +
192 + strbuf_addf(&buf_payload, "signo:%d", signo);
193 +
194 + perf_io_write_fl(__FILE__, __LINE__, event_name, NULL,
195 + &us_elapsed_absolute, NULL, NULL, &buf_payload);
196 + strbuf_release(&buf_payload);
197 +}
198 +
199 +static void fn_atexit(uint64_t us_elapsed_absolute, int code)
200 +{
201 + const char *event_name = "atexit";
202 + struct strbuf buf_payload = STRBUF_INIT;
203 +
204 + strbuf_addf(&buf_payload, "code:%d", code);
205 +
206 + perf_io_write_fl(__FILE__, __LINE__, event_name, NULL,
207 + &us_elapsed_absolute, NULL, NULL, &buf_payload);
208 + strbuf_release(&buf_payload);
209 +}
210 +
211 +static void maybe_append_string_va(struct strbuf *buf, const char *fmt,
212 + va_list ap)
213 +{
214 + if (fmt && *fmt && ap) {
215 + va_list copy_ap;
216 +
217 + va_copy(copy_ap, ap);
218 + strbuf_vaddf(buf, fmt, copy_ap);
219 + va_end(copy_ap);
220 + return;
221 + }
222 +
223 + if (fmt && *fmt) {
224 + strbuf_addstr(buf, fmt);
225 + return;
226 + }
227 +}
228 +
229 +static void fn_error_va_fl(const char *file, int line, const char *fmt,
230 + va_list ap)
231 +{
232 + const char *event_name = "error";
233 + struct strbuf buf_payload = STRBUF_INIT;
234 +
235 + maybe_append_string_va(&buf_payload, fmt, ap);
236 +
237 + perf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,
238 + &buf_payload);
239 + strbuf_release(&buf_payload);
240 +}
241 +
242 +static void fn_command_path_fl(const char *file, int line, const char *pathname)
243 +{
244 + const char *event_name = "cmd_path";
245 + struct strbuf buf_payload = STRBUF_INIT;
246 +
247 + strbuf_addstr(&buf_payload, pathname);
248 +
249 + perf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,
250 + &buf_payload);
251 + strbuf_release(&buf_payload);
252 +}
253 +
254 +static void fn_command_name_fl(const char *file, int line, const char *name,
255 + const char *hierarchy)
256 +{
257 + const char *event_name = "cmd_name";
258 + struct strbuf buf_payload = STRBUF_INIT;
259 +
260 + strbuf_addstr(&buf_payload, name);
261 + if (hierarchy && *hierarchy)
262 + strbuf_addf(&buf_payload, " (%s)", hierarchy);
263 +
264 + perf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,
265 + &buf_payload);
266 + strbuf_release(&buf_payload);
267 +}
268 +
269 +static void fn_command_mode_fl(const char *file, int line, const char *mode)
270 +{
271 + const char *event_name = "cmd_mode";
272 + struct strbuf buf_payload = STRBUF_INIT;
273 +
274 + strbuf_addstr(&buf_payload, mode);
275 +
276 + perf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,
277 + &buf_payload);
278 + strbuf_release(&buf_payload);
279 +}
280 +
281 +static void fn_alias_fl(const char *file, int line, const char *alias,
282 + const char **argv)
283 +{
284 + const char *event_name = "alias";
285 + struct strbuf buf_payload = STRBUF_INIT;
286 +
287 + strbuf_addf(&buf_payload, "alias:%s argv:", alias);
288 + sq_quote_argv_pretty(&buf_payload, argv);
289 +
290 + perf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,
291 + &buf_payload);
292 + strbuf_release(&buf_payload);
293 +}
294 +
295 +static void fn_child_start_fl(const char *file, int line,
296 + uint64_t us_elapsed_absolute,
297 + const struct child_process *cmd)
298 +{
299 + const char *event_name = "child_start";
300 + struct strbuf buf_payload = STRBUF_INIT;
301 +
302 + if (cmd->trace2_hook_name) {
303 + strbuf_addf(&buf_payload, "[ch%d] class:hook hook:%s",
304 + cmd->trace2_child_id, cmd->trace2_hook_name);
305 + } else {
306 + const char *child_class =
307 + cmd->trace2_child_class ? cmd->trace2_child_class : "?";
308 + strbuf_addf(&buf_payload, "[ch%d] class:%s",
309 + cmd->trace2_child_id, child_class);
310 + }
311 +
312 + if (cmd->dir) {
313 + strbuf_addstr(&buf_payload, " cd:");
314 + sq_quote_buf_pretty(&buf_payload, cmd->dir);
315 + }
316 +
317 + strbuf_addstr(&buf_payload, " argv:");
318 + if (cmd->git_cmd)
319 + strbuf_addstr(&buf_payload, " git");
320 + sq_quote_argv_pretty(&buf_payload, cmd->argv);
321 +
322 + perf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,
323 + NULL, NULL, &buf_payload);
324 + strbuf_release(&buf_payload);
325 +}
326 +
327 +static void fn_child_exit_fl(const char *file, int line,
328 + uint64_t us_elapsed_absolute, int cid, int pid,
329 + int code, uint64_t us_elapsed_child)
330 +{
331 + const char *event_name = "child_exit";
332 + struct strbuf buf_payload = STRBUF_INIT;
333 +
334 + strbuf_addf(&buf_payload, "[ch%d] pid:%d code:%d", cid, pid, code);
335 +
336 + perf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,
337 + &us_elapsed_child, NULL, &buf_payload);
338 + strbuf_release(&buf_payload);
339 +}
340 +
341 +static void fn_thread_start_fl(const char *file, int line,
342 + uint64_t us_elapsed_absolute)
343 +{
344 + const char *event_name = "thread_start";
345 + struct strbuf buf_payload = STRBUF_INIT;
346 +
347 + perf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,
348 + NULL, NULL, &buf_payload);
349 + strbuf_release(&buf_payload);
350 +}
351 +
352 +static void fn_thread_exit_fl(const char *file, int line,
353 + uint64_t us_elapsed_absolute,
354 + uint64_t us_elapsed_thread)
355 +{
356 + const char *event_name = "thread_exit";
357 + struct strbuf buf_payload = STRBUF_INIT;
358 +
359 + perf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,
360 + &us_elapsed_thread, NULL, &buf_payload);
361 + strbuf_release(&buf_payload);
362 +}
363 +
364 +static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,
365 + int exec_id, const char *exe, const char **argv)
366 +{
367 + const char *event_name = "exec";
368 + struct strbuf buf_payload = STRBUF_INIT;
369 +
370 + strbuf_addf(&buf_payload, "id:%d ", exec_id);
371 + strbuf_addstr(&buf_payload, "argv:");
372 + if (exe)
373 + strbuf_addf(&buf_payload, " %s", exe);
374 + sq_quote_argv_pretty(&buf_payload, argv);
375 +
376 + perf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,
377 + NULL, NULL, &buf_payload);
378 + strbuf_release(&buf_payload);
379 +}
380 +
381 +static void fn_exec_result_fl(const char *file, int line,
382 + uint64_t us_elapsed_absolute, int exec_id,
383 + int code)
384 +{
385 + const char *event_name = "exec_result";
386 + struct strbuf buf_payload = STRBUF_INIT;
387 +
388 + strbuf_addf(&buf_payload, "id:%d code:%d", exec_id, code);
389 + if (code > 0)
390 + strbuf_addf(&buf_payload, " err:%s", strerror(code));
391 +
392 + perf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,
393 + NULL, NULL, &buf_payload);
394 + strbuf_release(&buf_payload);
395 +}
396 +
397 +static void fn_param_fl(const char *file, int line, const char *param,
398 + const char *value)
399 +{
400 + const char *event_name = "def_param";
401 + struct strbuf buf_payload = STRBUF_INIT;
402 +
403 + strbuf_addf(&buf_payload, "%s:%s", param, value);
404 +
405 + perf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,
406 + &buf_payload);
407 + strbuf_release(&buf_payload);
408 +}
409 +
410 +static void fn_repo_fl(const char *file, int line,
411 + const struct repository *repo)
412 +{
413 + const char *event_name = "def_repo";
414 + struct strbuf buf_payload = STRBUF_INIT;
415 +
416 + strbuf_addstr(&buf_payload, "worktree:");
417 + sq_quote_buf_pretty(&buf_payload, repo->worktree);
418 +
419 + perf_io_write_fl(file, line, event_name, repo, NULL, NULL, NULL,
420 + &buf_payload);
421 + strbuf_release(&buf_payload);
422 +}
423 +
424 +static void fn_region_enter_printf_va_fl(const char *file, int line,
425 + uint64_t us_elapsed_absolute,
426 + const char *category,
427 + const char *label,
428 + const struct repository *repo,
429 + const char *fmt, va_list ap)
430 +{
431 + const char *event_name = "region_enter";
432 + struct strbuf buf_payload = STRBUF_INIT;
433 +
434 + if (label)
435 + strbuf_addf(&buf_payload, "label:%s ", label);
436 + maybe_append_string_va(&buf_payload, fmt, ap);
437 +
438 + perf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,
439 + NULL, category, &buf_payload);
440 + strbuf_release(&buf_payload);
441 +}
442 +
443 +static void fn_region_leave_printf_va_fl(
444 + const char *file, int line, uint64_t us_elapsed_absolute,
445 + uint64_t us_elapsed_region, const char *category, const char *label,
446 + const struct repository *repo, const char *fmt, va_list ap)
447 +{
448 + const char *event_name = "region_leave";
449 + struct strbuf buf_payload = STRBUF_INIT;
450 +
451 + if (label)
452 + strbuf_addf(&buf_payload, "label:%s ", label);
453 + maybe_append_string_va(&buf_payload, fmt, ap);
454 +
455 + perf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,
456 + &us_elapsed_region, category, &buf_payload);
457 + strbuf_release(&buf_payload);
458 +}
459 +
460 +static void fn_data_fl(const char *file, int line, uint64_t us_elapsed_absolute,
461 + uint64_t us_elapsed_region, const char *category,
462 + const struct repository *repo, const char *key,
463 + const char *value)
464 +{
465 + const char *event_name = "data";
466 + struct strbuf buf_payload = STRBUF_INIT;
467 +
468 + strbuf_addf(&buf_payload, "%s:%s", key, value);
469 +
470 + perf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,
471 + &us_elapsed_region, category, &buf_payload);
472 + strbuf_release(&buf_payload);
473 +}
474 +
475 +static void fn_data_json_fl(const char *file, int line,
476 + uint64_t us_elapsed_absolute,
477 + uint64_t us_elapsed_region, const char *category,
478 + const struct repository *repo, const char *key,
479 + const struct json_writer *value)
480 +{
481 + const char *event_name = "data_json";
482 + struct strbuf buf_payload = STRBUF_INIT;
483 +
484 + strbuf_addf(&buf_payload, "%s:%s", key, value->json.buf);
485 +
486 + perf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,
487 + &us_elapsed_region, category, &buf_payload);
488 + strbuf_release(&buf_payload);
489 +}
490 +
491 +static void fn_printf_va_fl(const char *file, int line,
492 + uint64_t us_elapsed_absolute, const char *fmt,
493 + va_list ap)
494 +{
495 + const char *event_name = "printf";
496 + struct strbuf buf_payload = STRBUF_INIT;
497 +
498 + maybe_append_string_va(&buf_payload, fmt, ap);
499 +
500 + perf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,
501 + NULL, NULL, &buf_payload);
502 + strbuf_release(&buf_payload);
503 +}
504 +
505 +struct tr2_tgt tr2_tgt_perf = {
506 + &tr2dst_perf,
507 +
508 + fn_init,
509 + fn_term,
510 +
511 + fn_version_fl,
512 + fn_start_fl,
513 + fn_exit_fl,
514 + fn_signal,
515 + fn_atexit,
516 + fn_error_va_fl,
517 + fn_command_path_fl,
518 + fn_command_name_fl,
519 + fn_command_mode_fl,
520 + fn_alias_fl,
521 + fn_child_start_fl,
522 + fn_child_exit_fl,
523 + fn_thread_start_fl,
524 + fn_thread_exit_fl,
525 + fn_exec_fl,
526 + fn_exec_result_fl,
527 + fn_param_fl,
528 + fn_repo_fl,
529 + fn_region_enter_printf_va_fl,
530 + fn_region_leave_printf_va_fl,
531 + fn_data_fl,
532 + fn_data_json_fl,
533 + fn_printf_va_fl,
534 +};
trace2/tr2_tls.c new
+164
@@ -0,0 +1,164 @@
1 +#include "cache.h"
2 +#include "thread-utils.h"
3 +#include "trace2/tr2_tls.h"
4 +
5 +/*
6 + * Initialize size of the thread stack for nested regions.
7 + * This is used to store nested region start times. Note that
8 + * this stack is per-thread and not per-trace-key.
9 + */
10 +#define TR2_REGION_NESTING_INITIAL_SIZE (100)
11 +
12 +static struct tr2tls_thread_ctx *tr2tls_thread_main;
13 +static uint64_t tr2tls_us_start_main;
14 +
15 +static pthread_mutex_t tr2tls_mutex;
16 +static pthread_key_t tr2tls_key;
17 +
18 +static int tr2_next_thread_id; /* modify under lock */
19 +
20 +struct tr2tls_thread_ctx *tr2tls_create_self(const char *thread_name)
21 +{
22 + uint64_t us_now = getnanotime() / 1000;
23 + struct tr2tls_thread_ctx *ctx = xcalloc(1, sizeof(*ctx));
24 +
25 + /*
26 + * Implicitly "tr2tls_push_self()" to capture the thread's start
27 + * time in array_us_start[0]. For the main thread this gives us the
28 + * application run time.
29 + */
30 + ctx->alloc = TR2_REGION_NESTING_INITIAL_SIZE;
31 + ctx->array_us_start = (uint64_t *)xcalloc(ctx->alloc, sizeof(uint64_t));
32 + ctx->array_us_start[ctx->nr_open_regions++] = us_now;
33 +
34 + ctx->thread_id = tr2tls_locked_increment(&tr2_next_thread_id);
35 +
36 + strbuf_init(&ctx->thread_name, 0);
37 + if (ctx->thread_id)
38 + strbuf_addf(&ctx->thread_name, "th%02d:", ctx->thread_id);
39 + strbuf_addstr(&ctx->thread_name, thread_name);
40 + if (ctx->thread_name.len > TR2_MAX_THREAD_NAME)
41 + strbuf_setlen(&ctx->thread_name, TR2_MAX_THREAD_NAME);
42 +
43 + pthread_setspecific(tr2tls_key, ctx);
44 +
45 + return ctx;
46 +}
47 +
48 +struct tr2tls_thread_ctx *tr2tls_get_self(void)
49 +{
50 + struct tr2tls_thread_ctx *ctx = pthread_getspecific(tr2tls_key);
51 +
52 + /*
53 + * If the thread-proc did not call trace2_thread_start(), we won't
54 + * have any TLS data associated with the current thread. Fix it
55 + * here and silently continue.
56 + */
57 + if (!ctx)
58 + ctx = tr2tls_create_self("unknown");
59 +
60 + return ctx;
61 +}
62 +
63 +int tr2tls_is_main_thread(void)
64 +{
65 + struct tr2tls_thread_ctx *ctx = pthread_getspecific(tr2tls_key);
66 +
67 + return ctx == tr2tls_thread_main;
68 +}
69 +
70 +void tr2tls_unset_self(void)
71 +{
72 + struct tr2tls_thread_ctx *ctx;
73 +
74 + ctx = tr2tls_get_self();
75 +
76 + pthread_setspecific(tr2tls_key, NULL);
77 +
78 + free(ctx->array_us_start);
79 + free(ctx);
80 +}
81 +
82 +void tr2tls_push_self(uint64_t us_now)
83 +{
84 + struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
85 +
86 + ALLOC_GROW(ctx->array_us_start, ctx->nr_open_regions + 1, ctx->alloc);
87 + ctx->array_us_start[ctx->nr_open_regions++] = us_now;
88 +}
89 +
90 +void tr2tls_pop_self(void)
91 +{
92 + struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
93 +
94 + if (!ctx->nr_open_regions)
95 + BUG("no open regions in thread '%s'", ctx->thread_name.buf);
96 +
97 + ctx->nr_open_regions--;
98 +}
99 +
100 +void tr2tls_pop_unwind_self(void)
101 +{
102 + struct tr2tls_thread_ctx *ctx = tr2tls_get_self();
103 +
104 + while (ctx->nr_open_regions > 1)
105 + tr2tls_pop_self();
106 +}
107 +
108 +uint64_t tr2tls_region_elasped_self(uint64_t us)
109 +{
110 + struct tr2tls_thread_ctx *ctx;
111 + uint64_t us_start;
112 +
113 + ctx = tr2tls_get_self();
114 + if (!ctx->nr_open_regions)
115 + return 0;
116 +
117 + us_start = ctx->array_us_start[ctx->nr_open_regions - 1];
118 +
119 + return us - us_start;
120 +}
121 +
122 +uint64_t tr2tls_absolute_elapsed(uint64_t us)
123 +{
124 + if (!tr2tls_thread_main)
125 + return 0;
126 +
127 + return us - tr2tls_us_start_main;
128 +}
129 +
130 +void tr2tls_init(void)
131 +{
132 + pthread_key_create(&tr2tls_key, NULL);
133 + init_recursive_mutex(&tr2tls_mutex);
134 +
135 + tr2tls_thread_main = tr2tls_create_self("main");
136 + /*
137 + * Keep a copy of the absolute start time of the main thread
138 + * in a fixed variable since other threads need to access it.
139 + * This also eliminates the need to lock accesses to the main
140 + * thread's array (because of reallocs).
141 + */
142 + tr2tls_us_start_main = tr2tls_thread_main->array_us_start[0];
143 +}
144 +
145 +void tr2tls_release(void)
146 +{
147 + tr2tls_unset_self();
148 + tr2tls_thread_main = NULL;
149 +
150 + pthread_mutex_destroy(&tr2tls_mutex);
151 + pthread_key_delete(tr2tls_key);
152 +}
153 +
154 +int tr2tls_locked_increment(int *p)
155 +{
156 + int current_value;
157 +
158 + pthread_mutex_lock(&tr2tls_mutex);
159 + current_value = *p;
160 + *p = current_value + 1;
161 + pthread_mutex_unlock(&tr2tls_mutex);
162 +
163 + return current_value;
164 +}
trace2/tr2_tls.h new
+97
@@ -0,0 +1,97 @@
1 +#ifndef TR2_TLS_H
2 +#define TR2_TLS_H
3 +
4 +#include "strbuf.h"
5 +
6 +/*
7 + * Arbitry limit for thread names for column alignment.
8 + */
9 +#define TR2_MAX_THREAD_NAME (24)
10 +
11 +struct tr2tls_thread_ctx {
12 + struct strbuf thread_name;
13 + uint64_t *array_us_start;
14 + int alloc;
15 + int nr_open_regions; /* plays role of "nr" in ALLOC_GROW */
16 + int thread_id;
17 +};
18 +
19 +/*
20 + * Create TLS data for the current thread. This gives us a place to
21 + * put per-thread data, such as thread start time, function nesting
22 + * and a per-thread label for our messages.
23 + *
24 + * We assume the first thread is "main". Other threads are given
25 + * non-zero thread-ids to help distinguish messages from concurrent
26 + * threads.
27 + *
28 + * Truncate the thread name if necessary to help with column alignment
29 + * in printf-style messages.
30 + *
31 + * In this and all following functions the term "self" refers to the
32 + * current thread.
33 + */
34 +struct tr2tls_thread_ctx *tr2tls_create_self(const char *thread_name);
35 +
36 +/*
37 + * Get our TLS data.
38 + */
39 +struct tr2tls_thread_ctx *tr2tls_get_self(void);
40 +
41 +/*
42 + * return true if the current thread is the main thread.
43 + */
44 +int tr2tls_is_main_thread(void);
45 +
46 +/*
47 + * Free our TLS data.
48 + */
49 +void tr2tls_unset_self(void);
50 +
51 +/*
52 + * Begin a new nested region and remember the start time.
53 + */
54 +void tr2tls_push_self(uint64_t us_now);
55 +
56 +/*
57 + * End the innermost nested region.
58 + */
59 +void tr2tls_pop_self(void);
60 +
61 +/*
62 + * Pop any extra (above the first) open regions on the current
63 + * thread and discard. During a thread-exit, we should only
64 + * have region[0] that was pushed in trace2_thread_start() if
65 + * the thread exits normally.
66 + */
67 +void tr2tls_pop_unwind_self(void);
68 +
69 +/*
70 + * Compute the elapsed time since the innermost region in the
71 + * current thread started and the given time (usually now).
72 + */
73 +uint64_t tr2tls_region_elasped_self(uint64_t us);
74 +
75 +/*
76 + * Compute the elapsed time since the main thread started
77 + * and the given time (usually now). This is assumed to
78 + * be the absolute run time of the process.
79 + */
80 +uint64_t tr2tls_absolute_elapsed(uint64_t us);
81 +
82 +/*
83 + * Initialize the tr2 TLS system.
84 + */
85 +void tr2tls_init(void);
86 +
87 +/*
88 + * Free all tr2 TLS resources.
89 + */
90 +void tr2tls_release(void);
91 +
92 +/*
93 + * Protected increment of an integer.
94 + */
95 +int tr2tls_locked_increment(int *p);
96 +
97 +#endif /* TR2_TLS_H */
usage.c
+31
@@ -22,17 +22,48 @@ void vreportf(const char *prefix, const char *err, va_list params)
22 static NORETURN void usage_builtin(const char *err, va_list params)
23 {
24 vreportf("usage: ", err, params);
25 +
26 + /*
27 + * When we detect a usage error *before* the command dispatch in
28 + * cmd_main(), we don't know what verb to report. Force it to this
29 + * to facilitate post-processing.
30 + */
31 + trace2_cmd_name("_usage_");
32 +
33 + /*
34 + * Currently, the (err, params) are usually just the static usage
35 + * string which isn't very useful here. Usually, the call site
36 + * manually calls fprintf(stderr,...) with the actual detailed
37 + * syntax error before calling usage().
38 + *
39 + * TODO It would be nice to update the call sites to pass both
40 + * the static usage string and the detailed error message.
41 + */
42 +
43 exit(129);
44 }
45
46 static NORETURN void die_builtin(const char *err, va_list params)
47 {
48 + /*
49 + * We call this trace2 function first and expect it to va_copy 'params'
50 + * before using it (because an 'ap' can only be walked once).
51 + */
52 + trace2_cmd_error_va(err, params);
53 +
54 vreportf("fatal: ", err, params);
55 +
56 exit(128);
57 }
58
59 static void error_builtin(const char *err, va_list params)
60 {
61 + /*
62 + * We call this trace2 function first and expect it to va_copy 'params'
63 + * before using it (because an 'ap' can only be walked once).
64 + */
65 + trace2_cmd_error_va(err, params);
66 +
67 vreportf("error: ", err, params);
68 }
69