{"thread":{"id":"50293","subject":"[PATCH 00/14] Trace2 tracing facility","startedAt":"2019-01-22T21:22:16Z","lastAt":"2019-05-20T16:50:41Z","messageCount":154,"participants":["Jeff Hostetler via GitGitGadget","Derrick Stolee via GitGitGadget","Junio C Hamano","Derrick Stolee","SZEDER Gábor","Josh Steadmon","Jeff Hostetler","Ævar Arnfjörð Bjarmason","Johannes Schindelin"],"isPatch":true,"patchVersion":1,"patchTotal":14},"messages":[{"id":"367362","messageId":"pull.108.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":null,"subject":"[PATCH 00/14] Trace2 tracing facility","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-22T21:22:12Z","receivedAt":"2019-01-22T21:22:16Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"This patch series contains a greatly refactored version of my original\nTrace2 series [1] from August 2018.\n\nA new design doc in Documentation/technical/api-trace2.txt (in the first\ncommit) explains the relationship of Trace2 to the current tracing facility.\nCalls to the current tracing facility have not been changed, rather new\ntrace2 calls have been added so that both continue to work in parallel for\nthe time being.\n\n[1] https://public-inbox.org/git/pull.29.git.gitgitgadget@gmail.com/\n\nCc: gitster@pobox.comCc: peff@peff.netCc: jrnieder@gmail.com\n\nDerrick Stolee (1):\n  pack-objects: add trace2 regions\n\nJeff Hostetler (13):\n  trace2: Documentation/technical/api-trace2.txt\n  trace2: create new combined trace facility\n  trace2: collect platform-specific process information\n  trace2:data: add trace2 regions to wt-status\n  trace2:data: add editor/pager child classification\n  trace2:data: add trace2 sub-process classification\n  trace2:data: add trace2 transport child classification\n  trace2:data: add trace2 hook classification\n  trace2:data: add trace2 instrumentation to index read/write\n  trace2:data: add subverb to checkout command\n  trace2:data: add subverb to reset command\n  trace2:data: add subverb for rebase\n  trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh\n\n Documentation/technical/api-trace2.txt | 1158 ++++++++++++++++++++++++\n Makefile                               |   11 +\n builtin/am.c                           |    1 +\n builtin/checkout.c                     |    7 +\n builtin/pack-objects.c                 |   12 +-\n builtin/rebase.c                       |   19 +\n builtin/receive-pack.c                 |    4 +\n builtin/reset.c                        |    6 +\n builtin/submodule--helper.c            |   11 +-\n builtin/worktree.c                     |    1 +\n cache.h                                |    1 +\n common-main.c                          |   13 +-\n compat/mingw.c                         |   11 +-\n compat/mingw.h                         |    3 +-\n compat/win32/ancestry.c                |  102 +++\n config.c                               |    2 +\n config.mak.uname                       |    2 +\n connect.c                              |    3 +\n editor.c                               |    1 +\n exec-cmd.c                             |    2 +\n git-compat-util.h                      |    7 +\n git.c                                  |   65 ++\n pager.c                                |    1 +\n read-cache.c                           |   47 +-\n remote-curl.c                          |    7 +\n repository.c                           |    2 +\n repository.h                           |    3 +\n run-command.c                          |   63 +-\n run-command.h                          |   17 +-\n sequencer.c                            |    2 +\n sh-i18n--envsubst.c                    |    3 +\n sub-process.c                          |    1 +\n submodule.c                            |   11 +-\n t/helper/test-parse-options.c          |    3 +\n t/helper/test-tool.c                   |    4 +\n t/helper/test-tool.h                   |    1 +\n t/helper/test-trace2.c                 |  273 ++++++\n t/t0001-init.sh                        |    1 +\n t/t0210-trace2-normal.sh               |  135 +++\n t/t0210/scrub_normal.perl              |   48 +\n t/t0211-trace2-perf.sh                 |  153 ++++\n t/t0211/scrub_perf.perl                |   76 ++\n t/t0212-trace2-event.sh                |  237 +++++\n t/t0212/parse_events.perl              |  251 +++++\n trace2.c                               |  809 +++++++++++++++++\n trace2.h                               |  403 +++++++++\n trace2/tr2_cfg.c                       |   92 ++\n trace2/tr2_cfg.h                       |   19 +\n trace2/tr2_dst.c                       |   90 ++\n trace2/tr2_dst.h                       |   34 +\n trace2/tr2_sid.c                       |   67 ++\n trace2/tr2_sid.h                       |   18 +\n trace2/tr2_tbuf.c                      |   32 +\n trace2/tr2_tbuf.h                      |   23 +\n trace2/tr2_tgt.h                       |  126 +++\n trace2/tr2_tgt_event.c                 |  606 +++++++++++++\n trace2/tr2_tgt_normal.c                |  331 +++++++\n trace2/tr2_tgt_perf.c                  |  573 ++++++++++++\n trace2/tr2_tls.c                       |  164 ++++\n trace2/tr2_tls.h                       |   95 ++\n trace2/tr2_verb.c                      |   30 +\n trace2/tr2_verb.h                      |   24 +\n transport-helper.c                     |    2 +\n transport.c                            |    1 +\n usage.c                                |   31 +\n wt-status.c                            |   23 +-\n 66 files changed, 6353 insertions(+), 21 deletions(-)\n create mode 100644 Documentation/technical/api-trace2.txt\n create mode 100644 compat/win32/ancestry.c\n create mode 100644 t/helper/test-trace2.c\n create mode 100755 t/t0210-trace2-normal.sh\n create mode 100644 t/t0210/scrub_normal.perl\n create mode 100755 t/t0211-trace2-perf.sh\n create mode 100644 t/t0211/scrub_perf.perl\n create mode 100755 t/t0212-trace2-event.sh\n create mode 100644 t/t0212/parse_events.perl\n create mode 100644 trace2.c\n create mode 100644 trace2.h\n create mode 100644 trace2/tr2_cfg.c\n create mode 100644 trace2/tr2_cfg.h\n create mode 100644 trace2/tr2_dst.c\n create mode 100644 trace2/tr2_dst.h\n create mode 100644 trace2/tr2_sid.c\n create mode 100644 trace2/tr2_sid.h\n create mode 100644 trace2/tr2_tbuf.c\n create mode 100644 trace2/tr2_tbuf.h\n create mode 100644 trace2/tr2_tgt.h\n create mode 100644 trace2/tr2_tgt_event.c\n create mode 100644 trace2/tr2_tgt_normal.c\n create mode 100644 trace2/tr2_tgt_perf.c\n create mode 100644 trace2/tr2_tls.c\n create mode 100644 trace2/tr2_tls.h\n create mode 100644 trace2/tr2_verb.c\n create mode 100644 trace2/tr2_verb.h\n\n\nbase-commit: 77556354bb7ac50450e3b28999e3576969869068\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-108%2Fjeffhostetler%2Fcore-trace2-2019-v0-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-108/jeffhostetler/core-trace2-2019-v0-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/108\n-- \ngitgitgadget\n"},{"id":"367363","messageId":"978b9ebf0d263327e30ade48e1685077bc78cc81.1548192131.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.git.gitgitgadget@gmail.com","subject":"[PATCH 04/14] trace2:data: add trace2 regions to wt-status","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-22T21:22:15Z","receivedAt":"2019-01-22T21:22:19Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2_region_enter() and trace2_region_leave() calls around the\nvarious phases of a status scan.  This gives elapsed time for each\nphase in the GIT_TR2_PERF and GIT_TR2_EVENT trace target.\n\nAlso, these Trace2 calls now use s->repo rather than the_repository.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n wt-status.c | 23 +++++++++++++++++++++--\n 1 file changed, 21 insertions(+), 2 deletions(-)\n\ndiff --git a/wt-status.c b/wt-status.c\nindex 0fe3bcd4cd..7c5ba4e902 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -748,12 +748,23 @@ static int has_unmerged(struct wt_status *s)\n \n void wt_status_collect(struct wt_status *s)\n {\n+\ttrace2_region_enter(\"status\", \"worktrees\", s->repo);\n \twt_status_collect_changes_worktree(s);\n-\tif (s->is_initial)\n+\ttrace2_region_leave(\"status\", \"worktrees\", s->repo);\n+\n+\tif (s->is_initial) {\n+\t\ttrace2_region_enter(\"status\", \"initial\", s->repo);\n \t\twt_status_collect_changes_initial(s);\n-\telse\n+\t\ttrace2_region_leave(\"status\", \"initial\", s->repo);\n+\t} else {\n+\t\ttrace2_region_enter(\"status\", \"index\", s->repo);\n \t\twt_status_collect_changes_index(s);\n+\t\ttrace2_region_leave(\"status\", \"index\", s->repo);\n+\t}\n+\n+\ttrace2_region_enter(\"status\", \"untracked\", s->repo);\n \twt_status_collect_untracked(s);\n+\ttrace2_region_leave(\"status\", \"untracked\", s->repo);\n \n \twt_status_get_state(s->repo, &s->state, s->branch && !strcmp(s->branch, \"HEAD\"));\n \tif (s->state.merge_in_progress && !has_unmerged(s))\n@@ -2291,6 +2302,12 @@ static void wt_porcelain_v2_print(struct wt_status *s)\n \n void wt_status_print(struct wt_status *s)\n {\n+\ttrace2_data_intmax(\"status\", s->repo, \"count/changed\", s->change.nr);\n+\ttrace2_data_intmax(\"status\", s->repo, \"count/untracked\", s->untracked.nr);\n+\ttrace2_data_intmax(\"status\", s->repo, \"count/ignored\", s->ignored.nr);\n+\n+\ttrace2_region_enter(\"status\", \"print\", s->repo);\n+\n \tswitch (s->status_format) {\n \tcase STATUS_FORMAT_SHORT:\n \t\twt_shortstatus_print(s);\n@@ -2309,6 +2326,8 @@ void wt_status_print(struct wt_status *s)\n \t\twt_longstatus_print(s);\n \t\tbreak;\n \t}\n+\n+\ttrace2_region_leave(\"status\", \"print\", s->repo);\n }\n \n /**\n-- \ngitgitgadget\n\n"},{"id":"367364","messageId":"5baee8295ebbbe464e308c0f7aa1e84caa00ec6b.1548192131.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.git.gitgitgadget@gmail.com","subject":"[PATCH 03/14] trace2: collect platform-specific process information","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-22T21:22:15Z","receivedAt":"2019-01-22T21:22:20Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd optional platform-specific code to log information about\nthe current process.\n\nOn Windows, this includes whether git.exe is running under a\ndebugger and information about the ancestors of the process.\n\nThe purpose of this information is to help indicate if the\nprocess was launched interactively or in the background by\nan IDE, for example.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n common-main.c           |   1 +\n compat/win32/ancestry.c | 102 ++++++++++++++++++++++++++++++++++++++++\n config.mak.uname        |   2 +\n trace2.h                |  13 +++++\n 4 files changed, 118 insertions(+)\n create mode 100644 compat/win32/ancestry.c\n\ndiff --git a/common-main.c b/common-main.c\nindex 6dbdc4adf2..d484aec209 100644\n--- a/common-main.c\n+++ b/common-main.c\n@@ -37,6 +37,7 @@ int main(int argc, const char **argv)\n \n \ttrace2_initialize();\n \ttrace2_cmd_start(argv);\n+\ttrace2_collect_process_info();\n \n \tgit_resolve_executable_dir(argv[0]);\n \ndiff --git a/compat/win32/ancestry.c b/compat/win32/ancestry.c\nnew file mode 100644\nindex 0000000000..629d8214b9\n--- /dev/null\n+++ b/compat/win32/ancestry.c\n@@ -0,0 +1,102 @@\n+#include \"../../cache.h\"\n+#include \"../../json-writer.h\"\n+#include <Psapi.h>\n+#include <tlHelp32.h>\n+\n+/*\n+ * Find the process data for the given PID in the given snapshot\n+ * and update the PROCESSENTRY32 data.\n+ */\n+static int find_pid(DWORD pid, HANDLE hSnapshot, PROCESSENTRY32 *pe32)\n+{\n+\tpe32->dwSize = sizeof(PROCESSENTRY32);\n+\n+\tif (Process32First(hSnapshot, pe32)) {\n+\t\tdo {\n+\t\t\tif (pe32->th32ProcessID == pid)\n+\t\t\t\treturn 1;\n+\t\t} while (Process32Next(hSnapshot, pe32));\n+\t}\n+\treturn 0;\n+}\n+\n+/*\n+ * Accumulate JSON array:\n+ *     [\n+ *         exe-name-parent,\n+ *         exe-name-grand-parent,\n+ *         ...\n+ *     ]\n+ *\n+ * Note: we only report the filename of the process executable; the\n+ *       only way to get its full pathname is to use OpenProcess()\n+ *       and GetModuleFileNameEx() or QueryfullProcessImageName()\n+ *       and that seems rather expensive (on top of the cost of\n+ *       getting the snapshot).\n+ */\n+static void get_processes(struct json_writer *jw, HANDLE hSnapshot)\n+{\n+\tPROCESSENTRY32 pe32;\n+\tDWORD pid;\n+\n+\tpid = GetCurrentProcessId();\n+\n+\t/* We only want parent processes, so skip self. */\n+\tif (!find_pid(pid, hSnapshot, &pe32))\n+\t\treturn;\n+\tpid = pe32.th32ParentProcessID;\n+\n+\twhile (find_pid(pid, hSnapshot, &pe32)) {\n+\n+\t\tjw_array_string(jw, pe32.szExeFile);\n+\n+\t\tpid = pe32.th32ParentProcessID;\n+\t}\n+}\n+\n+/*\n+ * Emit JSON data for the current and parent processes.  Individual\n+ * trace2 targets can decide how to actually print it.\n+ */\n+static void get_ancestry(void)\n+{\n+\tHANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);\n+\n+\tif (hSnapshot != INVALID_HANDLE_VALUE) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\t\tjw_array_begin(&jw, 0);\n+\t\tget_processes(&jw, hSnapshot);\n+\t\tjw_end(&jw);\n+\n+\t\ttrace2_data_json(\"process\", the_repository,\n+\t\t\t\t \"windows/ancestry\", &jw);\n+\n+\t\tjw_release(&jw);\n+\t\tCloseHandle(hSnapshot);\n+\t}\n+}\n+\n+/*\n+ * Is a debugger attached to the current process?\n+ *\n+ * This will catch debug runs (where the debugger started the process).\n+ * This is the normal case.  Since this code is called during our startup,\n+ * it will not report instances where a debugger is attached dynamically\n+ * to a running git process, but that is relatively rare.\n+ */\n+static void get_is_being_debugged(void)\n+{\n+\tif (IsDebuggerPresent())\n+\t\ttrace2_data_intmax(\"process\", the_repository,\n+\t\t\t   \"windows/debugger_present\", 1);\n+}\n+\n+void trace2_collect_process_info(void)\n+{\n+\tif (!trace2_is_enabled())\n+\t\treturn;\n+\n+\tget_is_being_debugged();\n+\tget_ancestry();\n+}\ndiff --git a/config.mak.uname b/config.mak.uname\nindex e391431041..13d7c5410f 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -391,6 +391,7 @@ ifeq ($(uname_S),Windows)\n \tBASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE\n \tCOMPAT_OBJS = compat/msvc.o compat/winansi.o \\\n \t\tcompat/win32/pthread.o compat/win32/syslog.o \\\n+\t\tcompat/win32/ancestry.o \\\n \t\tcompat/win32/dirent.o\n \tCOMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\\\".exe\\\"\n \tBASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE\n@@ -528,6 +529,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))\n \tCOMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32\n \tCOMPAT_CFLAGS += -DSTRIP_EXTENSION=\\\".exe\\\"\n \tCOMPAT_OBJS += compat/mingw.o compat/winansi.o \\\n+\t\tcompat/win32/ancestry.o \\\n \t\tcompat/win32/path-utils.o \\\n \t\tcompat/win32/pthread.o compat/win32/syslog.o \\\n \t\tcompat/win32/dirent.o\ndiff --git a/trace2.h b/trace2.h\nindex 7ab9f355f3..a4a075d060 100644\n--- a/trace2.h\n+++ b/trace2.h\n@@ -387,4 +387,17 @@ __attribute__((format (printf, 1, 2)))\n void trace2_printf(const char *fmt, ...);\n #endif\n \n+/*\n+ * Optional platform-specific code to dump information about the\n+ * current and any parent process(es).  This is intended to allow\n+ * post-processors to know who spawned this git instance and anything\n+ * else the platform may be able to tell us about the current process.\n+ */\n+#if defined(GIT_WINDOWS_NATIVE)\n+void trace2_collect_process_info(void);\n+#else\n+#define trace2_collect_process_info() \\\n+\tdo {} while (0)\n+#endif\n+\n #endif /* TRACE2_H */\n-- \ngitgitgadget\n\n"},{"id":"367365","messageId":"49b6b9bd24e7ee2deaee83868e9d5e8c3d3c8956.1548192131.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.git.gitgitgadget@gmail.com","subject":"[PATCH 07/14] trace2:data: add trace2 transport child classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-22T21:22:18Z","receivedAt":"2019-01-22T21:22:22Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 child classification for transport processes.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n connect.c          | 3 +++\n transport-helper.c | 2 ++\n 2 files changed, 5 insertions(+)\n\ndiff --git a/connect.c b/connect.c\nindex 24281b6082..3c6f829a05 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -1251,6 +1251,7 @@ struct child_process *git_connect(int fd[2], const char *url,\n \t\tconn = NULL;\n \t} else if (protocol == PROTO_GIT) {\n \t\tconn = git_connect_git(fd, hostandport, path, prog, version, flags);\n+\t\tconn->trace2_child_class = \"transport/git\";\n \t} else {\n \t\tstruct strbuf cmd = STRBUF_INIT;\n \t\tconst char *const *var;\n@@ -1293,9 +1294,11 @@ struct child_process *git_connect(int fd[2], const char *url,\n \t\t\t\tstrbuf_release(&cmd);\n \t\t\t\treturn NULL;\n \t\t\t}\n+\t\t\tconn->trace2_child_class = \"transport/ssh\";\n \t\t\tfill_ssh_args(conn, ssh_host, port, version, flags);\n \t\t} else {\n \t\t\ttransport_check_allowed(\"file\");\n+\t\t\tconn->trace2_child_class = \"transport/file\";\n \t\t\tif (version > 0) {\n \t\t\t\targv_array_pushf(&conn->env_array, GIT_PROTOCOL_ENVIRONMENT \"=version=%d\",\n \t\t\t\t\t\t version);\ndiff --git a/transport-helper.c b/transport-helper.c\nindex bf225c698f..1eb656b687 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -127,6 +127,8 @@ static struct child_process *get_helper(struct transport *transport)\n \t\targv_array_pushf(&helper->env_array, \"%s=%s\",\n \t\t\t\t GIT_DIR_ENVIRONMENT, get_git_dir());\n \n+\thelper->trace2_child_class = helper->args.argv[0]; /* \"remote-<name>\" */\n+\n \tcode = start_command(helper);\n \tif (code < 0 && errno == ENOENT)\n \t\tdie(_(\"unable to find remote helper for '%s'\"), data->name);\n-- \ngitgitgadget\n\n"},{"id":"367366","messageId":"394164709724f6a90c03016aee4a65c671839459.1548192131.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.git.gitgitgadget@gmail.com","subject":"[PATCH 05/14] trace2:data: add editor/pager child classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-22T21:22:16Z","receivedAt":"2019-01-22T21:22:23Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 process classification for editor and pager\nchild processes.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n editor.c | 1 +\n pager.c  | 1 +\n 2 files changed, 2 insertions(+)\n\ndiff --git a/editor.c b/editor.c\nindex c985eee1f9..71547674ab 100644\n--- a/editor.c\n+++ b/editor.c\n@@ -78,6 +78,7 @@ static int launch_specified_editor(const char *editor, const char *path,\n \t\tp.argv = args;\n \t\tp.env = env;\n \t\tp.use_shell = 1;\n+\t\tp.trace2_child_class = \"editor\";\n \t\tif (start_command(&p) < 0)\n \t\t\treturn error(\"unable to start editor '%s'\", editor);\n \ndiff --git a/pager.c b/pager.c\nindex a768797fcf..4168460ae9 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -100,6 +100,7 @@ void prepare_pager_args(struct child_process *pager_process, const char *pager)\n \targv_array_push(&pager_process->args, pager);\n \tpager_process->use_shell = 1;\n \tsetup_pager_env(&pager_process->env_array);\n+\tpager_process->trace2_child_class = \"pager\";\n }\n \n void setup_pager(void)\n-- \ngitgitgadget\n\n"},{"id":"367367","messageId":"0f8f366bbebcb89e794425e4d557c6cf107f6f8a.1548192131.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.git.gitgitgadget@gmail.com","subject":"[PATCH 08/14] trace2:data: add trace2 hook classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-22T21:22:19Z","receivedAt":"2019-01-22T21:22:25Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nClassify certain child processes as hooks.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/am.c           | 1 +\n builtin/receive-pack.c | 4 ++++\n builtin/worktree.c     | 1 +\n sequencer.c            | 2 ++\n transport.c            | 1 +\n 5 files changed, 9 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 95370313b6..a81e61c1bc 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -468,6 +468,7 @@ static int run_post_rewrite_hook(const struct am_state *state)\n \n \tcp.in = xopen(am_path(state, \"rewritten\"), O_RDONLY);\n \tcp.stdout_to_stderr = 1;\n+\tcp.trace2_hook_name = \"post-rewrite\";\n \n \tret = run_command(&cp);\n \ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 33187bd8e9..6eef8575b9 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -694,6 +694,8 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,\n \tproc.argv = argv;\n \tproc.in = -1;\n \tproc.stdout_to_stderr = 1;\n+\tproc.trace2_hook_name = hook_name;\n+\n \tif (feed_state->push_options) {\n \t\tint i;\n \t\tfor (i = 0; i < feed_state->push_options->nr; i++)\n@@ -807,6 +809,7 @@ static int run_update_hook(struct command *cmd)\n \tproc.stdout_to_stderr = 1;\n \tproc.err = use_sideband ? -1 : 0;\n \tproc.argv = argv;\n+\tproc.trace2_hook_name = \"update\";\n \n \tcode = start_command(&proc);\n \tif (code)\n@@ -1190,6 +1193,7 @@ static void run_update_post_hook(struct command *commands)\n \tproc.no_stdin = 1;\n \tproc.stdout_to_stderr = 1;\n \tproc.err = use_sideband ? -1 : 0;\n+\tproc.trace2_hook_name = \"post-update\";\n \n \tif (!start_command(&proc)) {\n \t\tif (use_sideband)\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 5e84026177..32e6f1b341 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -401,6 +401,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\t\tcp.dir = path;\n \t\t\tcp.env = env;\n \t\t\tcp.argv = NULL;\n+\t\t\tcp.trace2_hook_name = \"post-checkout\";\n \t\t\targv_array_pushl(&cp.args, absolute_path(hook),\n \t\t\t\t\t oid_to_hex(&null_oid),\n \t\t\t\t\t oid_to_hex(&commit->object.oid),\ndiff --git a/sequencer.c b/sequencer.c\nindex b68bca0bef..9a6ec7fcd4 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1102,6 +1102,7 @@ static int run_rewrite_hook(const struct object_id *oldoid,\n \tproc.argv = argv;\n \tproc.in = -1;\n \tproc.stdout_to_stderr = 1;\n+\tproc.trace2_hook_name = \"post-rewrite\";\n \n \tcode = start_command(&proc);\n \tif (code)\n@@ -3771,6 +3772,7 @@ static int pick_commits(struct repository *r,\n \t\t\t\thook.in = open(rebase_path_rewritten_list(),\n \t\t\t\t\tO_RDONLY);\n \t\t\t\thook.stdout_to_stderr = 1;\n+\t\t\t\thook.trace2_hook_name = \"post-rewrite\";\n \t\t\t\targv_array_push(&hook.args, post_rewrite_hook);\n \t\t\t\targv_array_push(&hook.args, \"rebase\");\n \t\t\t\t/* we don't care if this hook failed */\ndiff --git a/transport.c b/transport.c\nindex 99678153c1..ed5a733c4a 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1061,6 +1061,7 @@ static int run_pre_push_hook(struct transport *transport,\n \n \tproc.argv = argv;\n \tproc.in = -1;\n+\tproc.trace2_hook_name = \"pre-push\";\n \n \tif (start_command(&proc)) {\n \t\tfinish_command(&proc);\n-- \ngitgitgadget\n\n"},{"id":"367368","messageId":"4aaf4834bfa9f2169e2c00f7cdc6c75281567c15.1548192131.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.git.gitgitgadget@gmail.com","subject":"[PATCH 02/14] trace2: create new combined trace facility","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-22T21:22:14Z","receivedAt":"2019-01-22T21:22:28Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate a new unified tracing facility for git.  The eventual intent is to\nreplace the current trace_printf* and trace_performance* routines with a\nunified set of git_trace2* routines.\n\nIn addition to the usual printf-style API, trace2 provides higer-level\nevent verbs with fixed-fields allowing structured data to be written.\nThis makes post-processing and analysis easier for external tools.\n\nTrace2 defines 3 output targets.  These are set using the environment\nvariables \"GIT_TR2\", \"GIT_TR2_PERF\", and \"GIT_TR2_EVENT\".  These may be\nset to \"1\" or to an absolute pathname (just like the current GIT_TRACE).\n\n* GIT_TR2 is intended to be a replacement for GIT_TRACE and logs command\n  summary data.\n\n* GIT_TR2_PERF is intended as a replacement for GIT_TRACE_PERFORMANCE.\n  It extends the output with columns for the command process, thread,\n  repo, absolute and relative elapsed times.  It reports events for\n  child process start/stop, thread start/stop, and per-thread function\n  nesting.\n\n* GIT_TR2_EVENT is a new structured format. It writes event data as a\n  series of JSON records.\n\nCalls to trace2 functions log to any of the 3 output targets enabled\nwithout the need to call different trace_printf* or trace_performance*\nroutines.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                      |  10 +\n builtin/submodule--helper.c   |  11 +-\n cache.h                       |   1 +\n common-main.c                 |  12 +-\n compat/mingw.c                |  11 +-\n compat/mingw.h                |   3 +-\n config.c                      |   2 +\n exec-cmd.c                    |   2 +\n git-compat-util.h             |   7 +\n git.c                         |  65 +++\n remote-curl.c                 |   7 +\n repository.c                  |   2 +\n repository.h                  |   3 +\n run-command.c                 |  63 ++-\n run-command.h                 |  17 +-\n sh-i18n--envsubst.c           |   3 +\n submodule.c                   |  11 +-\n t/helper/test-parse-options.c |   3 +\n t/helper/test-tool.c          |   3 +\n t/t0001-init.sh               |   1 +\n trace2.c                      | 809 ++++++++++++++++++++++++++++++++++\n trace2.h                      | 390 ++++++++++++++++\n trace2/tr2_cfg.c              |  92 ++++\n trace2/tr2_cfg.h              |  19 +\n trace2/tr2_dst.c              |  90 ++++\n trace2/tr2_dst.h              |  34 ++\n trace2/tr2_sid.c              |  67 +++\n trace2/tr2_sid.h              |  18 +\n trace2/tr2_tbuf.c             |  32 ++\n trace2/tr2_tbuf.h             |  23 +\n trace2/tr2_tgt.h              | 126 ++++++\n trace2/tr2_tgt_event.c        | 606 +++++++++++++++++++++++++\n trace2/tr2_tgt_normal.c       | 331 ++++++++++++++\n trace2/tr2_tgt_perf.c         | 573 ++++++++++++++++++++++++\n trace2/tr2_tls.c              | 164 +++++++\n trace2/tr2_tls.h              |  95 ++++\n trace2/tr2_verb.c             |  30 ++\n trace2/tr2_verb.h             |  24 +\n usage.c                       |  31 ++\n 39 files changed, 3774 insertions(+), 17 deletions(-)\n create mode 100644 trace2.c\n create mode 100644 trace2.h\n create mode 100644 trace2/tr2_cfg.c\n create mode 100644 trace2/tr2_cfg.h\n create mode 100644 trace2/tr2_dst.c\n create mode 100644 trace2/tr2_dst.h\n create mode 100644 trace2/tr2_sid.c\n create mode 100644 trace2/tr2_sid.h\n create mode 100644 trace2/tr2_tbuf.c\n create mode 100644 trace2/tr2_tbuf.h\n create mode 100644 trace2/tr2_tgt.h\n create mode 100644 trace2/tr2_tgt_event.c\n create mode 100644 trace2/tr2_tgt_normal.c\n create mode 100644 trace2/tr2_tgt_perf.c\n create mode 100644 trace2/tr2_tls.c\n create mode 100644 trace2/tr2_tls.h\n create mode 100644 trace2/tr2_verb.c\n create mode 100644 trace2/tr2_verb.h\n\ndiff --git a/Makefile b/Makefile\nindex 1a44c811aa..5d4e993cdc 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -996,6 +996,16 @@ LIB_OBJS += tempfile.o\n LIB_OBJS += thread-utils.o\n LIB_OBJS += tmp-objdir.o\n LIB_OBJS += trace.o\n+LIB_OBJS += trace2.o\n+LIB_OBJS += trace2/tr2_cfg.o\n+LIB_OBJS += trace2/tr2_dst.o\n+LIB_OBJS += trace2/tr2_sid.o\n+LIB_OBJS += trace2/tr2_tbuf.o\n+LIB_OBJS += trace2/tr2_tgt_event.o\n+LIB_OBJS += trace2/tr2_tgt_normal.o\n+LIB_OBJS += trace2/tr2_tgt_perf.o\n+LIB_OBJS += trace2/tr2_tls.o\n+LIB_OBJS += trace2/tr2_verb.o\n LIB_OBJS += trailer.o\n LIB_OBJS += transport.o\n LIB_OBJS += transport-helper.o\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex b45514be31..82716b5060 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -1813,11 +1813,12 @@ static int update_submodules(struct submodule_update_clone *suc)\n {\n \tint i;\n \n-\trun_processes_parallel(suc->max_jobs,\n-\t\t\t       update_clone_get_next_task,\n-\t\t\t       update_clone_start_failure,\n-\t\t\t       update_clone_task_finished,\n-\t\t\t       suc);\n+\trun_processes_parallel_tr2(suc->max_jobs,\n+\t\t\t\t   update_clone_get_next_task,\n+\t\t\t\t   update_clone_start_failure,\n+\t\t\t\t   update_clone_task_finished,\n+\t\t\t\t   suc,\n+\t\t\t\t   \"submodule\", \"parallel/update\");\n \n \t/*\n \t * We saved the output and put it out all at once now.\ndiff --git a/cache.h b/cache.h\nindex 49713cc5a5..af996443ee 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -9,6 +9,7 @@\n #include \"gettext.h\"\n #include \"convert.h\"\n #include \"trace.h\"\n+#include \"trace2.h\"\n #include \"string-list.h\"\n #include \"pack-revindex.h\"\n #include \"hash.h\"\ndiff --git a/common-main.c b/common-main.c\nindex 3728f66b4c..6dbdc4adf2 100644\n--- a/common-main.c\n+++ b/common-main.c\n@@ -25,12 +25,18 @@ static void restore_sigpipe_to_default(void)\n \n int main(int argc, const char **argv)\n {\n+\tint result;\n+\n \t/*\n \t * Always open file descriptors 0/1/2 to avoid clobbering files\n \t * in die().  It also avoids messing up when the pipes are dup'ed\n \t * onto stdin/stdout/stderr in the child processes we spawn.\n \t */\n \tsanitize_stdfds();\n+\trestore_sigpipe_to_default();\n+\n+\ttrace2_initialize();\n+\ttrace2_cmd_start(argv);\n \n \tgit_resolve_executable_dir(argv[0]);\n \n@@ -40,7 +46,9 @@ int main(int argc, const char **argv)\n \n \tattr_start();\n \n-\trestore_sigpipe_to_default();\n+\tresult = cmd_main(argc, argv);\n+\n+\ttrace2_cmd_exit(result);\n \n-\treturn cmd_main(argc, argv);\n+\treturn result;\n }\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex b459e1a291..111fe68baf 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -1476,19 +1476,23 @@ static int try_shell_exec(const char *cmd, char *const *argv)\n \t\treturn 0;\n \tprog = path_lookup(interpr, 1);\n \tif (prog) {\n+\t\tint exec_id;\n \t\tint argc = 0;\n \t\tconst char **argv2;\n \t\twhile (argv[argc]) argc++;\n \t\tALLOC_ARRAY(argv2, argc + 1);\n \t\targv2[0] = (char *)cmd;\t/* full path to the script file */\n \t\tmemcpy(&argv2[1], &argv[1], sizeof(*argv) * argc);\n+\t\texec_id = trace2_exec(prog, argv2);\n \t\tpid = mingw_spawnv(prog, argv2, 1);\n \t\tif (pid >= 0) {\n \t\t\tint status;\n \t\t\tif (waitpid(pid, &status, 0) < 0)\n \t\t\t\tstatus = 255;\n+\t\t\ttrace2_exec_result(exec_id, status);\n \t\t\texit(status);\n \t\t}\n+\t\ttrace2_exec_result(exec_id, -1);\n \t\tpid = 1;\t/* indicate that we tried but failed */\n \t\tfree(prog);\n \t\tfree(argv2);\n@@ -1501,12 +1505,17 @@ int mingw_execv(const char *cmd, char *const *argv)\n \t/* check if git_command is a shell script */\n \tif (!try_shell_exec(cmd, argv)) {\n \t\tint pid, status;\n+\t\tint exec_id;\n \n+\t\texec_id = trace2_exec(cmd, (const char **)argv);\n \t\tpid = mingw_spawnv(cmd, (const char **)argv, 0);\n-\t\tif (pid < 0)\n+\t\tif (pid < 0) {\n+\t\t\ttrace2_exec_result(exec_id, -1);\n \t\t\treturn -1;\n+\t\t}\n \t\tif (waitpid(pid, &status, 0) < 0)\n \t\t\tstatus = 255;\n+\t\ttrace2_exec_result(exec_id, status);\n \t\texit(status);\n \t}\n \treturn -1;\ndiff --git a/compat/mingw.h b/compat/mingw.h\nindex 30d9fb3e36..4d73f8aa9d 100644\n--- a/compat/mingw.h\n+++ b/compat/mingw.h\n@@ -147,8 +147,7 @@ static inline int fcntl(int fd, int cmd, ...)\n \terrno = EINVAL;\n \treturn -1;\n }\n-/* bash cannot reliably detect negative return codes as failure */\n-#define exit(code) exit((code) & 0xff)\n+\n #define sigemptyset(x) (void)0\n static inline int sigaddset(sigset_t *set, int signum)\n { return 0; }\ndiff --git a/config.c b/config.c\nindex ff521eb27a..4beeb7795a 100644\n--- a/config.c\n+++ b/config.c\n@@ -2656,6 +2656,8 @@ int git_config_set_gently(const char *key, const char *value)\n void git_config_set(const char *key, const char *value)\n {\n \tgit_config_set_multivar(key, value, NULL, 0);\n+\n+\ttrace2_cmd_set_config(key, value);\n }\n \n /*\ndiff --git a/exec-cmd.c b/exec-cmd.c\nindex 4f81f44310..7deeab3039 100644\n--- a/exec-cmd.c\n+++ b/exec-cmd.c\n@@ -209,6 +209,8 @@ static int git_get_exec_path(struct strbuf *buf, const char *argv0)\n \t\treturn -1;\n \t}\n \n+\ttrace2_cmd_path(buf->buf);\n+\n \treturn 0;\n }\n \ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex b8ace77410..a41b277929 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -1238,6 +1238,13 @@ static inline int is_missing_file_error(int errno_)\n \n extern int cmd_main(int, const char **);\n \n+/*\n+ * Intercept all calls to exit() and route them to trace2 to\n+ * optionally emit a message before calling the real exit().\n+ */\n+int trace2_cmd_exit_fl(const char *file, int line, int code);\n+#define exit(code) exit(trace2_cmd_exit_fl(__FILE__, __LINE__, (code)))\n+\n /*\n  * You can mark a stack variable with UNLEAK(var) to avoid it being\n  * reported as a leak by tools like LSAN or valgrind. The argument\ndiff --git a/git.c b/git.c\nindex 4d53a3d50d..2db81c775a 100644\n--- a/git.c\n+++ b/git.c\n@@ -146,16 +146,20 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\t\tgit_set_exec_path(cmd + 1);\n \t\t\telse {\n \t\t\t\tputs(git_exec_path());\n+\t\t\t\ttrace2_cmd_verb(\"_query_\");\n \t\t\t\texit(0);\n \t\t\t}\n \t\t} else if (!strcmp(cmd, \"--html-path\")) {\n \t\t\tputs(system_path(GIT_HTML_PATH));\n+\t\t\ttrace2_cmd_verb(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"--man-path\")) {\n \t\t\tputs(system_path(GIT_MAN_PATH));\n+\t\t\ttrace2_cmd_verb(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"--info-path\")) {\n \t\t\tputs(system_path(GIT_INFO_PATH));\n+\t\t\ttrace2_cmd_verb(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"-p\") || !strcmp(cmd, \"--paginate\")) {\n \t\t\tuse_pager = 1;\n@@ -284,6 +288,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\t(*argv)++;\n \t\t\t(*argc)--;\n \t\t} else if (skip_prefix(cmd, \"--list-cmds=\", &cmd)) {\n+\t\t\ttrace2_cmd_verb(\"_query_\");\n \t\t\tif (!strcmp(cmd, \"parseopt\")) {\n \t\t\t\tstruct string_list list = STRING_LIST_INIT_DUP;\n \t\t\t\tint i;\n@@ -331,9 +336,14 @@ static int handle_alias(int *argcp, const char ***argv)\n \t\t\tcommit_pager_choice();\n \n \t\t\tchild.use_shell = 1;\n+\t\t\tchild.trace2_child_class = \"shell_alias\";\n \t\t\targv_array_push(&child.args, alias_string + 1);\n \t\t\targv_array_pushv(&child.args, (*argv) + 1);\n \n+\t\t\ttrace2_cmd_alias(alias_command, child.args.argv);\n+\t\t\ttrace2_cmd_list_config();\n+\t\t\ttrace2_cmd_verb(\"_run_shell_alias_\");\n+\n \t\t\tret = run_command(&child);\n \t\t\tif (ret >= 0)   /* normal exit */\n \t\t\t\texit(ret);\n@@ -368,6 +378,9 @@ static int handle_alias(int *argcp, const char ***argv)\n \t\t/* insert after command name */\n \t\tmemcpy(new_argv + count, *argv + 1, sizeof(char *) * *argcp);\n \n+\t\ttrace2_cmd_alias(alias_command, new_argv);\n+\t\ttrace2_cmd_list_config();\n+\n \t\t*argv = new_argv;\n \t\t*argcp += count - 1;\n \n@@ -416,6 +429,8 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)\n \t\tsetup_work_tree();\n \n \ttrace_argv_printf(argv, \"trace: built-in: git\");\n+\ttrace2_cmd_verb(p->cmd);\n+\ttrace2_cmd_list_config();\n \n \tvalidate_cache_entries(&the_index);\n \tstatus = p->fn(argc, argv, prefix);\n@@ -665,7 +680,14 @@ static void execv_dashed_external(const char **argv)\n \tcmd.clean_on_exit = 1;\n \tcmd.wait_after_clean = 1;\n \tcmd.silent_exec_failure = 1;\n+\tcmd.trace2_child_class = \"dashed\";\n \n+\ttrace2_cmd_verb(\"_run_dashed_\");\n+\n+\t/*\n+\t * The code in run_command() logs trace2 child_start/child_exit\n+\t * events, so we do not need to report exec/exec_result events here.\n+\t */\n \ttrace_argv_printf(cmd.args.argv, \"trace: exec:\");\n \n \t/*\n@@ -675,6 +697,12 @@ static void execv_dashed_external(const char **argv)\n \t * the program.\n \t */\n \tstatus = run_command(&cmd);\n+\n+\t/*\n+\t * If the child process ran and we are now going to exit, emit a\n+\t * generic string as our trace2 command verb to indicate that we\n+\t * launched a dashed command.\n+\t */\n \tif (status >= 0)\n \t\texit(status);\n \telse if (errno != ENOENT)\n@@ -700,6 +728,43 @@ static int run_argv(int *argcp, const char ***argv)\n \t\tif (!done_alias)\n \t\t\thandle_builtin(*argcp, *argv);\n \n+#if 0 // TODO In GFW, need to amend a7924b655e940b06cb547c235d6bed9767929673 to include trace2_ and _tr2 lines.\n+\t\telse if (get_builtin(**argv)) {\n+\t\t\tstruct argv_array args = ARGV_ARRAY_INIT;\n+\t\t\tint i;\n+\n+\t\t\t/*\n+\t\t\t * The current process is committed to launching a\n+\t\t\t * child process to run the command named in (**argv)\n+\t\t\t * and exiting.  Log a generic string as the trace2\n+\t\t\t * command verb to indicate this.  Note that the child\n+\t\t\t * process will log the actual verb when it runs.\n+\t\t\t */\n+\t\t\ttrace2_cmd_verb(\"_run_git_alias_\");\n+\n+\t\t\tif (get_super_prefix())\n+\t\t\t\tdie(\"%s doesn't support --super-prefix\", **argv);\n+\n+\t\t\tcommit_pager_choice();\n+\n+\t\t\targv_array_push(&args, \"git\");\n+\t\t\tfor (i = 0; i < *argcp; i++)\n+\t\t\t\targv_array_push(&args, (*argv)[i]);\n+\n+\t\t\ttrace_argv_printf(args.argv, \"trace: exec:\");\n+\n+\t\t\t/*\n+\t\t\t * if we fail because the command is not found, it is\n+\t\t\t * OK to return. Otherwise, we just pass along the status code.\n+\t\t\t */\n+\t\t\ti = run_command_v_opt_tr2(args.argv, RUN_SILENT_EXEC_FAILURE |\n+\t\t\t\t\t\t  RUN_CLEAN_ON_EXIT, \"git_alias\");\n+\t\t\tif (i >= 0 || errno != ENOENT)\n+\t\t\t\texit(i);\n+\t\t\tdie(\"could not execute builtin %s\", **argv);\n+\t\t}\n+#endif // a7924b655e940b06cb547c235d6bed9767929673\n+\n \t\t/* .. then try the external ones */\n \t\texecv_dashed_external(*argv);\n \ndiff --git a/remote-curl.c b/remote-curl.c\nindex 1220dffcdc..8950b5c383 100644\n--- a/remote-curl.c\n+++ b/remote-curl.c\n@@ -1340,6 +1340,13 @@ int cmd_main(int argc, const char **argv)\n \tstring_list_init(&options.deepen_not, 1);\n \tstring_list_init(&options.push_options, 1);\n \n+\t/*\n+\t * Just report \"remote-curl\" here (folding all the various aliases\n+\t * (\"git-remote-http\", \"git-remote-https\", and etc.) here since they\n+\t * are all just copies of the same actual executable.\n+\t */\n+\ttrace2_cmd_verb(\"remote-curl\");\n+\n \tremote = remote_get(argv[1]);\n \n \tif (argc > 2) {\ndiff --git a/repository.c b/repository.c\nindex 7b02e1dffa..b741089dbb 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -119,6 +119,8 @@ static int repo_init_gitdir(struct repository *repo, const char *gitdir)\n void repo_set_worktree(struct repository *repo, const char *path)\n {\n \trepo->worktree = real_pathdup(path, 1);\n+\n+\ttrace2_def_repo(repo);\n }\n \n static int read_and_verify_repository_format(struct repository_format *format,\ndiff --git a/repository.h b/repository.h\nindex 9f16c42c1e..97b03846ba 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -90,6 +90,9 @@ struct repository {\n \t/* Repository's current hash algorithm, as serialized on disk. */\n \tconst struct git_hash_algo *hash_algo;\n \n+\t/* A unique-id for tracing purposes. */\n+\tint trace2_repo_id;\n+\n \t/* Configurations */\n \n \t/* Indicate if a repository has a different 'commondir' from 'gitdir' */\ndiff --git a/run-command.c b/run-command.c\nindex 3db26b7b0e..8948f6fc6f 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -219,9 +219,29 @@ static int exists_in_PATH(const char *file)\n \n int sane_execvp(const char *file, char * const argv[])\n {\n+#ifndef GIT_WINDOWS_NATIVE\n+\t/*\n+\t * execvp() doesn't return, so we all we can do is tell trace2\n+\t * what we are about to do and let it leave a hint in the log\n+\t * (unless of course the execvp() fails).\n+\t *\n+\t * we skip this for Windows because the compat layer already\n+\t * has to emulate the execvp() call anyway.\n+\t */\n+\tint exec_id = trace2_exec(file, (const char**)argv);\n+#endif\n+\n \tif (!execvp(file, argv))\n \t\treturn 0; /* cannot happen ;-) */\n \n+#ifndef GIT_WINDOWS_NATIVE\n+\t{\n+\t\tint ec = errno;\n+\t\ttrace2_exec_result(exec_id, ec);\n+\t\terrno = ec;\n+\t}\n+#endif\n+\n \t/*\n \t * When a command can't be found because one of the directories\n \t * listed in $PATH is unsearchable, execvp reports EACCES, but\n@@ -712,6 +732,7 @@ int start_command(struct child_process *cmd)\n \t\tcmd->err = fderr[0];\n \t}\n \n+\ttrace2_child_start(cmd);\n \ttrace_run_command(cmd);\n \n \tfflush(NULL);\n@@ -926,6 +947,8 @@ int start_command(struct child_process *cmd)\n #endif\n \n \tif (cmd->pid < 0) {\n+\t\ttrace2_child_exit(cmd, -1);\n+\n \t\tif (need_in)\n \t\t\tclose_pair(fdin);\n \t\telse if (cmd->in)\n@@ -964,13 +987,16 @@ int start_command(struct child_process *cmd)\n int finish_command(struct child_process *cmd)\n {\n \tint ret = wait_or_whine(cmd->pid, cmd->argv[0], 0);\n+\ttrace2_child_exit(cmd, ret);\n \tchild_process_clear(cmd);\n \treturn ret;\n }\n \n int finish_command_in_signal(struct child_process *cmd)\n {\n-\treturn wait_or_whine(cmd->pid, cmd->argv[0], 1);\n+\tint ret = wait_or_whine(cmd->pid, cmd->argv[0], 1);\n+\ttrace2_child_exit(cmd, ret);\n+\treturn ret;\n }\n \n \n@@ -992,7 +1018,18 @@ int run_command_v_opt(const char **argv, int opt)\n \treturn run_command_v_opt_cd_env(argv, opt, NULL, NULL);\n }\n \n+int run_command_v_opt_tr2(const char **argv, int opt, const char *tr2_class)\n+{\n+\treturn run_command_v_opt_cd_env_tr2(argv, opt, NULL, NULL, tr2_class);\n+}\n+\n int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env)\n+{\n+\treturn run_command_v_opt_cd_env_tr2(argv, opt, dir, env, NULL);\n+}\n+\n+int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir, const char *const *env,\n+\t\t\t\t const char *tr2_class)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \tcmd.argv = argv;\n@@ -1004,6 +1041,7 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const\n \tcmd.clean_on_exit = opt & RUN_CLEAN_ON_EXIT ? 1 : 0;\n \tcmd.dir = dir;\n \tcmd.env = env;\n+\tcmd.trace2_child_class = tr2_class;\n \treturn run_command(&cmd);\n }\n \n@@ -1319,6 +1357,7 @@ int run_hook_ve(const char *const *env, const char *name, va_list args)\n \thook.env = env;\n \thook.no_stdin = 1;\n \thook.stdout_to_stderr = 1;\n+\thook.trace2_hook_name = name;\n \n \treturn run_command(&hook);\n }\n@@ -1807,3 +1846,25 @@ int run_processes_parallel(int n,\n \tpp_cleanup(&pp);\n \treturn 0;\n }\n+\n+int run_processes_parallel_tr2(int n,\n+\t\t\t       get_next_task_fn get_next_task,\n+\t\t\t       start_failure_fn start_failure,\n+\t\t\t       task_finished_fn task_finished,\n+\t\t\t       void *pp_cb,\n+\t\t\t       const char *tr2_category,\n+\t\t\t       const char *tr2_label)\n+{\n+\tint result;\n+\n+\ttrace2_region_enter_printf(tr2_category, tr2_label, NULL,\n+\t\t\t\t   \"max:%d\", ((n < 1) ? online_cpus() : n));\n+\n+\tresult = run_processes_parallel(n, get_next_task, start_failure,\n+\t\t\t\t\ttask_finished, pp_cb);\n+\n+\ttrace2_region_leave(tr2_category, tr2_label, NULL);\n+\n+\treturn result;\n+}\n+\ndiff --git a/run-command.h b/run-command.h\nindex 68f5369fc2..91ac29102d 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -10,6 +10,12 @@ struct child_process {\n \tstruct argv_array args;\n \tstruct argv_array env_array;\n \tpid_t pid;\n+\n+\tint trace2_child_id;\n+\tuint64_t trace2_child_us_start;\n+\tconst char *trace2_child_class;\n+\tconst char *trace2_hook_name;\n+\n \t/*\n \t * Using .in, .out, .err:\n \t * - Specify 0 for no redirections (child inherits stdin, stdout,\n@@ -73,12 +79,14 @@ extern int run_hook_ve(const char *const *env, const char *name, va_list args);\n #define RUN_USING_SHELL 16\n #define RUN_CLEAN_ON_EXIT 32\n int run_command_v_opt(const char **argv, int opt);\n-\n+int run_command_v_opt_tr2(const char **argv, int opt, const char *tr2_class);\n /*\n  * env (the environment) is to be formatted like environ: \"VAR=VALUE\".\n  * To unset an environment variable use just \"VAR\".\n  */\n int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env);\n+int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir, const char *const *env,\n+\t\t\t\t const char *tr2_class);\n \n /**\n  * Execute the given command, sending \"in\" to its stdin, and capturing its\n@@ -220,5 +228,12 @@ int run_processes_parallel(int n,\n \t\t\t   start_failure_fn,\n \t\t\t   task_finished_fn,\n \t\t\t   void *pp_cb);\n+int run_processes_parallel_tr2(int n,\n+\t\t\t       get_next_task_fn,\n+\t\t\t       start_failure_fn,\n+\t\t\t       task_finished_fn,\n+\t\t\t       void *pp_cb,\n+\t\t\t       const char *tr2_category,\n+\t\t\t       const char *tr2_label);\n \n #endif\ndiff --git a/sh-i18n--envsubst.c b/sh-i18n--envsubst.c\nindex 09c6b445b9..2296ba1a2d 100644\n--- a/sh-i18n--envsubst.c\n+++ b/sh-i18n--envsubst.c\n@@ -14,6 +14,7 @@\n  */\n \n #include \"git-compat-util.h\"\n+#include \"trace2.h\"\n \n /* Substitution of environment variables in shell format strings.\n    Copyright (C) 2003-2007 Free Software Foundation, Inc.\n@@ -67,6 +68,8 @@ cmd_main (int argc, const char *argv[])\n   /* Default values for command line options.  */\n   /* unsigned short int show_variables = 0; */\n \n+  trace2_cmd_verb(\"sh-i18n--envsubst\");\n+\n   switch (argc)\n \t{\n \tcase 1:\ndiff --git a/submodule.c b/submodule.c\nindex 6415cc5580..acc96e133b 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1374,11 +1374,12 @@ int fetch_populated_submodules(struct repository *r,\n \t/* default value, \"--submodule-prefix\" and its value are added later */\n \n \tcalculate_changed_submodule_paths(r);\n-\trun_processes_parallel(max_parallel_jobs,\n-\t\t\t       get_next_submodule,\n-\t\t\t       fetch_start_failure,\n-\t\t\t       fetch_finish,\n-\t\t\t       &spf);\n+\trun_processes_parallel_tr2(max_parallel_jobs,\n+\t\t\t\t   get_next_submodule,\n+\t\t\t\t   fetch_start_failure,\n+\t\t\t\t   fetch_finish,\n+\t\t\t\t   &spf,\n+\t\t\t\t   \"submodule\", \"parallel/fetch\");\n \n \targv_array_clear(&spf.args);\n out:\ndiff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c\nindex 47fee660b8..5318681739 100644\n--- a/t/helper/test-parse-options.c\n+++ b/t/helper/test-parse-options.c\n@@ -2,6 +2,7 @@\n #include \"cache.h\"\n #include \"parse-options.h\"\n #include \"string-list.h\"\n+#include \"trace2.h\"\n \n static int boolean = 0;\n static int integer = 0;\n@@ -153,6 +154,8 @@ int cmd__parse_options(int argc, const char **argv)\n \tint i;\n \tint ret = 0;\n \n+\ttrace2_cmd_verb(\"_parse_\");\n+\n \targc = parse_options(argc, (const char **)argv, prefix, options, usage, 0);\n \n \tif (length_cb.called) {\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex bfb195b1a8..85d1f812fe 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -1,5 +1,6 @@\n #include \"git-compat-util.h\"\n #include \"test-tool.h\"\n+#include \"trace2.h\"\n \n struct test_cmd {\n \tconst char *name;\n@@ -78,6 +79,8 @@ int cmd_main(int argc, const char **argv)\n \t\tif (!strcmp(cmds[i].name, argv[1])) {\n \t\t\targv++;\n \t\t\targc--;\n+\t\t\ttrace2_cmd_verb(cmds[i].name);\n+\t\t\ttrace2_cmd_list_config();\n \t\t\treturn cmds[i].fn(argc, argv);\n \t\t}\n \t}\ndiff --git a/t/t0001-init.sh b/t/t0001-init.sh\nindex 42a263cada..5e27604b24 100755\n--- a/t/t0001-init.sh\n+++ b/t/t0001-init.sh\n@@ -93,6 +93,7 @@ test_expect_success 'No extra GIT_* on alias scripts' '\n \t\tsed -n \\\n \t\t\t-e \"/^GIT_PREFIX=/d\" \\\n \t\t\t-e \"/^GIT_TEXTDOMAINDIR=/d\" \\\n+\t\t\t-e \"/^GIT_TR2_PARENT/d\" \\\n \t\t\t-e \"/^GIT_/s/=.*//p\" |\n \t\tsort\n \tEOF\ndiff --git a/trace2.c b/trace2.c\nnew file mode 100644\nindex 0000000000..72af89ba73\n--- /dev/null\n+++ b/trace2.c\n@@ -0,0 +1,809 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"json-writer.h\"\n+#include \"quote.h\"\n+#include \"run-command.h\"\n+#include \"sigchain.h\"\n+#include \"thread-utils.h\"\n+#include \"version.h\"\n+#include \"trace2/tr2_cfg.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_sid.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+#include \"trace2/tr2_verb.h\"\n+\n+static int trace2_enabled;\n+\n+static int tr2_next_child_id; /* modify under lock */\n+static int tr2_next_exec_id; /* modify under lock */\n+static int tr2_next_repo_id = 1; /* modify under lock. zero is reserved */\n+\n+/*\n+ * A table of the builtin TRACE2 targets.  Each of these may be independently\n+ * enabled or disabled.  Each TRACE2 API method will try to write an event to\n+ * *each* of the enabled targets.\n+ */\n+static struct tr2_tgt *tr2_tgt_builtins[] =\n+{\n+\t&tr2_tgt_normal,\n+\t&tr2_tgt_perf,\n+\t&tr2_tgt_event,\n+\tNULL\n+};\n+\n+#define for_each_builtin(j, tgt_j)\t\t\t\\\n+\tfor (j = 0, tgt_j = tr2_tgt_builtins[j];\t\\\n+\t     tgt_j;\t\t\t\t\t\\\n+\t     j++, tgt_j = tr2_tgt_builtins[j])\n+\n+#define for_each_wanted_builtin(j, tgt_j)\t\\\n+\tfor_each_builtin(j, tgt_j)\t\t\\\n+\tif (tr2_dst_trace_want(tgt_j->pdst))\n+\n+/*\n+ * Force (rather than lazily) initialize any of the requested\n+ * builtin TRACE2 targets at startup (and before we've seen an\n+ * actual TRACE2 event call) so we can see if we need to setup\n+ * the TR2 and TLS machinery.\n+ *\n+ * Return the number of builtin targets enabled.\n+ */\n+static int tr2_tgt_want_builtins(void)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tint sum = 0;\n+\n+\tfor_each_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_init())\n+\t\t\tsum++;\n+\t}\n+\n+\treturn sum;\n+}\n+\n+/*\n+ * Properly terminate each builtin target.  Give each target\n+ * a chance to write a summary event and/or flush if necessary\n+ * and then close the fd.\n+ */\n+static void tr2_tgt_disable_builtins(void)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tfor_each_builtin(j, tgt_j) {\n+\t\ttgt_j->pfn_term();\n+\t}\n+}\n+\n+static int tr2main_exit_code;\n+\n+/*\n+ * Our atexit routine should run after everything has finished.\n+ *\n+ * Note that events generated here might not actually appear if\n+ * we are writing to fd 1 or 2 and our atexit routine runs after\n+ * the pager's atexit routine (since it closes them to shutdown\n+ * the pipes).\n+ */\n+static void tr2main_atexit_handler(void)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Clear any unbalanced regions so that our atexit message\n+\t * does not appear nested.  This improves the appearance of\n+\t * the trace output if someone calls die(), for example.\n+\t */\n+\ttr2tls_pop_unwind_self();\n+\t\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_atexit)\n+\t\t\ttgt_j->pfn_atexit(us_elapsed_absolute,\n+\t\t\t\t\t  tr2main_exit_code);\n+\t}\n+\n+\ttr2_tgt_disable_builtins();\n+\n+\ttr2tls_release();\n+\ttr2_sid_release();\n+\ttr2_verb_release();\n+\ttr2_cfg_free_patterns();\n+\n+\ttrace2_enabled = 0;\n+}\n+\n+static void tr2main_signal_handler(int signo)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_signal)\n+\t\t\ttgt_j->pfn_signal(us_elapsed_absolute, signo);\n+\t}\n+\n+\tsigchain_pop(signo);\n+\traise(signo);\n+}\n+\n+void trace2_initialize_fl(const char *file, int line)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (trace2_enabled)\n+\t\treturn;\n+\n+\tif (!tr2_tgt_want_builtins())\n+\t\treturn;\n+\ttrace2_enabled = 1;\n+\n+\ttr2_sid_get();\n+\n+\tatexit(tr2main_atexit_handler);\n+\tsigchain_push(SIGPIPE, tr2main_signal_handler);\n+\ttr2tls_init();\n+\n+\t/*\n+\t * Emit 'version' message on each active builtin target.\n+\t */\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_version_fl)\n+\t\t\ttgt_j->pfn_version_fl(file, line);\n+\t}\n+}\n+\n+int trace2_is_enabled(void)\n+{\n+\treturn trace2_enabled;\n+}\n+\n+void trace2_cmd_start_fl(const char *file, int line, const char **argv)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_start_fl)\n+\t\t\ttgt_j->pfn_start_fl(file, line, argv);\n+\t}\n+}\n+\n+int trace2_cmd_exit_fl(const char *file, int line, int code)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tcode &= 0xff;\n+\n+\tif (!trace2_enabled)\n+\t\treturn code;\n+\n+\ttr2main_exit_code = code;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_exit_fl)\n+\t\t\ttgt_j->pfn_exit_fl(file, line, us_elapsed_absolute, code);\n+\t}\n+\n+\treturn code;\n+}\n+\n+void trace2_cmd_error_va_fl(const char *file, int line,\n+\t\t\t    const char *fmt, va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\t/*\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy (because an 'ap' can only be walked once).\n+\t */\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_error_va_fl)\n+\t\t\ttgt_j->pfn_error_va_fl(file, line, fmt, ap);\n+\t}\n+}\n+\n+void trace2_cmd_path_fl(const char *file, int line, const char *pathname)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_command_path_fl)\n+\t\t\ttgt_j->pfn_command_path_fl(file, line, pathname);\n+\t}\n+}\n+\n+void trace2_cmd_verb_fl(const char *file, int line, const char *command_verb)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tconst char *hierarchy;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\ttr2_verb_append_hierarchy(command_verb);\n+\thierarchy = tr2_verb_get_hierarchy();\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_command_verb_fl)\n+\t\t\ttgt_j->pfn_command_verb_fl(file, line, command_verb,\n+\t\t\t\t\t\t   hierarchy);\n+\t}\n+}\n+\n+void trace2_cmd_subverb_fl(const char *file, int line,\n+\t\t\t   const char *command_subverb)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_command_subverb_fl)\n+\t\t\ttgt_j->pfn_command_subverb_fl(file, line,\n+\t\t\t\t\t\t      command_subverb);\n+\t}\n+}\n+\n+void trace2_cmd_alias_fl(const char *file, int line,\n+\t\t\t const char *alias, const char **argv)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_alias_fl)\n+\t\t\ttgt_j->pfn_alias_fl(file, line, alias, argv);\n+\t}\n+}\n+\n+void trace2_cmd_list_config_fl(const char *file, int line)\n+{\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\ttr2_cfg_list_config_fl(file, line);\n+}\n+\n+void trace2_cmd_set_config_fl(const char *file, int line,\n+\t\t\t      const char *key, const char *value)\n+{\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\ttr2_cfg_set_fl(file, line, key, value);\n+}\n+\n+void trace2_child_start_fl(const char *file, int line,\n+\t\t\t   struct child_process *cmd)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tcmd->trace2_child_id = tr2tls_locked_increment(&tr2_next_child_id);\n+\tcmd->trace2_child_us_start = us_now;\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_child_start_fl)\n+\t\t\ttgt_j->pfn_child_start_fl(file, line,\n+\t\t\t\t\t\t  us_elapsed_absolute, cmd);\n+\t}\n+}\n+\n+void trace2_child_exit_fl(const char *file, int line,\n+\t\t\t  struct child_process *cmd,\n+\t\t\t  int child_exit_code)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_child;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\t\n+\tif (cmd->trace2_child_us_start)\n+\t\tus_elapsed_child = us_now - cmd->trace2_child_us_start;\n+\telse\n+\t\tus_elapsed_child = 0;\n+\t\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_child_exit_fl)\n+\t\t\ttgt_j->pfn_child_exit_fl(\n+\t\t\t\tfile, line, us_elapsed_absolute,\n+\t\t\t\tcmd->trace2_child_id,\n+\t\t\t\tcmd->pid,\n+\t\t\t\tchild_exit_code, us_elapsed_child);\n+\t}\n+}\n+\n+int trace2_exec_fl(const char *file, int line,\n+\t\t   const char *exe, const char **argv)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tint exec_id;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn -1;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\texec_id = tr2tls_locked_increment(&tr2_next_exec_id);\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_exec_fl)\n+\t\t\ttgt_j->pfn_exec_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t   exec_id, exe, argv);\n+\t}\n+\n+\treturn exec_id;\n+}\n+\n+void trace2_exec_result_fl(const char *file, int line, int exec_id, int code)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_exec_result_fl)\n+\t\t\ttgt_j->pfn_exec_result_fl(file, line,\n+\t\t\t\t\t\t  us_elapsed_absolute,\n+\t\t\t\t\t\t  exec_id, code);\n+\t}\n+}\n+\n+void trace2_thread_start_fl(const char *file, int line,\n+\t\t\t    const char *thread_name)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tif (tr2tls_is_main_thread())\n+\t{\n+\t\t/*\n+\t\t * We should only be called from the new thread's thread-proc,\n+\t\t * so this is technically a bug.  But in those cases where the\n+\t\t * main thread also runs the thread-proc function (or when we\n+\t\t * are built with threading disabled), we need to allow it.\n+\t\t *\n+\t\t * Convert this call to a region-enter so the nesting looks\n+\t\t * looks correct.  \n+\t\t */\n+\t\ttrace2_region_enter_printf_fl(file, line, NULL, NULL, NULL,\n+\t\t\t\t\t      \"thread-proc on main: %s\",\n+\t\t\t\t\t      thread_name);\n+\t\treturn;\n+\t}\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\ttr2tls_create_self(thread_name);\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_thread_start_fl)\n+\t\t\ttgt_j->pfn_thread_start_fl(file, line,\n+\t\t\t\t\t\t   us_elapsed_absolute);\n+\t}\n+}\n+\n+void trace2_thread_exit_fl(const char *file, int line)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_thread;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tif (tr2tls_is_main_thread())\n+\t{\n+\t\t/*\n+\t\t * We should only be called from the exiting thread's thread-proc,\n+\t\t * so this is technically a bug.  But in those cases where the\n+\t\t * main thread also runs the thread-proc function (or when we\n+\t\t * are built with threading disabled), we need to allow it.\n+\t\t *\n+\t\t * Convert this call to a region-leave so the nesting looks\n+\t\t * looks correct.  \n+\t\t */\n+\t\ttrace2_region_leave_printf_fl(file, line, NULL, NULL, NULL,\n+\t\t\t\t\t      \"thread-proc on main\");\n+\t\treturn;\n+\t}\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Clear any unbalanced regions and then get the relative time\n+\t * for the outer-most region (which we pushed when the thread\n+\t * started).  This gives us the run time of the thread.\n+\t */\n+\ttr2tls_pop_unwind_self();\n+\tus_elapsed_thread = tr2tls_region_elasped_self(us_now);\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_thread_exit_fl)\n+\t\t\ttgt_j->pfn_thread_exit_fl(file, line,\n+\t\t\t\t\t\t  us_elapsed_absolute,\n+\t\t\t\t\t\t  us_elapsed_thread);\n+\t}\n+\n+\ttr2tls_unset_self();\n+}\n+\n+void trace2_def_param_fl(const char *file, int line,\n+\t\t\t const char *param, const char *value)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_param_fl)\n+\t\t\ttgt_j->pfn_param_fl(file, line, param, value);\n+\t}\n+}\n+\n+void trace2_def_repo_fl(const char *file, int line,\n+\t\t\tstruct repository *repo)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tif (repo->trace2_repo_id)\n+\t\treturn;\n+\n+\trepo->trace2_repo_id = tr2tls_locked_increment(&tr2_next_repo_id);\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_repo_fl)\n+\t\t\ttgt_j->pfn_repo_fl(file, line, repo);\n+\t}\n+}\n+\n+void trace2_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category,\n+\t\t\t\t      const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Print the region-enter message at the current nesting\n+\t * (indentation) level and then push a new level.\n+\t *\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy.\n+\t */\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_region_enter_printf_va_fl)\n+\t\t\ttgt_j->pfn_region_enter_printf_va_fl(\n+\t\t\t\tfile, line, us_elapsed_absolute,\n+\t\t\t\tcategory, label, repo, fmt, ap);\n+\t}\n+\n+\ttr2tls_push_self(us_now);\n+}\n+\n+void trace2_region_enter_fl(const char *file, int line,\n+\t\t\t    const char *category,\n+\t\t\t    const char *label,\n+\t\t\t    const struct repository *repo)\n+{\n+\ttrace2_region_enter_printf_va_fl(file, line,\n+\t\t\t\t\t category, label, repo,\n+\t\t\t\t\t NULL, NULL);\n+}\n+\n+void trace2_region_enter_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category,\n+\t\t\t\t   const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_enter_printf_va_fl(file, line,\n+\t\t\t\t\t category, label, repo,\n+\t\t\t\t\t fmt, ap);\n+\tva_end(ap);\n+}\n+\n+#ifndef HAVE_VARIADIC_MACROS\n+void trace2_region_enter_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo,\n+\t\t\t\tconst char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_enter_printf_va_fl(NULL, 0,\n+\t\t\t\t\t category, label, repo,\n+\t\t\t\t\t fmt, ap);\n+\tva_end(ap);\n+}\n+#endif\n+\n+void trace2_region_leave_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category,\n+\t\t\t\t      const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_region;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Get the elapsed time in the current region before we\n+\t * pop it off the stack.  Pop the stack.  And then print\n+\t * the perf message at the new (shallower) level so that\n+\t * it lines up with the corresponding push/enter.\n+\t */\n+\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n+\t\n+\ttr2tls_pop_self();\n+\n+\t/*\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy.\n+\t */\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_region_leave_printf_va_fl)\n+\t\t\ttgt_j->pfn_region_leave_printf_va_fl(\n+\t\t\t\tfile, line, us_elapsed_absolute,\n+\t\t\t\tus_elapsed_region,\n+\t\t\t\tcategory, label, repo,\n+\t\t\t\tfmt, ap);\n+\t}\n+}\n+\n+void trace2_region_leave_fl(const char *file, int line,\n+\t\t\t    const char *category,\n+\t\t\t    const char *label,\n+\t\t\t    const struct repository *repo)\n+{\n+\ttrace2_region_leave_printf_va_fl(file, line,\n+\t\t\t\t\t category, label, repo,\n+\t\t\t\t\t NULL, NULL);\n+}\n+\n+void trace2_region_leave_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category,\n+\t\t\t\t   const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_leave_printf_va_fl(file, line,\n+\t\t\t\t\t category, label, repo,\n+\t\t\t\t\t fmt, ap);\n+\tva_end(ap);\n+}\n+\n+#ifndef HAVE_VARIADIC_MACROS\n+void trace2_region_leave_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo,\n+\t\t\t\tconst char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_leave_printf_va_fl(NULL, 0,\n+\t\t\t\t\t category, label, repo,\n+\t\t\t\t\t fmt, ap);\n+\tva_end(ap);\n+}\n+#endif\n+\n+void trace2_data_string_fl(const char *file, int line,\n+\t\t\t   const char *category,\n+\t\t\t   const struct repository *repo,\n+\t\t\t   const char *key,\n+\t\t\t   const char *value)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_region;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_data_fl)\n+\t\t\ttgt_j->pfn_data_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t   us_elapsed_region,\n+\t\t\t\t\t   category, repo, key, value);\n+\t}\n+}\n+\n+void trace2_data_intmax_fl(const char *file, int line,\n+\t\t\t   const char *category,\n+\t\t\t   const struct repository *repo,\n+\t\t\t   const char *key,\n+\t\t\t   intmax_t value)\n+{\n+\tstruct strbuf buf_string = STRBUF_INIT;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tstrbuf_addf(&buf_string, \"%\"PRIdMAX, value);\n+\ttrace2_data_string_fl(file, line, category, repo, key, buf_string.buf);\n+\tstrbuf_release(&buf_string);\n+}\n+\n+void trace2_data_json_fl(const char *file, int line,\n+\t\t\t const char *category,\n+\t\t\t const struct repository *repo,\n+\t\t\t const char *key,\n+\t\t\t const struct json_writer *value)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_region;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_data_fl)\n+\t\t\ttgt_j->pfn_data_json_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t\tus_elapsed_region,\n+\t\t\t\t\t\tcategory, repo, key, value);\n+\t}\n+}\n+\n+void trace2_printf_va_fl(const char *file, int line,\n+\t\t\t const char *fmt, va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy.\n+\t */\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_printf_va_fl)\n+\t\t\ttgt_j->pfn_printf_va_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t\tfmt, ap);\n+\t}\n+}\n+\n+void trace2_printf_fl(const char *file, int line,\n+\t\t      const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_printf_va_fl(file, line, fmt, ap);\n+\tva_end(ap);\n+}\n+\n+#ifndef HAVE_VARIADIC_MACROS\n+void trace2_printf(const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_printf_va_fl(NULL, 0, fmt, ap);\n+\tva_end(ap);\n+}\n+#endif\ndiff --git a/trace2.h b/trace2.h\nnew file mode 100644\nindex 0000000000..7ab9f355f3\n--- /dev/null\n+++ b/trace2.h\n@@ -0,0 +1,390 @@\n+#ifndef TRACE2_H\n+#define TRACE2_H\n+\n+struct child_process;\n+struct repository;\n+struct json_writer;\n+\n+/*\n+ * The public TRACE2 routines are grouped into the following groups:\n+ *\n+ * [] trace2_initialize -- initialization.\n+ * [] trace2_cmd_*      -- emit command/control messages.\n+ * [] trace2_child*     -- emit child start/stop messages.\n+ * [] trace2_exec*      -- emit exec start/stop messages.\n+ * [] trace2_thread*    -- emit thread start/stop messages.\n+ * [] trace2_def*       -- emit definition/parameter mesasges.\n+ * [] trace2_region*    -- emit region nesting messages.\n+ * [] trace2_data*      -- emit region/thread/repo data messages.\n+ * [] trace2_printf*    -- legacy trace[1] messages.\n+ */\n+\n+/*\n+ * Initialize TRACE2 tracing facility if any of the builtin TRACE2\n+ * targets are enabled in the environment.  Emits a 'version' event.\n+ *\n+ * Cleanup/Termination is handled automatically by a registered\n+ * atexit() routine.\n+ */\n+void trace2_initialize_fl(const char *file, int line);\n+\n+#define trace2_initialize() trace2_initialize_fl(__FILE__, __LINE__)\n+\n+/*\n+ * Return true if trace2 is enabled.\n+ */\n+int trace2_is_enabled(void);\n+\n+/*\n+ * Emit a 'start' event with the original (unmodified) argv.\n+ */\n+void trace2_cmd_start_fl(const char *file, int line, const char **argv);\n+\n+#define trace2_cmd_start(argv) trace2_cmd_start_fl(__FILE__, __LINE__, (argv))\n+\n+/*\n+ * Emit an 'exit' event.\n+ *\n+ * Write the exit-code that will be passed to exit() or returned\n+ * from main().\n+ *\n+ * Use this prior to actually calling exit().\n+ * See \"#define exit()\" in git-compat-util.h\n+ */\n+int trace2_cmd_exit_fl(const char *file, int line, int code);\n+\n+#define trace2_cmd_exit(code) (trace2_cmd_exit_fl(__FILE__, __LINE__, (code)))\n+\n+/*\n+ * Emit an 'error' event.\n+ *\n+ * Write an error message to the TRACE2 targets.\n+ */\n+void trace2_cmd_error_va_fl(const char *file, int line,\n+\t\t\t    const char *fmt, va_list ap);\n+\n+#define trace2_cmd_error_va(fmt, ap) \\\n+\ttrace2_cmd_error_va_fl(__FILE__, __LINE__, (fmt), (ap))\n+\n+/*\n+ * Emit a 'pathname' event with the canonical pathname of the current process\n+ * This gives post-processors a simple field to identify the command without\n+ * having to parse the argv.  For example, to distinguish invocations from\n+ * installed versus debug executables.\n+ */\n+void trace2_cmd_path_fl(const char *file, int line, const char *pathname);\n+\n+#define trace2_cmd_path(p) \\\n+\ttrace2_cmd_path_fl(__FILE__, __LINE__, (p))\n+\n+/*\n+ * Emit a 'cmd_verb' event with the canonical name of the (usually)\n+ * builtin command.  This gives post-processors a simple field\n+ * to identify the command verb without having to parse the argv.\n+ */\n+void trace2_cmd_verb_fl(const char *file, int line, const char *command_verb);\n+\n+#define trace2_cmd_verb(v) \\\n+\ttrace2_cmd_verb_fl(__FILE__, __LINE__, (v))\n+\n+/*\n+ * Emit a 'cmd_subverb' event to further describe the command being run.\n+ * For example, \"checkout\" can checkout a single file or can checkout a\n+ * different branch.  This gives post-processors a simple field to compare\n+ * equivalent commands without having to parse the argv.\n+ */\n+void trace2_cmd_subverb_fl(const char *file, int line,\n+\t\t\t   const char *command_subverb);\n+\n+#define trace2_cmd_subverb(sv) \\\n+\ttrace2_cmd_subverb_fl(__FILE__, __LINE__, (sv))\n+\n+/*\n+ * Emit an 'alias' expansion event.\n+ */\n+void trace2_cmd_alias_fl(const char *file, int line,\n+\t\t\t const char *alias, const char **argv);\n+\n+#define trace2_cmd_alias(alias, argv) \\\n+\ttrace2_cmd_alias_fl(__FILE__, __LINE__, (alias), (argv))\n+\n+/*\n+ * Emit one or more 'def_param' events for \"interesting\" configuration\n+ * settings.\n+ *\n+ * The environment variable \"GIT_TR2_CONFIG_PARAMS\" can be set to a\n+ * list of patterns considered important.  For example:\n+ *\n+ *    GIT_TR2_CONFIG_PARAMS=\"core.*,remote.*.url\"\n+ *\n+ * Note: this routine does a read-only iteration on the config data\n+ * (using read_early_config()), so it must not be called until enough\n+ * of the process environment has been established.  This includes the\n+ * location of the git and worktree directories, expansion of any \"-c\"\n+ * and \"-C\" command line options, and etc.\n+ */\n+void trace2_cmd_list_config_fl(const char *file, int line);\n+\n+#define trace2_cmd_list_config() \\\n+\ttrace2_cmd_list_config_fl(__FILE__, __LINE__)\n+\n+/*\n+ * Emit a \"def_param\" event for the given config key/value pair IF\n+ * we consider the key to be \"interesting\".\n+ *\n+ * Use this for new/updated config settings created/updated after\n+ * trace2_cmd_list_config() is called.\n+ */\n+void trace2_cmd_set_config_fl(const char *file, int line,\n+\t\t\t      const char *key, const char *value);\n+\n+#define trace2_cmd_set_config(k, v) \\\n+\ttrace2_cmd_set_config_fl(__FILE__, __LINE__, (k), (v))\n+\n+/*\n+ * Emit a 'child_start' event prior to spawning a child process.\n+ *\n+ * Before calling optionally set \"cmd->trace2_child_class\" to a string\n+ * describing the type of the child process.  For example, \"editor\" or\n+ * \"pager\".\n+ */\n+void trace2_child_start_fl(const char *file, int line,\n+\t\t\t   struct child_process *cmd);\n+\n+#define trace2_child_start(cmd) \\\n+\ttrace2_child_start_fl(__FILE__, __LINE__, (cmd))\n+\n+/*\n+ * Emit a 'child_exit' event after the child process completes. \n+ */\n+void trace2_child_exit_fl(const char *file, int line,\n+\t\t\t  struct child_process *cmd,\n+\t\t\t  int child_exit_code);\n+\n+#define trace2_child_exit(cmd, code) \\\n+\ttrace2_child_exit_fl(__FILE__, __LINE__, (cmd), (code))\n+\n+/*\n+ * Emit an 'exec' event prior to calling one of exec(), execv(),\n+ * execvp(), and etc.  On Unix-derived systems, this will be the\n+ * last event emitted for the current process, unless the exec\n+ * fails.  On Windows, exec() behaves like 'child_start' and a\n+ * waitpid(), so additional events may be emitted.\n+ *\n+ * Returns the \"exec_id\".\n+ */\n+int trace2_exec_fl(const char *file, int line,\n+\t\t   const char *exe, const char **argv);\n+\n+#define trace2_exec(exe, argv) \\\n+\ttrace2_exec_fl(__FILE__, __LINE__, (exe), (argv))\n+\n+/*\n+ * Emit an 'exec_result' when possible.  On Unix-derived systems,\n+ * this should be called after exec() returns (which only happens\n+ * when there is an error starting the new process).  On Windows,\n+ * this should be called after the waitpid().\n+ *\n+ * The \"exec_id\" should be the value returned from trace2_exec().\n+ */\n+void trace2_exec_result_fl(const char *file, int line, int exec_id, int code);\n+\n+#define trace2_exec_result(id, code) \\\n+\ttrace2_exec_result_fl(__FILE__, __LINE__, (id), (code))\n+\n+/*\n+ * Emit a 'thread_start' event.  This must be called from inside the\n+ * thread-proc to set up the trace2 TLS data for the thread.\n+ *\n+ * Thread names should be descriptive, like \"preload_index\".\n+ * Thread names will be decorated with an instance number automatically.\n+ */\n+void trace2_thread_start_fl(const char *file, int line,\n+\t\t\t\t const char *thread_name);\n+\n+#define trace2_thread_start(thread_name) \\\n+\ttrace2_thread_start_fl(__FILE__, __LINE__, (thread_name))\n+\n+/*\n+ * Emit a 'thread_exit' event.  This must be called from inside the\n+ * thread-proc to report thread-specific data and cleanup TLS data\n+ * for the thread.\n+ */\n+void trace2_thread_exit_fl(const char *file, int line);\n+\n+#define trace2_thread_exit() trace2_thread_exit_fl(__FILE__, __LINE__)\n+\n+/*\n+ * Emit a 'param' event.\n+ *\n+ * Write a \"<param> = <value>\" pair describing some aspect of the\n+ * run such as an important configuration setting or command line\n+ * option that significantly changes command behavior.\n+ */ \n+void trace2_def_param_fl(const char *file, int line,\n+\t\t\t const char *param, const char *value);\n+\n+#define trace2_def_param(param, value) \\\n+\ttrace2_def_param_fl(__FILE__, __LINE__, (param), (value))\n+\n+/*\n+ * Tell trace2 about a newly instantiated repo object and assign\n+ * a trace2-repo-id to be used in subsequent activity events.\n+ *\n+ * Emits a 'worktree' event for this repo instance.\n+ */\n+void trace2_def_repo_fl(const char *file, int line,\n+\t\t\tstruct repository *repo);\n+\n+#define trace2_def_repo(repo) \\\n+\ttrace2_def_repo_fl(__FILE__, __LINE__, repo)\n+\n+/*\n+ * Emit a 'region_enter' event for <category>.<label> with optional\n+ * repo-id and printf message.\n+ *\n+ * Enter a new nesting level on the current thread and remember the\n+ * current time.  This controls the indenting of all subsequent events\n+ * on this thread.\n+ */\n+void trace2_region_enter_fl(const char *file, int line,\n+\t\t\t    const char *category,\n+\t\t\t    const char *label,\n+\t\t\t    const struct repository *repo);\n+\n+#define trace2_region_enter(category, label, repo) \\\n+\ttrace2_region_enter_fl( \\\n+\t\t__FILE__, __LINE__, (category), (label), (repo))\n+\n+void trace2_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category,\n+\t\t\t\t      const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap);\n+\n+#define trace2_region_enter_printf_va(category, label, repo, fmt, ap) \\\n+\ttrace2_region_enter_printf_va_fl( \\\n+\t\t__FILE__, __LINE__, (category), (label), (repo), (fmt), (ap))\n+\n+void trace2_region_enter_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category, const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...);\n+\n+#ifdef HAVE_VARIADIC_MACROS\n+#define trace2_region_enter_printf(category, label, repo, ...) \\\n+\ttrace2_region_enter_printf_fl( \\\n+\t\t__FILE__, __LINE__, (category), (label), (repo), __VA_ARGS__)\n+#else\n+__attribute__((format (region_enter_printf, 4, 5)))\n+void trace2_region_enter_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo,\n+\t\t\t\tconst char *fmt, ...);\n+#endif\n+\n+/*\n+ * Emit a 'region_leave' event for <category>.<label> with optional\n+ * repo-id and printf message.\n+ *\n+ * Leave current nesting level and report the elapsed time spent\n+ * in this nesting level.\n+ */\n+void trace2_region_leave_fl(const char *file, int line,\n+\t\t\t    const char *category,\n+\t\t\t    const char *label,\n+\t\t\t    const struct repository *repo);\n+\n+#define trace2_region_leave(category, label, repo) \\\n+\ttrace2_region_leave_fl( \\\n+\t\t__FILE__, __LINE__, (category), (label), (repo))\n+\n+void trace2_region_leave_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category,\n+\t\t\t\t      const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap);\n+\n+#define trace2_region_leave_printf_va(category, label, repo, fmt, ap) \\\n+\ttrace2_region_leave_printf_va_fl( \\\n+\t\t__FILE__, __LINE__, (category), (label), (repo), (fmt), (ap))\n+\n+void trace2_region_leave_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category, const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...);\n+\n+#ifdef HAVE_VARIADIC_MACROS\n+#define trace2_region_leave_printf(category, label, repo, ...) \\\n+\ttrace2_region_leave_printf_fl( \\\n+\t\t__FILE__, __LINE__, (category), (label), (repo), __VA_ARGS__)\n+#else\n+__attribute__((format (region_leave_printf, 4, 5)))\n+void trace2_region_leave_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo,\n+\t\t\t\tconst char *fmt, ...);\n+#endif\n+\n+/*\n+ * Emit a key-value pair 'data' event of the form <category>.<key> = <value>.\n+ * This event implicitly contains information about thread, nesting region,\n+ * and optional repo-id.\n+ *\n+ * On event-based TRACE2 targets, this generates a 'data' event suitable\n+ * for post-processing.  On printf-based TRACE2 targets, this is converted\n+ * into a fixed-format printf message.\n+ */\n+void trace2_data_string_fl(const char *file, int line,\n+\t\t\t   const char *category,\n+\t\t\t   const struct repository *repo,\n+\t\t\t   const char *key,\n+\t\t\t   const char *value);\n+\n+#define trace2_data_string(category, repo, key, value) \\\n+\ttrace2_data_string_fl( \\\n+\t\t__FILE__, __LINE__, (category), (repo), (key), (value))\n+\n+void trace2_data_intmax_fl(const char *file, int line,\n+\t\t\t   const char *category,\n+\t\t\t   const struct repository *repo,\n+\t\t\t   const char *key,\n+\t\t\t   intmax_t value);\n+\n+#define trace2_data_intmax(category, repo, key, value) \\\n+\ttrace2_data_intmax_fl( \\\n+\t\t__FILE__, __LINE__, (category), (repo), (key), (value))\n+\n+void trace2_data_json_fl(const char *file, int line,\n+\t\t\t const char *category,\n+\t\t\t const struct repository *repo,\n+\t\t\t const char *key,\n+\t\t\t const struct json_writer *jw);\n+\n+#define trace2_data_json(category, repo, key, value) \\\n+\ttrace2_data_json_fl( \\\n+\t\t__FILE__, __LINE__, (category), (repo), (key), (value))\n+\n+/*\n+ * Emit a 'printf' event.\n+ *\n+ * Write an arbitrary formatted message to the TRACE2 targets.  These\n+ * text messages should be considered as human-readable strings without\n+ * any formatting guidelines.  Post-processors may choose to ignore\n+ * them.\n+ */\n+void trace2_printf_va_fl(const char *file, int line,\n+\t\t\t const char *fmt, va_list ap);\n+\n+#define trace2_printf_va(fmt, ap) \\\n+\ttrace2_printf_va_fl(__FILE__, __LINE__, (fmt), (ap))\n+\n+void trace2_printf_fl(const char *file, int line, const char *fmt, ...);\n+\n+#ifdef HAVE_VARIADIC_MACROS\n+#define trace2_printf(...) \\\n+\ttrace2_printf_fl(__FILE__, __LINE__, __VA_ARGS__)\n+#else\n+__attribute__((format (printf, 1, 2)))\n+void trace2_printf(const char *fmt, ...);\n+#endif\n+\n+#endif /* TRACE2_H */\ndiff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c\nnew file mode 100644\nindex 0000000000..7b8291010f\n--- /dev/null\n+++ b/trace2/tr2_cfg.c\n@@ -0,0 +1,92 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"tr2_cfg.h\"\n+\n+#define TR2_ENVVAR_CFG_PARAM \"GIT_TR2_CONFIG_PARAMS\"\n+\n+static struct strbuf **tr2_cfg_patterns;\n+static int tr2_cfg_count_patterns;\n+static int tr2_cfg_loaded;\n+\n+/*\n+ * Parse a string containing a comma-delimited list of config keys\n+ * or wildcard patterns into a list of strbufs.\n+ */\n+static int tr2_cfg_load_patterns(void)\n+{\n+\tstruct strbuf **s;\n+\tconst char *envvar;\n+\n+\tif (tr2_cfg_loaded)\n+\t\treturn tr2_cfg_count_patterns;\n+\ttr2_cfg_loaded = 1;\n+\n+\tenvvar = getenv(TR2_ENVVAR_CFG_PARAM);\n+\tif (!envvar || !*envvar)\n+\t\treturn tr2_cfg_count_patterns;\n+\n+\ttr2_cfg_patterns = strbuf_split_buf(envvar, strlen(envvar), ',', -1);\n+\tfor (s = tr2_cfg_patterns; *s; s++) {\n+\t\tstruct strbuf *buf = *s;\n+\n+\t\tif (buf->len && buf->buf[buf->len - 1] == ',')\n+\t\t\tstrbuf_setlen(buf, buf->len - 1);\n+\t\tstrbuf_trim_trailing_newline(*s);\n+\t\tstrbuf_trim(*s);\n+\t}\n+\n+\ttr2_cfg_count_patterns = s - tr2_cfg_patterns;\n+\treturn tr2_cfg_count_patterns;\n+}\n+\n+void tr2_cfg_free_patterns(void)\n+{\n+\tif (tr2_cfg_patterns)\n+\t\tstrbuf_list_free(tr2_cfg_patterns);\n+\ttr2_cfg_count_patterns = 0;\n+\ttr2_cfg_loaded = 0;\n+}\n+\n+struct tr2_cfg_data\n+{\n+\tconst char *file;\n+\tint line;\n+};\n+\n+/*\n+ * See if the given config key matches any of our patterns of interest.\n+ */\n+static int tr2_cfg_cb(const char *key, const char *value, void *d)\n+{\n+\tstruct strbuf **s;\n+\tstruct tr2_cfg_data *data = (struct tr2_cfg_data *)d;\n+\n+\tfor (s = tr2_cfg_patterns; *s; s++) {\n+\t\tstruct strbuf *buf = *s;\n+\t\tint wm = wildmatch(buf->buf, key, WM_CASEFOLD);\n+\t\tif (wm == WM_MATCH) {\n+\t\t\ttrace2_def_param_fl(data->file, data->line, key, value);\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void tr2_cfg_list_config_fl(const char *file, int line)\n+{\n+\tstruct tr2_cfg_data data = { file, line };\n+\n+\tif (tr2_cfg_load_patterns() > 0)\n+\t\tread_early_config(tr2_cfg_cb, &data);\n+}\n+\n+void tr2_cfg_set_fl(const char *file, int line,\n+\t\t    const char *key, const char *value)\n+{\n+\tstruct tr2_cfg_data data = { file, line };\n+\n+\tif (tr2_cfg_load_patterns() > 0)\n+\t\ttr2_cfg_cb(key, value, &data);\n+}\n+\ndiff --git a/trace2/tr2_cfg.h b/trace2/tr2_cfg.h\nnew file mode 100644\nindex 0000000000..0e7ca4eace\n--- /dev/null\n+++ b/trace2/tr2_cfg.h\n@@ -0,0 +1,19 @@\n+#ifndef TR2_CFG_H\n+#define TR2_CFG_H\n+\n+/*\n+ * Iterate over all config settings and emit 'def_param' events for the\n+ * \"interesting\" ones to TRACE2.\n+ */\n+void tr2_cfg_list_config_fl(const char *file, int line);\n+\n+/*\n+ * Emit a \"def_param\" event for the given key/value pair IF we consider\n+ * the key to be \"interesting\".\n+ */\n+void tr2_cfg_set_fl(const char *file, int line,\n+\t\t    const char *key, const char *value);\n+\n+void tr2_cfg_free_patterns(void);\n+\n+#endif /* TR2_CFG_H */\ndiff --git a/trace2/tr2_dst.c b/trace2/tr2_dst.c\nnew file mode 100644\nindex 0000000000..485b2f8851\n--- /dev/null\n+++ b/trace2/tr2_dst.c\n@@ -0,0 +1,90 @@\n+#include \"cache.h\"\n+#include \"trace2/tr2_dst.h\"\n+\n+void tr2_dst_trace_disable(struct tr2_dst *dst)\n+{\n+\tif (dst->need_close)\n+\t\tclose(dst->fd);\n+\tdst->fd = 0;\n+\tdst->initialized = 1;\n+\tdst->need_close = 0;\n+}\n+\n+int tr2_dst_get_trace_fd(struct tr2_dst *dst)\n+{\n+\tconst char *trace;\n+\n+\t/* don't open twice */\n+\tif (dst->initialized)\n+\t\treturn dst->fd;\n+\n+\ttrace = getenv(dst->env_var_name);\n+\n+\tif (!trace || !strcmp(trace, \"\") ||\n+\t    !strcmp(trace, \"0\") || !strcasecmp(trace, \"false\"))\n+\t\tdst->fd = 0;\n+\telse if (!strcmp(trace, \"1\") || !strcasecmp(trace, \"true\"))\n+\t\tdst->fd = STDERR_FILENO;\n+\telse if (strlen(trace) == 1 && isdigit(*trace))\n+\t\tdst->fd = atoi(trace);\n+\telse if (is_absolute_path(trace)) {\n+\t\tint fd = open(trace, O_WRONLY | O_APPEND | O_CREAT, 0666);\n+\t\tif (fd == -1) {\n+\t\t\t/*\n+\t\t\t * Silently eat the error and disable tracing on this\n+\t\t\t * destination.  This will cause us to lose events,\n+\t\t\t * but that is better than causing a stream of warnings\n+\t\t\t * for each git command they run.\n+\t\t\t *\n+\t\t\t * warning(\"could not open '%s' for tracing: %s\",\n+\t\t\t *         trace, strerror(errno));\n+\t\t\t */\n+\t\t\ttr2_dst_trace_disable(dst);\n+\t\t} else {\n+\t\t\tdst->fd = fd;\n+\t\t\tdst->need_close = 1;\n+\t\t}\n+\t} else {\n+\t\twarning(\"unknown trace value for '%s': %s\\n\"\n+\t\t\t\"         If you want to trace into a file, then please set %s\\n\"\n+\t\t\t\"         to an absolute pathname (starting with /)\",\n+\t\t\tdst->env_var_name, trace, dst->env_var_name);\n+\t\ttr2_dst_trace_disable(dst);\n+\t}\n+\n+\tdst->initialized = 1;\n+\treturn dst->fd;\n+}\n+\n+int tr2_dst_trace_want(struct tr2_dst *dst)\n+{\n+\treturn !!tr2_dst_get_trace_fd(dst);\n+}\n+\n+void tr2_dst_write_line(struct tr2_dst *dst, struct strbuf *buf_line)\n+{\n+\tstrbuf_complete_line(buf_line); /* ensure final NL on buffer */\n+\n+\t/*\n+\t * We do not use write_in_full() because we do not want\n+\t * a short-write to try again.  We are using O_APPEND mode\n+\t * files and the kernel handles the atomic seek+write. If\n+\t * another thread or git process is concurrently writing to\n+\t * this fd or file, our remainder-write may not be contiguous\n+\t * with our initial write of this message.  And that will\n+\t * confuse readers.  So just don't bother.\n+\t *\n+\t * It is assumed that TRACE2 messages are short enough that\n+\t * the system can write them in 1 attempt and we won't see\n+\t * a short-write.\n+\t *\n+\t * If we get an IO error, just close the trace dst.\n+\t */\n+\n+\tif (write(tr2_dst_get_trace_fd(dst),\n+\t\t  buf_line->buf, buf_line->len) < 0) {\n+\t\twarning(\"unable to write trace for '%s': %s\",\n+\t\t\tdst->env_var_name, strerror(errno));\n+\t\ttr2_dst_trace_disable(dst);\n+\t}\n+}\ndiff --git a/trace2/tr2_dst.h b/trace2/tr2_dst.h\nnew file mode 100644\nindex 0000000000..3eea3f2d1a\n--- /dev/null\n+++ b/trace2/tr2_dst.h\n@@ -0,0 +1,34 @@\n+#ifndef TR2_DST_H\n+#define TR2_DST_H\n+\n+struct tr2_dst {\n+\tconst char * const env_var_name;\n+\tint fd;\n+\tunsigned int initialized : 1;\n+\tunsigned int  need_close : 1;\n+};\n+\n+/*\n+ * Disable TRACE2 on the destination.  In TRACE2 a destination (DST)\n+ * wraps a file descriptor; it is associated with a TARGET which\n+ * defines the formatting.\n+ */\n+void tr2_dst_trace_disable(struct tr2_dst *dst);\n+\n+/*\n+ * Return the file descriptor for the DST.\n+ * If 0, the dst is closed or disabled.\n+ */\n+int tr2_dst_get_trace_fd(struct tr2_dst *dst);\n+\n+/*\n+ * Return true if the DST is opened for writing.\n+ */\n+int tr2_dst_trace_want(struct tr2_dst *dst);\n+\n+/*\n+ * Write a single line/message to the trace file.\n+ */\n+void tr2_dst_write_line(struct tr2_dst *dst, struct strbuf *buf_line);\n+\n+#endif /* TR2_DST_H */\ndiff --git a/trace2/tr2_sid.c b/trace2/tr2_sid.c\nnew file mode 100644\nindex 0000000000..43e8a3e184\n--- /dev/null\n+++ b/trace2/tr2_sid.c\n@@ -0,0 +1,67 @@\n+#include \"cache.h\"\n+#include \"trace2/tr2_sid.h\"\n+\n+#define TR2_ENVVAR_PARENT_SID \"GIT_TR2_PARENT_SID\"\n+\n+static struct strbuf tr2sid_buf = STRBUF_INIT;\n+static int tr2sid_nr_git_parents;\n+\n+/*\n+ * Compute a \"unique\" session id (SID) for the current process.  This allows\n+ * all events from this process to have a single label (much like a PID).\n+ *\n+ * Export this into our environment so that all child processes inherit it.\n+ *\n+ * If we were started by another git instance, use our parent's SID as a\n+ * prefix.  (This lets us track parent/child relationships even if there\n+ * is an intermediate shell process.)\n+ *\n+ * Additionally, count the number of nested git processes.\n+ */\n+static void tr2_sid_compute(void)\n+{\n+\tuint64_t us_now;\n+\tconst char *parent_sid;\n+\n+\tif (tr2sid_buf.len)\n+\t\treturn;\n+\n+\tparent_sid = getenv(TR2_ENVVAR_PARENT_SID);\n+\tif (parent_sid && *parent_sid) {\n+\t\tconst char *p;\n+\t\tfor (p = parent_sid; *p; p++)\n+\t\t\tif (*p == '/')\n+\t\t\t\ttr2sid_nr_git_parents++;\n+\n+\t\tstrbuf_addstr(&tr2sid_buf, parent_sid);\n+\t\tstrbuf_addch(&tr2sid_buf, '/');\n+\t\ttr2sid_nr_git_parents++;\n+\t}\n+\n+\tus_now = getnanotime() / 1000;\n+\tstrbuf_addf(&tr2sid_buf, \"%\"PRIuMAX\"-%\"PRIdMAX,\n+\t\t    (uintmax_t)us_now, (intmax_t)getpid());\n+\n+\tsetenv(TR2_ENVVAR_PARENT_SID, tr2sid_buf.buf, 1);\n+}\n+\n+const char *tr2_sid_get(void)\n+{\n+\tif (!tr2sid_buf.len)\n+\t\ttr2_sid_compute();\n+\n+\treturn tr2sid_buf.buf;\n+}\n+\n+int tr2_sid_depth(void)\n+{\n+\tif (!tr2sid_buf.len)\n+\t\ttr2_sid_compute();\n+\n+\treturn tr2sid_nr_git_parents;\n+}\n+\n+void tr2_sid_release(void)\n+{\n+\tstrbuf_release(&tr2sid_buf);\n+}\ndiff --git a/trace2/tr2_sid.h b/trace2/tr2_sid.h\nnew file mode 100644\nindex 0000000000..9bef321708\n--- /dev/null\n+++ b/trace2/tr2_sid.h\n@@ -0,0 +1,18 @@\n+#ifndef TR2_SID_H\n+#define TR2_SID_H\n+\n+/*\n+ * Get our session id. Compute if necessary.\n+ */\n+const char *tr2_sid_get(void);\n+\n+/*\n+ * Get our process depth.  A top-level git process invoked from the\n+ * command line will have depth=0.  A child git process will have\n+ * depth=1 and so on.\n+ */\n+int tr2_sid_depth(void);\n+\n+void tr2_sid_release(void);\n+\n+#endif /* TR2_SID_H */\ndiff --git a/trace2/tr2_tbuf.c b/trace2/tr2_tbuf.c\nnew file mode 100644\nindex 0000000000..452b4d0520\n--- /dev/null\n+++ b/trace2/tr2_tbuf.c\n@@ -0,0 +1,32 @@\n+#include \"cache.h\"\n+#include \"tr2_tbuf.h\"\n+\n+void tr2_tbuf_local_time(struct tr2_tbuf *tb)\n+{\n+\tstruct timeval tv;\n+\tstruct tm tm;\n+\ttime_t secs;\n+\n+\tgettimeofday(&tv, NULL);\n+\tsecs = tv.tv_sec;\n+\tlocaltime_r(&secs, &tm);\n+\n+\txsnprintf(tb->buf, sizeof(tb->buf), \"%02d:%02d:%02d.%06ld\",\n+\t\t  tm.tm_hour, tm.tm_min, tm.tm_sec, (long)tv.tv_usec);\n+}\n+\n+void tr2_tbuf_utc_time(struct tr2_tbuf *tb)\n+{\n+\tstruct timeval tv;\n+\tstruct tm tm;\n+\ttime_t secs;\n+\n+\tgettimeofday(&tv, NULL);\n+\tsecs = tv.tv_sec;\n+\tgmtime_r(&secs, &tm);\n+\n+\txsnprintf(tb->buf, sizeof(tb->buf), \"%4d-%02d-%02d %02d:%02d:%02d.%06ld\",\n+\t\t  tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,\n+\t\t  tm.tm_hour, tm.tm_min, tm.tm_sec, (long)tv.tv_usec);\n+}\n+\ndiff --git a/trace2/tr2_tbuf.h b/trace2/tr2_tbuf.h\nnew file mode 100644\nindex 0000000000..9cdefa3957\n--- /dev/null\n+++ b/trace2/tr2_tbuf.h\n@@ -0,0 +1,23 @@\n+#ifndef TR2_TBUF_H\n+#define TR2_TBUF_H\n+\n+/*\n+ * A simple wrapper around a fixed buffer to avoid C syntax\n+ * quirks and the need to pass around an additional size_t\n+ * argument.\n+ */\n+struct tr2_tbuf {\n+\tchar buf[32];\n+};\n+\n+/*\n+ * Fill buffer with formatted local time string.\n+ */\n+void tr2_tbuf_local_time(struct tr2_tbuf *tb);\n+\n+/*\n+ * Fill buffer with formatted UTC time string.\n+ */\n+void tr2_tbuf_utc_time(struct tr2_tbuf *tb);\n+\n+#endif /* TR2_TBUF_H */\ndiff --git a/trace2/tr2_tgt.h b/trace2/tr2_tgt.h\nnew file mode 100644\nindex 0000000000..4fdf253b57\n--- /dev/null\n+++ b/trace2/tr2_tgt.h\n@@ -0,0 +1,126 @@\n+#ifndef TR2_TGT_H\n+#define TR2_TGT_H\n+\n+/*\n+ * Function prototypes for a TRACE2 \"target\" vtable.\n+ */\n+\n+typedef int (tr2_tgt_init_t)(void);\n+typedef void (tr2_tgt_term_t)(void);\n+\n+typedef void (tr2_tgt_evt_version_fl_t)\n+\t(const char *file, int line);\n+\n+typedef void (tr2_tgt_evt_start_fl_t)\n+\t(const char *file, int line, const char **argv);\n+typedef void (tr2_tgt_evt_exit_fl_t)\n+\t(const char *file, int line, uint64_t us_elapsed_absolute, int code);\n+typedef void (tr2_tgt_evt_signal_t)\n+\t(uint64_t us_elapsed_absolute, int signo);\n+typedef void (tr2_tgt_evt_atexit_t)\n+\t(uint64_t us_elapsed_absolute, int code);\n+\n+typedef void (tr2_tgt_evt_error_va_fl_t)\n+\t(const char *file, int line, const char *fmt, va_list ap);\n+\n+typedef void (tr2_tgt_evt_command_path_fl_t)\n+\t(const char *file, int line, const char *command_path);\n+typedef void (tr2_tgt_evt_command_verb_fl_t)\n+\t(const char *file, int line, const char *command_verb,\n+\t const char *hierarchy);\n+typedef void (tr2_tgt_evt_command_subverb_fl_t)\n+\t(const char *file, int line, const char *command_subverb);\n+\n+typedef void (tr2_tgt_evt_alias_fl_t)\n+\t(const char *file, int line, const char *alias, const char **argv);\n+\n+typedef void (tr2_tgt_evt_child_start_fl_t)\n+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t const struct child_process *cmd);\n+typedef void (tr2_tgt_evt_child_exit_fl_t)\n+\t(const char *file, int line, uint64_t us_elapsed_absolute, int cid,\n+\t int pid, int code, uint64_t us_elapsed_child);\n+\n+typedef void (tr2_tgt_evt_thread_start_fl_t)\n+\t(const char *file, int line, uint64_t us_elapsed_absolute);\n+typedef void (tr2_tgt_evt_thread_exit_fl_t)\n+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t uint64_t us_elapsed_thread);\n+\n+typedef void (tr2_tgt_evt_exec_fl_t)\n+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t int exec_id, const char *exe, const char **argv);\n+typedef void (tr2_tgt_evt_exec_result_fl_t)\n+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t int exec_id, int code);\n+\n+typedef void (tr2_tgt_evt_param_fl_t)\n+\t(const char *file, int line, const char *param, const char *value);\n+\n+typedef void (tr2_tgt_evt_repo_fl_t)\n+\t(const char *file, int line, const struct repository *repo);\n+\n+typedef void (tr2_tgt_evt_region_enter_printf_va_fl_t)\n+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t const char *category, const char *label, const struct repository *repo,\n+\t const char *fmt, va_list ap);\n+typedef void (tr2_tgt_evt_region_leave_printf_va_fl_t)\n+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t uint64_t us_elapsed_region, const char *category, const char *label,\n+\t const struct repository *repo, const char *fmt, va_list ap);\n+\n+typedef void (tr2_tgt_evt_data_fl_t)\n+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t uint64_t us_elapsed_region, const char *category,\n+\t const struct repository *repo, const char *key, const char *value);\n+typedef void (tr2_tgt_evt_data_json_fl_t)\n+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t uint64_t us_elapsed_region, const char *category,\n+\t const struct repository *repo,\n+\t const char *key, const struct json_writer *value);\n+\n+typedef void (tr2_tgt_evt_printf_va_fl_t)\n+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t const char *fmt, va_list ap);\n+\n+/*\n+ * \"vtable\" for a TRACE2 target.  Use NULL if a target does not want\n+ * to emit that message.\n+ */\n+struct tr2_tgt\n+{\n+\tstruct tr2_dst                          *pdst;\n+\n+\ttr2_tgt_init_t                          *pfn_init;\n+\ttr2_tgt_term_t                          *pfn_term;\n+\n+\ttr2_tgt_evt_version_fl_t                *pfn_version_fl;\n+\ttr2_tgt_evt_start_fl_t                  *pfn_start_fl;\n+\ttr2_tgt_evt_exit_fl_t                   *pfn_exit_fl;\n+\ttr2_tgt_evt_signal_t                    *pfn_signal;\n+\ttr2_tgt_evt_atexit_t                    *pfn_atexit;\n+\ttr2_tgt_evt_error_va_fl_t               *pfn_error_va_fl;\n+\ttr2_tgt_evt_command_path_fl_t           *pfn_command_path_fl;\n+\ttr2_tgt_evt_command_verb_fl_t           *pfn_command_verb_fl;\n+\ttr2_tgt_evt_command_subverb_fl_t        *pfn_command_subverb_fl;\n+\ttr2_tgt_evt_alias_fl_t                  *pfn_alias_fl;\n+\ttr2_tgt_evt_child_start_fl_t            *pfn_child_start_fl;\n+\ttr2_tgt_evt_child_exit_fl_t             *pfn_child_exit_fl;\n+\ttr2_tgt_evt_thread_start_fl_t           *pfn_thread_start_fl;\n+\ttr2_tgt_evt_thread_exit_fl_t            *pfn_thread_exit_fl;\n+\ttr2_tgt_evt_exec_fl_t                   *pfn_exec_fl;\n+\ttr2_tgt_evt_exec_result_fl_t            *pfn_exec_result_fl;\n+\ttr2_tgt_evt_param_fl_t                  *pfn_param_fl;\n+\ttr2_tgt_evt_repo_fl_t                   *pfn_repo_fl;\n+\ttr2_tgt_evt_region_enter_printf_va_fl_t *pfn_region_enter_printf_va_fl;\n+\ttr2_tgt_evt_region_leave_printf_va_fl_t *pfn_region_leave_printf_va_fl;\n+\ttr2_tgt_evt_data_fl_t                   *pfn_data_fl;\n+\ttr2_tgt_evt_data_json_fl_t              *pfn_data_json_fl;\n+\ttr2_tgt_evt_printf_va_fl_t              *pfn_printf_va_fl;\n+};\n+\n+extern struct tr2_tgt tr2_tgt_event;\n+extern struct tr2_tgt tr2_tgt_normal;\n+extern struct tr2_tgt tr2_tgt_perf;\n+\n+#endif /* TR2_TGT_H */\ndiff --git a/trace2/tr2_tgt_event.c b/trace2/tr2_tgt_event.c\nnew file mode 100644\nindex 0000000000..495171ae6d\n--- /dev/null\n+++ b/trace2/tr2_tgt_event.c\n@@ -0,0 +1,606 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"json-writer.h\"\n+#include \"run-command.h\"\n+#include \"version.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_tbuf.h\"\n+#include \"trace2/tr2_sid.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+static struct tr2_dst tr2dst_event  = { \"GIT_TR2_EVENT\", 0, 0, 0 };\n+\n+/*\n+ * The version number of the JSON data generated by the EVENT target\n+ * in this source file.  Update this if you make a significant change\n+ * to the JSON fields or message structure.  You probably do not need\n+ * to update this if you just add another call to one of the existing\n+ * TRACE2 API methods.\n+ */\n+#define TR2_EVENT_VERSION \"1\"\n+\n+/*\n+ * Region nesting limit for messages written to the event target.\n+ *\n+ * The \"region_enter\" and \"region_leave\" messages (especially recursive\n+ * messages such as those produced while diving the worktree or index)\n+ * are primarily intended for the performance target during debugging.\n+ *\n+ * Some of the outer-most messages, however, may be of interest to the\n+ * event target.  Set this environment variable to a larger integer for\n+ * more detail in the event target.\n+ */\n+#define TR2_ENVVAR_EVENT_NESTING \"GIT_TR2_EVENT_NESTING\"\n+static int tr2env_event_nesting_wanted = 2;\n+\n+/*\n+ * Set this environment variable to true to omit the <time>, <file>, and\n+ * <line> fields from most events.\n+ */\n+#define TR2_ENVVAR_EVENT_BRIEF \"GIT_TR2_EVENT_BRIEF\"\n+static int tr2env_event_brief;\n+\n+static int fn_init(void)\n+{\n+\tint want = tr2_dst_trace_want(&tr2dst_event);\n+\tint want_nesting;\n+\tint want_brief;\n+\tchar *nesting;\n+\tchar *brief;\n+\n+\tif (!want)\n+\t\treturn want;\n+\n+\tnesting = getenv(TR2_ENVVAR_EVENT_NESTING);\n+\tif (nesting && ((want_nesting = atoi(nesting)) > 0))\n+\t\ttr2env_event_nesting_wanted = want_nesting;\n+\n+\tbrief = getenv(TR2_ENVVAR_EVENT_BRIEF);\n+\tif (brief && ((want_brief = atoi(brief)) > 0))\n+\t\ttr2env_event_brief = want_brief;\n+\n+\treturn want;\n+}\n+\n+static void fn_term(void)\n+{\n+\ttr2_dst_trace_disable(&tr2dst_event);\n+}\n+\n+/*\n+ * Append common key-value pairs to the currently open JSON object.\n+ *     \"event:\"<event_name>\"\n+ *      \"sid\":\"<sid>\"\n+ *   \"thread\":\"<thread_name>\"\n+ *     \"time\":\"<time>\"\n+ *     \"file\":\"<filename>\"\n+ *     \"line\":<line_number>\n+ *     \"repo\":<repo_id>\n+ */\n+static void event_fmt_prepare(const char *event_name,\n+\t\t\t      const char *file, int line,\n+\t\t\t      const struct repository *repo,\n+\t\t\t      struct json_writer *jw)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tstruct tr2_tbuf tb_now;\n+\n+\tjw_object_string(jw, \"event\", event_name);\n+\tjw_object_string(jw, \"sid\", tr2_sid_get());\n+\tjw_object_string(jw, \"thread\", ctx->thread_name.buf);\n+\n+\t/*\n+\t * In brief mode, only emit <time> on these 2 event types.\n+\t */\n+\tif (!tr2env_event_brief ||\n+\t    !strcmp(event_name, \"version\") ||\n+\t    !strcmp(event_name, \"atexit\")) {\n+\t\ttr2_tbuf_utc_time(&tb_now);\n+\t\tjw_object_string(jw, \"time\", tb_now.buf);\n+\t}\n+\n+\tif (!tr2env_event_brief && file && *file) {\n+\t\tjw_object_string(jw, \"file\", file);\n+\t\tjw_object_intmax(jw, \"line\", line);\n+\t}\n+\n+\tif (repo)\n+\t\tjw_object_intmax(jw, \"repo\", repo->trace2_repo_id);\n+}\n+\n+static void fn_version_fl(const char *file, int line)\n+{\n+\tconst char *event_name = \"version\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"evt\", TR2_EVENT_VERSION);\n+\tjw_object_string(&jw, \"exe\", git_version_string);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_start_fl(const char *file, int line, const char **argv)\n+{\n+\tconst char *event_name = \"start\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tjw_array_argv(&jw, argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_exit_fl(const char *file, int line,\n+\t\t       uint64_t us_elapsed_absolute, int code)\n+{\n+\tconst char *event_name = \"exit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_signal(uint64_t us_elapsed_absolute, int signo)\n+{\n+\tconst char *event_name = \"signal\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, __FILE__, __LINE__, NULL, &jw);\n+\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\tjw_object_intmax(&jw, \"signo\", signo);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_atexit(uint64_t us_elapsed_absolute, int code)\n+{\n+\tconst char *event_name = \"atexit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, __FILE__, __LINE__, NULL, &jw);\n+\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void maybe_add_string_va(struct json_writer *jw,\n+\t\t\t\tconst char *field_name,\n+\t\t\t\tconst char *fmt, va_list ap)\n+{\n+\tif (fmt && *fmt && ap) {\n+\t\tva_list copy_ap;\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\n+\t\tva_copy(copy_ap, ap);\n+\t\tstrbuf_vaddf(&buf, fmt, copy_ap);\n+\t\tva_end(copy_ap);\n+\n+\t\tjw_object_string(jw, field_name, buf.buf);\n+\t\tstrbuf_release(&buf);\n+\t\treturn;\n+\t}\n+\n+\tif (fmt && *fmt) {\n+\t\tjw_object_string(jw, field_name, fmt);\n+\t\treturn;\n+\t}\n+}\n+\n+static void fn_error_va_fl(const char *file, int line,\n+\t\t\t   const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"error\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tmaybe_add_string_va(&jw, \"msg\", fmt, ap);\n+\t/*\n+\t * Also emit the format string as a field in case\n+\t * post-processors want to aggregate common error\n+\t * messages by type without argument fields (such\n+\t * as pathnames or branch names) cluttering it up.\n+\t */\n+\tif (fmt && *fmt)\n+\t\tjw_object_string(&jw, \"fmt\", fmt);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_command_path_fl(const char *file, int line,\n+\t\t\t       const char *pathname)\n+{\n+\tconst char *event_name = \"cmd_path\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"path\", pathname);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_command_verb_fl(const char *file, int line,\n+\t\t\t       const char *command_verb,\n+\t\t\t       const char *verb_hierarchy)\n+{\n+\tconst char *event_name = \"cmd_verb\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"name\", command_verb);\n+\tif (verb_hierarchy && *verb_hierarchy)\n+\t\tjw_object_string(&jw, \"hierarchy\", verb_hierarchy);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_command_subverb_fl(const char *file, int line,\n+\t\t\t       const char *command_subverb)\n+{\n+\tconst char *event_name = \"cmd_subverb\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"name\", command_subverb);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_alias_fl(const char *file, int line,\n+\t\t\tconst char *alias, const char **argv)\n+{\n+\tconst char *event_name = \"alias\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"alias\", alias);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tjw_array_argv(&jw, argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_child_start_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      const struct child_process *cmd)\n+{\n+\tconst char *event_name = \"child_start\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"child_id\", cmd->trace2_child_id);\n+\tif (cmd->trace2_hook_name) {\n+\t\tjw_object_string(&jw, \"child_class\", \"hook\");\n+\t\tjw_object_string(&jw, \"hook_name\", cmd->trace2_hook_name);\n+\t} else {\n+\t\tconst char *child_class = ((cmd->trace2_child_class) ?\n+\t\t\t\t\t   cmd->trace2_child_class : \"?\");\n+\t\tjw_object_string(&jw, \"child_class\", child_class);\n+\t}\n+\tif (cmd->dir)\n+\t\tjw_object_string(&jw, \"cd\", cmd->dir);\n+\tjw_object_bool(&jw, \"use_shell\", cmd->use_shell);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tif (cmd->git_cmd)\n+\t\tjw_array_string(&jw, \"git\");\n+\tjw_array_argv(&jw, cmd->argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_child_exit_fl(const char *file, int line,\n+\t\t\t     uint64_t us_elapsed_absolute,\n+\t\t\t     int cid, int pid, int code,\n+\t\t\t     uint64_t us_elapsed_child)\n+{\n+\tconst char *event_name = \"child_exit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_rel = (double)us_elapsed_child / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"child_id\", cid);\n+\tjw_object_intmax(&jw, \"pid\", pid);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\n+\tjw_release(&jw);\n+}\n+\n+static void fn_thread_start_fl(const char *file, int line,\n+\t\t\t       uint64_t us_elapsed_absolute)\n+{\n+\tconst char *event_name = \"thread_start\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_thread_exit_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      uint64_t us_elapsed_thread)\n+{\n+\tconst char *event_name = \"thread_exit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_rel = (double)us_elapsed_thread / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_exec_fl(const char *file, int line,\n+\t\t       uint64_t us_elapsed_absolute,\n+\t\t       int exec_id, const char *exe, const char **argv)\n+{\n+\tconst char *event_name = \"exec\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"exec_id\", exec_id);\n+\tif (exe)\n+\t\tjw_object_string(&jw, \"exe\", exe);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tjw_array_argv(&jw, argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_exec_result_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      int exec_id, int code)\n+{\n+\tconst char *event_name = \"exec_result\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"exec_id\", exec_id);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_param_fl(const char *file, int line,\n+\t\t\tconst char *param, const char *value)\n+{\n+\tconst char *event_name = \"def_param\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"param\", param);\n+\tjw_object_string(&jw, \"value\", value);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_repo_fl(const char *file, int line,\n+\t\t       const struct repository *repo)\n+{\n+\tconst char *event_name = \"def_repo\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\tjw_object_string(&jw, \"worktree\", repo->worktree);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t const char *category,\n+\t\t\t\t\t const char *label,\n+\t\t\t\t\t const struct repository *repo,\n+\t\t\t\t\t const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_enter\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(\n+\t\t\tevent_name, file, line, repo, &jw);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tif (category)\n+\t\t\tjw_object_string(&jw, \"category\", category);\n+\t\tif (label)\n+\t\t\tjw_object_string(&jw, \"label\", label);\n+\t\tmaybe_add_string_va(&jw, \"msg\", fmt, ap);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+static void fn_region_leave_printf_va_fl(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t uint64_t us_elapsed_region,\n+\t\t\t\t\t const char *category,\n+\t\t\t\t\t const char *label,\n+\t\t\t\t\t const struct repository *repo,\n+\t\t\t\t\t const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_leave\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\t\tdouble t_rel = (double)us_elapsed_region / 1000000.0;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tif (category)\n+\t\t\tjw_object_string(&jw, \"category\", category);\n+\t\tif (label)\n+\t\t\tjw_object_string(&jw, \"label\", label);\n+\t\tmaybe_add_string_va(&jw, \"msg\", fmt, ap);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+static void fn_data_fl(const char *file, int line,\n+\t\t       uint64_t us_elapsed_absolute,\n+\t\t       uint64_t us_elapsed_region,\n+\t\t       const char *category,\n+\t\t       const struct repository *repo,\n+\t\t       const char *key,\n+\t\t       const char *value)\n+{\n+\tconst char *event_name = \"data\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\t\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\t\tdouble t_rel = (double)us_elapsed_region / 1000000.0;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\t\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tjw_object_string(&jw, \"category\", category);\n+\t\tjw_object_string(&jw, \"key\", key);\n+\t\tjw_object_string(&jw, \"value\", value);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+static void fn_data_json_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute,\n+\t\t\t    uint64_t us_elapsed_region,\n+\t\t\t    const char *category,\n+\t\t\t    const struct repository *repo,\n+\t\t\t    const char *key,\n+\t\t\t    const struct json_writer *value)\n+{\n+\tconst char *event_name = \"data_json\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\t\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\t\tdouble t_rel = (double)us_elapsed_region / 1000000.0;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\t\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tjw_object_string(&jw, \"category\", category);\n+\t\tjw_object_string(&jw, \"key\", key);\n+\t\tjw_object_sub_jw(&jw, \"value\", value);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+struct tr2_tgt tr2_tgt_event =\n+{\n+\t&tr2dst_event,\n+\n+\tfn_init,\n+\tfn_term,\n+\n+\tfn_version_fl,\n+\tfn_start_fl,\n+\tfn_exit_fl,\n+\tfn_signal,\n+\tfn_atexit,\n+\tfn_error_va_fl,\n+\tfn_command_path_fl,\n+\tfn_command_verb_fl,\n+\tfn_command_subverb_fl,\n+\tfn_alias_fl,\n+\tfn_child_start_fl,\n+\tfn_child_exit_fl,\n+\tfn_thread_start_fl,\n+\tfn_thread_exit_fl,\n+\tfn_exec_fl,\n+\tfn_exec_result_fl,\n+\tfn_param_fl,\n+\tfn_repo_fl,\n+\tfn_region_enter_printf_va_fl,\n+\tfn_region_leave_printf_va_fl,\n+\tfn_data_fl,\n+\tfn_data_json_fl,\n+\tNULL, /* printf */\n+};\ndiff --git a/trace2/tr2_tgt_normal.c b/trace2/tr2_tgt_normal.c\nnew file mode 100644\nindex 0000000000..602eba5112\n--- /dev/null\n+++ b/trace2/tr2_tgt_normal.c\n@@ -0,0 +1,331 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"run-command.h\"\n+#include \"quote.h\"\n+#include \"version.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_tbuf.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+static struct tr2_dst tr2dst_normal = { \"GIT_TR2\", 0, 0, 0 };\n+\n+/*\n+ * Set this environment variable to true to omit the \"<time> <file>:<line>\"\n+ * fields from each line written to the builtin normal target.\n+ *\n+ * Unit tests may want to use this to help with testing.\n+ */\n+#define TR2_ENVVAR_NORMAL_BRIEF \"GIT_TR2_BRIEF\"\n+static int tr2env_normal_brief;\n+\n+#define TR2FMT_NORMAL_FL_WIDTH       (50)\n+\n+static int fn_init(void)\n+{\n+\tint want = tr2_dst_trace_want(&tr2dst_normal);\n+\tint want_brief;\n+\tchar *brief;\n+\n+\tif (!want)\n+\t\treturn want;\n+\n+\tbrief = getenv(TR2_ENVVAR_NORMAL_BRIEF);\n+\tif (brief && *brief &&\n+\t    ((want_brief = git_parse_maybe_bool(brief)) != -1))\n+\t\ttr2env_normal_brief = want_brief;\n+\n+\treturn want;\n+}\n+\n+static void fn_term(void)\n+{\n+\ttr2_dst_trace_disable(&tr2dst_normal);\n+}\n+\n+static void normal_fmt_prepare(const char *file, int line, struct strbuf *buf)\n+{\n+\tstrbuf_setlen(buf,0);\n+\n+\tif (!tr2env_normal_brief) {\n+\t\tstruct tr2_tbuf tb_now;\n+\n+\t\ttr2_tbuf_local_time(&tb_now);\n+\t\tstrbuf_addstr(buf, tb_now.buf);\n+\t\tstrbuf_addch(buf, ' ');\n+\n+\t\tif (file && *file)\n+\t\t\tstrbuf_addf(buf, \"%s:%d \", file, line);\n+\t\twhile (buf->len < TR2FMT_NORMAL_FL_WIDTH)\n+\t\t\tstrbuf_addch(buf, ' ');\n+\t}\n+}\n+\n+static void normal_io_write_fl(const char *file, int line,\n+\t\t\t       const struct strbuf *buf_payload)\n+{\n+\tstruct strbuf buf_line = STRBUF_INIT;\n+\n+\tnormal_fmt_prepare(file, line, &buf_line);\n+\tstrbuf_addbuf(&buf_line, buf_payload);\n+\ttr2_dst_write_line(&tr2dst_normal, &buf_line);\n+\tstrbuf_release(&buf_line);\n+}\n+\n+static void fn_version_fl(const char *file, int line)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"version %s\", git_version_string);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_start_fl(const char *file, int line, const char **argv)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"start \");\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exit_fl(const char *file, int line,\n+\t\t       uint64_t us_elapsed_absolute, int code)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"exit elapsed:%.6f code:%d\",\n+\t\t    elapsed, code);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_signal(uint64_t us_elapsed_absolute, int signo)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"signal elapsed:%.6f code:%d\",\n+\t\t    elapsed, signo);\n+\tnormal_io_write_fl(__FILE__, __LINE__, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_atexit(uint64_t us_elapsed_absolute, int code)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"atexit elapsed:%.6f code:%d\",\n+\t\t    elapsed, code);\n+\tnormal_io_write_fl(__FILE__, __LINE__, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void maybe_append_string_va(struct strbuf *buf,\n+\t\t\t\t   const char *fmt, va_list ap)\n+{\n+\tif (fmt && *fmt && ap) {\n+\t\tva_list copy_ap;\n+\n+\t\tva_copy(copy_ap, ap);\n+\t\tstrbuf_vaddf(buf, fmt, copy_ap);\n+\t\tva_end(copy_ap);\n+\t\treturn;\n+\t}\n+\n+\tif (fmt && *fmt) {\n+\t\tstrbuf_addstr(buf, fmt);\n+\t\treturn;\n+\t}\n+}\n+\n+static void fn_error_va_fl(const char *file, int line,\n+\t\t\t   const char *fmt, va_list ap)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"error \");\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_path_fl(const char *file, int line,\n+\t\t\t       const char *pathname)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"cmd_path %s\", pathname);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_verb_fl(const char *file, int line,\n+\t\t\t       const char *command_verb,\n+\t\t\t       const char *verb_hierarchy)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"cmd_verb %s\", command_verb);\n+\tif (verb_hierarchy && *verb_hierarchy)\n+\t\tstrbuf_addf(&buf_payload, \" (%s)\", verb_hierarchy);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_subverb_fl(const char *file, int line,\n+\t\t\t       const char *command_subverb)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"cmd_subverb %s\", command_subverb);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_alias_fl(const char *file, int line, const char *alias,\n+\t\t\tconst char **argv)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"alias %s ->\", alias);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_start_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      const struct child_process *cmd)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"child_start[%d] \", cmd->trace2_child_id);\n+\n+\tif (cmd->dir) {\n+\t\tstrbuf_addstr(&buf_payload, \" cd\");\n+\t\tsq_quote_buf_pretty(&buf_payload, cmd->dir);\n+\t\tstrbuf_addstr(&buf_payload, \"; \");\n+\t}\n+\n+\t/*\n+\t * TODO if (cmd->env) { Consider dumping changes to environment. }\n+\t * See trace_add_env() in run-command.c as used by original trace.c\n+\t */\n+\n+\tif (cmd->git_cmd)\n+\t\tstrbuf_addstr(&buf_payload, \"git\");\n+\tsq_quote_argv_pretty(&buf_payload, cmd->argv);\n+\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_exit_fl(const char *file, int line,\n+\t\t\t     uint64_t us_elapsed_absolute,\n+\t\t\t     int cid, int pid, int code,\n+\t\t\t     uint64_t us_elapsed_child)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_child / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"child_exit[%d] pid:%d code:%d elapsed:%.6f\",\n+\t\t    cid, pid, code, elapsed);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_fl(const char *file, int line,\n+\t\t       uint64_t us_elapsed_absolute,\n+\t\t       int exec_id, const char *exe, const char **argv)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"exec[%d] \", exec_id);\n+\tif (exe)\n+\t\tstrbuf_addstr(&buf_payload, exe);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_result_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      int exec_id, int code)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"exec_result[%d] code:%d\", exec_id, code);\n+\tif (code > 0)\n+\t\tstrbuf_addf(&buf_payload, \" err:%s\", strerror(code));\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_param_fl(const char *file, int line, const char *param,\n+\t\t\tconst char *value)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"def_param %s=%s\", param, value);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_repo_fl(const char *file, int line,\n+\t\t       const struct repository *repo)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"worktree \");\n+\tsq_quote_buf_pretty(&buf_payload, repo->worktree);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_printf_va_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute,\n+\t\t\t    const char *fmt, va_list ap)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+struct tr2_tgt tr2_tgt_normal =\n+{\n+\t&tr2dst_normal,\n+\n+\tfn_init,\n+\tfn_term,\n+\n+\tfn_version_fl,\n+\tfn_start_fl,\n+\tfn_exit_fl,\n+\tfn_signal,\n+\tfn_atexit,\n+\tfn_error_va_fl,\n+\tfn_command_path_fl,\n+\tfn_command_verb_fl,\n+\tfn_command_subverb_fl,\n+\tfn_alias_fl,\n+\tfn_child_start_fl,\n+\tfn_child_exit_fl,\n+\tNULL, /* thread_start */\n+\tNULL, /* thread_exit */\n+\tfn_exec_fl,\n+\tfn_exec_result_fl,\n+\tfn_param_fl,\n+\tfn_repo_fl,\n+\tNULL, /* region_enter */\n+\tNULL, /* region_leave */\n+\tNULL, /* data */\n+\tNULL, /* data_json */\n+\tfn_printf_va_fl,\n+};\ndiff --git a/trace2/tr2_tgt_perf.c b/trace2/tr2_tgt_perf.c\nnew file mode 100644\nindex 0000000000..035855d883\n--- /dev/null\n+++ b/trace2/tr2_tgt_perf.c\n@@ -0,0 +1,573 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"run-command.h\"\n+#include \"quote.h\"\n+#include \"version.h\"\n+#include \"json-writer.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_sid.h\"\n+#include \"trace2/tr2_tbuf.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+static struct tr2_dst tr2dst_perf   = { \"GIT_TR2_PERF\", 0, 0, 0 };\n+\n+/*\n+ * Set this environment variable to true to omit the \"<time> <file>:<line>\"\n+ * fields from each line written to the builtin performance target.\n+ *\n+ * Unit tests may want to use this to help with testing.\n+ */\n+#define TR2_ENVVAR_PERF_BRIEF \"GIT_TR2_PERF_BRIEF\"\n+static int tr2env_perf_brief;\n+\n+#define TR2FMT_PERF_FL_WIDTH       (50)\n+#define TR2FMT_PERF_MAX_EVENT_NAME (12)\n+#define TR2FMT_PERF_REPO_WIDTH     (4)\n+#define TR2FMT_PERF_CATEGORY_WIDTH (10)\n+\n+#define TR2_DOTS_BUFFER_SIZE (100)\n+#define TR2_INDENT (2)\n+#define TR2_INDENT_LENGTH(ctx) (((ctx)->nr_open_regions - 1) * TR2_INDENT)\n+\n+static struct strbuf dots = STRBUF_INIT;\n+\n+static int fn_init(void)\n+{\n+\tint want = tr2_dst_trace_want(&tr2dst_perf);\n+\tint want_brief;\n+\tchar *brief;\n+\n+\tif (!want)\n+\t\treturn want;\n+\n+\tstrbuf_addchars(&dots, '.', TR2_DOTS_BUFFER_SIZE);\n+\n+\tbrief = getenv(TR2_ENVVAR_PERF_BRIEF);\n+\tif (brief && *brief &&\n+\t    ((want_brief = git_parse_maybe_bool(brief)) != -1))\n+\t\ttr2env_perf_brief = want_brief;\n+\n+\treturn want;\n+}\n+\n+static void fn_term(void)\n+{\n+\ttr2_dst_trace_disable(&tr2dst_perf);\n+\n+\tstrbuf_release(&dots);\n+}\n+\n+/*\n+ * Format trace line prefix in human-readable classic format for\n+ * the performance target:\n+ *     \"[<time> [<file>:<line>] <bar>] <nr_parents> <bar>\n+ *         <thread_name> <bar> <event_name> <bar> [<repo>] <bar>\n+ *         [<elapsed_absolute>] [<elapsed_relative>] <bar>\n+ *         [<category>] <bar> [<dots>] \"\n+ */\n+static void perf_fmt_prepare(\n+\tconst char *event_name, struct tr2tls_thread_ctx *ctx,\n+\tconst char *file, int line, const struct repository *repo,\n+\tuint64_t *p_us_elapsed_absolute, uint64_t *p_us_elapsed_relative,\n+\tconst char *category, struct strbuf *buf)\n+{\n+\tint len;\n+\n+\tstrbuf_setlen(buf,0);\n+\n+\tif (!tr2env_perf_brief) {\n+\n+\t\tstruct tr2_tbuf tb_now;\n+\n+\t\ttr2_tbuf_local_time(&tb_now);\n+\t\tstrbuf_addstr(buf, tb_now.buf);\n+\t\tstrbuf_addch(buf, ' ');\n+\n+\t\tif (file && *file)\n+\t\t\tstrbuf_addf(buf, \"%s:%d \", file, line);\n+\t\twhile (buf->len < TR2FMT_PERF_FL_WIDTH)\n+\t\t\tstrbuf_addch(buf, ' ');\n+\n+\t\tstrbuf_addstr(buf, \"| \");\n+\t}\n+\n+\tstrbuf_addf(buf, \"d%d | \", tr2_sid_depth());\n+\tstrbuf_addf(buf, \"%-*s | %-*s | \",\n+\t\t    TR2_MAX_THREAD_NAME, ctx->thread_name.buf,\n+\t\t    TR2FMT_PERF_MAX_EVENT_NAME, event_name);\n+\n+\tlen = buf->len + TR2FMT_PERF_REPO_WIDTH;\n+\tif (repo)\n+\t\tstrbuf_addf(buf, \"r%d \", repo->trace2_repo_id);\n+\twhile (buf->len < len)\n+\t\tstrbuf_addch(buf, ' ' );\n+\tstrbuf_addstr(buf, \"| \");\n+\n+\tif (p_us_elapsed_absolute)\n+\t\tstrbuf_addf(buf, \"%9.6f | \",\n+\t\t\t    ((double)(*p_us_elapsed_absolute)) / 1000000.0);\n+\telse\n+\t\tstrbuf_addf(buf, \"%9s | \", \" \");\n+\n+\tif (p_us_elapsed_relative)\n+\t\tstrbuf_addf(buf, \"%9.6f | \",\n+\t\t\t    ((double)(*p_us_elapsed_relative)) / 1000000.0);\n+\telse\n+\t\tstrbuf_addf(buf, \"%9s | \", \" \");\n+\t\t\n+\tstrbuf_addf(buf, \"%-*s | \", TR2FMT_PERF_CATEGORY_WIDTH,\n+\t\t    (category ? category : \"\"));\n+\n+\tif (ctx->nr_open_regions > 0) {\n+\t\tint len_indent = TR2_INDENT_LENGTH(ctx);\n+\t\twhile (len_indent > dots.len) {\n+\t\t\tstrbuf_addf(buf, \"%s\", dots.buf);\n+\t\t\tlen_indent -= dots.len;\n+\t\t}\n+\t\tstrbuf_addf(buf, \"%.*s\", len_indent, dots.buf);\n+\t}\n+}\n+\n+static void perf_io_write_fl(\n+\tconst char *file, int line,\n+\tconst char *event_name,\n+\tconst struct repository *repo,\n+\tuint64_t *p_us_elapsed_absolute,\n+\tuint64_t *p_us_elapsed_relative,\n+\tconst char *category,\n+\tconst struct strbuf *buf_payload)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tstruct strbuf buf_line = STRBUF_INIT;\n+\n+\tperf_fmt_prepare(event_name, ctx, file, line, repo,\n+\t\t\t p_us_elapsed_absolute, p_us_elapsed_relative,\n+\t\t\t category, &buf_line);\n+\tstrbuf_addbuf(&buf_line, buf_payload);\n+\ttr2_dst_write_line(&tr2dst_perf, &buf_line);\n+\tstrbuf_release(&buf_line);\n+}\n+\n+static void fn_version_fl(const char *file, int line)\n+{\n+\tconst char *event_name = \"version\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"%s\", git_version_string);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_start_fl(const char *file, int line, const char **argv)\n+{\n+\tconst char *event_name = \"start\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exit_fl(const char *file, int line,\n+\t\t       uint64_t us_elapsed_absolute, int code)\n+{\n+\tconst char *event_name = \"exit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"code:%d\", code);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_signal(uint64_t us_elapsed_absolute, int signo)\n+{\n+\tconst char *event_name = \"signal\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"signo:%d\", signo);\n+\n+\tperf_io_write_fl(__FILE__, __LINE__, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_atexit(uint64_t us_elapsed_absolute, int code)\n+{\n+\tconst char *event_name = \"atexit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"code:%d\", code);\n+\n+\tperf_io_write_fl(__FILE__, __LINE__, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void maybe_append_string_va(struct strbuf *buf,\n+\t\t\t\t   const char *fmt, va_list ap)\n+{\n+\tif (fmt && *fmt && ap) {\n+\t\tva_list copy_ap;\n+\n+\t\tva_copy(copy_ap, ap);\n+\t\tstrbuf_vaddf(buf, fmt, copy_ap);\n+\t\tva_end(copy_ap);\n+\t\treturn;\n+\t}\n+\n+\tif (fmt && *fmt) {\n+\t\tstrbuf_addstr(buf, fmt);\n+\t\treturn;\n+\t}\n+}\n+\n+static void fn_error_va_fl(const char *file, int line,\n+\t\t\t   const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"error\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_path_fl(const char *file, int line,\n+\t\t\t       const char *pathname)\n+{\n+\tconst char *event_name = \"cmd_path\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, pathname);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_verb_fl(const char *file, int line,\n+\t\t\t       const char *command_verb,\n+\t\t\t       const char *verb_hierarchy)\n+{\n+\tconst char *event_name = \"cmd_verb\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, command_verb);\n+\tif (verb_hierarchy && *verb_hierarchy)\n+\t\tstrbuf_addf(&buf_payload, \" (%s)\", verb_hierarchy);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_subverb_fl(const char *file, int line,\n+\t\t\t       const char *command_subverb)\n+{\n+\tconst char *event_name = \"cmd_subverb\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, command_subverb);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_alias_fl(const char *file, int line, const char *alias,\n+\t\t\tconst char **argv)\n+{\n+\tconst char *event_name = \"alias\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"alias:%s argv:\", alias);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_start_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      const struct child_process *cmd)\n+{\n+\tconst char *event_name = \"child_start\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tif (cmd->trace2_hook_name) {\n+\t\tstrbuf_addf(&buf_payload, \"[ch%d] class:hook hook:%s\",\n+\t\t\t    cmd->trace2_child_id, cmd->trace2_hook_name);\n+\t} else {\n+\t\tconst char *child_class = ((cmd->trace2_child_class) ?\n+\t\t\t\t\t   cmd->trace2_child_class : \"?\");\n+\t\tstrbuf_addf(&buf_payload, \"[ch%d] class:%s\",\n+\t\t\t    cmd->trace2_child_id, child_class);\n+\t}\n+\n+\tif (cmd->dir) {\n+\t\tstrbuf_addstr(&buf_payload, \" cd:\");\n+\t\tsq_quote_buf_pretty(&buf_payload, cmd->dir);\n+\t}\n+\n+\tstrbuf_addstr(&buf_payload, \" argv:\");\n+\tif (cmd->git_cmd)\n+\t\tstrbuf_addstr(&buf_payload, \" git\");\n+\tsq_quote_argv_pretty(&buf_payload, cmd->argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_exit_fl(const char *file, int line,\n+\t\t\t     uint64_t us_elapsed_absolute,\n+\t\t\t     int cid, int pid, int code,\n+\t\t\t     uint64_t us_elapsed_child)\n+{\n+\tconst char *event_name = \"child_exit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"[ch%d] pid:%d code:%d\", cid, pid, code);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, &us_elapsed_child, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_thread_start_fl(const char *file, int line,\n+\t\t\t\t     uint64_t us_elapsed_absolute)\n+{\n+\tconst char *event_name = \"thread_start\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_thread_exit_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      uint64_t us_elapsed_thread)\n+{\n+\tconst char *event_name = \"thread_exit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, &us_elapsed_thread, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_fl(const char *file, int line,\n+\t\t       uint64_t us_elapsed_absolute,\n+\t\t       int exec_id, const char *exe, const char **argv)\n+{\n+\tconst char *event_name = \"exec\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"id:%d \", exec_id);\n+\tstrbuf_addstr(&buf_payload, \"argv:\");\n+\tif (exe)\n+\t\tstrbuf_addf(&buf_payload, \" %s\", exe);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_result_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      int exec_id, int code)\n+{\n+\tconst char *event_name = \"exec_result\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"id:%d code:%d\", exec_id, code);\n+\tif (code > 0)\n+\t\tstrbuf_addf(&buf_payload, \" err:%s\", strerror(code));\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_param_fl(const char *file, int line,\n+\t\t\tconst char *param, const char *value)\n+{\n+\tconst char *event_name = \"def_param\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"%s:%s\", param, value);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_repo_fl(const char *file, int line,\n+\t\t       const struct repository *repo)\n+{\n+\tconst char *event_name = \"def_repo\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"worktree:\");\n+\tsq_quote_buf_pretty(&buf_payload, repo->worktree);\n+\n+\tperf_io_write_fl(file, line, event_name, repo,\n+\t\t\t NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t const char *category,\n+\t\t\t\t\t const char *label,\n+\t\t\t\t\t const struct repository *repo,\n+\t\t\t\t\t const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_enter\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tif (label)\n+\t\tstrbuf_addf(&buf_payload, \"label:%s \", label);\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, repo,\n+\t\t\t &us_elapsed_absolute, NULL, category,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_region_leave_printf_va_fl(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t uint64_t us_elapsed_region,\n+\t\t\t\t\t const char *category,\n+\t\t\t\t\t const char *label,\n+\t\t\t\t\t const struct repository *repo,\n+\t\t\t\t\t const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_leave\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tif (label)\n+\t\tstrbuf_addf(&buf_payload, \"label:%s \", label);\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, repo,\n+\t\t\t &us_elapsed_absolute, &us_elapsed_region, category,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_data_fl(const char *file, int line,\n+\t\t       uint64_t us_elapsed_absolute,\n+\t\t       uint64_t us_elapsed_region,\n+\t\t       const char *category,\n+\t\t       const struct repository *repo,\n+\t\t       const char *key,\n+\t\t       const char *value)\n+{\n+\tconst char *event_name = \"data\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"%s:%s\", key, value);\n+\n+\tperf_io_write_fl(file, line, event_name, repo,\n+\t\t\t &us_elapsed_absolute, &us_elapsed_region, category,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_data_json_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute,\n+\t\t\t    uint64_t us_elapsed_region,\n+\t\t\t    const char *category,\n+\t\t\t    const struct repository *repo,\n+\t\t\t    const char *key,\n+\t\t\t    const struct json_writer *value)\n+{\n+\tconst char *event_name = \"data_json\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"%s:%s\", key, value->json.buf);\n+\n+\tperf_io_write_fl(file, line, event_name, repo,\n+\t\t\t &us_elapsed_absolute, &us_elapsed_region, category,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_printf_va_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute,\n+\t\t\t    const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"printf\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+struct tr2_tgt tr2_tgt_perf =\n+{\n+\t&tr2dst_perf,\n+\n+\tfn_init,\n+\tfn_term,\n+\n+\tfn_version_fl,\n+\tfn_start_fl,\n+\tfn_exit_fl,\n+\tfn_signal,\n+\tfn_atexit,\n+\tfn_error_va_fl,\n+\tfn_command_path_fl,\n+\tfn_command_verb_fl,\n+\tfn_command_subverb_fl,\n+\tfn_alias_fl,\n+\tfn_child_start_fl,\n+\tfn_child_exit_fl,\n+\tfn_thread_start_fl,\n+\tfn_thread_exit_fl,\n+\tfn_exec_fl,\n+\tfn_exec_result_fl,\n+\tfn_param_fl,\n+\tfn_repo_fl,\n+\tfn_region_enter_printf_va_fl,\n+\tfn_region_leave_printf_va_fl,\n+\tfn_data_fl,\n+\tfn_data_json_fl,\n+\tfn_printf_va_fl,\n+};\ndiff --git a/trace2/tr2_tls.c b/trace2/tr2_tls.c\nnew file mode 100644\nindex 0000000000..f5e304428c\n--- /dev/null\n+++ b/trace2/tr2_tls.c\n@@ -0,0 +1,164 @@\n+#include \"cache.h\"\n+#include \"thread-utils.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+/*\n+ * Initialize size of the thread stack for nested regions.\n+ * This is used to store nested region start times.  Note that\n+ * this stack is per-thread and not per-trace-key.\n+ */\n+#define TR2_REGION_NESTING_INITIAL_SIZE (100)\n+\n+static struct tr2tls_thread_ctx *tr2tls_thread_main;\n+static uint64_t tr2tls_us_start_main;\n+\n+static pthread_mutex_t tr2tls_mutex;\n+static pthread_key_t tr2tls_key;\n+\n+static int tr2_next_thread_id; /* modify under lock */\n+\n+struct tr2tls_thread_ctx *tr2tls_create_self(const char *thread_name)\n+{\n+\tuint64_t us_now = getnanotime() / 1000;\n+\tstruct tr2tls_thread_ctx *ctx = xcalloc(1, sizeof(*ctx));\n+\n+\t/*\n+\t * Implicitly \"tr2tls_push_self()\" to capture the thread's start\n+\t * time in array_us_start[0].  For the main thread this gives us the\n+\t * application run time.\n+\t */\n+\tctx->alloc = TR2_REGION_NESTING_INITIAL_SIZE;\n+\tctx->array_us_start = (uint64_t*)xcalloc(ctx->alloc, sizeof(uint64_t));\n+\tctx->array_us_start[ctx->nr_open_regions++] = us_now;\n+\n+\tctx->thread_id = tr2tls_locked_increment(&tr2_next_thread_id);\n+\t\n+\tstrbuf_init(&ctx->thread_name, 0);\n+\tif (ctx->thread_id)\n+\t\tstrbuf_addf(&ctx->thread_name, \"th%02d:\", ctx->thread_id);\n+\tstrbuf_addstr(&ctx->thread_name, thread_name);\n+\tif (ctx->thread_name.len > TR2_MAX_THREAD_NAME)\n+\t\tstrbuf_setlen(&ctx->thread_name, TR2_MAX_THREAD_NAME);\n+\n+\tpthread_setspecific(tr2tls_key, ctx);\n+\n+\treturn ctx;\n+}\n+\n+struct tr2tls_thread_ctx *tr2tls_get_self(void)\n+{\n+\tstruct tr2tls_thread_ctx * ctx = pthread_getspecific(tr2tls_key);\n+\n+\t/*\n+\t * If the thread-proc did not call trace2_thread_start(), we won't\n+\t * have any TLS data associated with the current thread.  Fix it\n+\t * here and silently continue.\n+\t */\n+\tif (!ctx)\n+\t\tctx = tr2tls_create_self(\"unknown\");\n+\n+\treturn ctx;\n+}\n+\n+int tr2tls_is_main_thread(void)\n+{\n+\tstruct tr2tls_thread_ctx * ctx = pthread_getspecific(tr2tls_key);\n+\n+\treturn ctx == tr2tls_thread_main;\n+}\n+\n+void tr2tls_unset_self(void)\n+{\n+\tstruct tr2tls_thread_ctx * ctx;\n+\n+\tctx = tr2tls_get_self();\n+\n+\tpthread_setspecific(tr2tls_key, NULL);\n+\n+\tfree(ctx->array_us_start);\n+\tfree(ctx);\n+}\n+\n+void tr2tls_push_self(uint64_t us_now)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\n+\tALLOC_GROW(ctx->array_us_start, ctx->nr_open_regions + 1, ctx->alloc);\n+\tctx->array_us_start[ctx->nr_open_regions++] = us_now;\n+}\n+\n+void tr2tls_pop_self(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\n+\tif (!ctx->nr_open_regions)\n+\t\tBUG(\"no open regions in thread '%s'\", ctx->thread_name.buf);\n+\n+\tctx->nr_open_regions--;\n+}\n+\n+void tr2tls_pop_unwind_self(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\n+\twhile (ctx->nr_open_regions > 1)\n+\t\ttr2tls_pop_self();\n+}\n+\n+uint64_t tr2tls_region_elasped_self(uint64_t us)\n+{\n+\tstruct tr2tls_thread_ctx *ctx;\n+\tuint64_t us_start;\n+\n+\tctx = tr2tls_get_self();\n+\tif (!ctx->nr_open_regions)\n+\t\treturn 0;\n+\t\n+\tus_start = ctx->array_us_start[ctx->nr_open_regions - 1];\n+\n+\treturn us - us_start;\n+}\n+\n+uint64_t tr2tls_absolute_elapsed(uint64_t us)\n+{\n+\tif (!tr2tls_thread_main)\n+\t\treturn 0;\n+\n+\treturn us - tr2tls_us_start_main;\n+}\n+\n+void tr2tls_init(void)\n+{\n+\tpthread_key_create(&tr2tls_key, NULL);\n+\tinit_recursive_mutex(&tr2tls_mutex);\n+\n+\ttr2tls_thread_main = tr2tls_create_self(\"main\");\n+\t/*\n+\t * Keep a copy of the absolute start time of the main thread\n+\t * in a fixed variable since other threads need to access it.\n+\t * This also eliminates the need to lock accesses to the main\n+\t * thread's array (because of reallocs).\n+\t */\n+\ttr2tls_us_start_main = tr2tls_thread_main->array_us_start[0];\n+}\n+\n+void tr2tls_release(void)\n+{\n+\ttr2tls_unset_self();\n+\ttr2tls_thread_main = NULL;\n+\n+\tpthread_mutex_destroy(&tr2tls_mutex);\n+\tpthread_key_delete(tr2tls_key);\n+}\n+\n+int tr2tls_locked_increment(int *p)\n+{\n+\tint current_value;\n+\n+\tpthread_mutex_lock(&tr2tls_mutex);\n+\tcurrent_value = *p;\n+\t*p = current_value + 1;\n+\tpthread_mutex_unlock(&tr2tls_mutex);\n+\n+\treturn current_value;\n+}\ndiff --git a/trace2/tr2_tls.h b/trace2/tr2_tls.h\nnew file mode 100644\nindex 0000000000..99ea9018ce\n--- /dev/null\n+++ b/trace2/tr2_tls.h\n@@ -0,0 +1,95 @@\n+#ifndef TR2_TLS_H\n+#define TR2_TLS_H\n+\n+/*\n+ * Arbitry limit for thread names for column alignment.\n+ */\n+#define TR2_MAX_THREAD_NAME (24)\n+\n+struct tr2tls_thread_ctx {\n+\tstruct strbuf thread_name;\n+\tuint64_t *array_us_start;\n+\tint alloc;\n+\tint nr_open_regions; /* plays role of \"nr\" in ALLOC_GROW */\n+\tint thread_id;\n+};\n+\n+/*\n+ * Create TLS data for the current thread.  This gives us a place to\n+ * put per-thread data, such as thread start time, function nesting\n+ * and a per-thread label for our messages.\n+ *\n+ * We assume the first thread is \"main\".  Other threads are given\n+ * non-zero thread-ids to help distinguish messages from concurrent\n+ * threads.\n+ *\n+ * Truncate the thread name if necessary to help with column alignment\n+ * in printf-style messages.\n+ *\n+ * In this and all following functions the term \"self\" refers to the\n+ * current thread.\n+ */\n+struct tr2tls_thread_ctx *tr2tls_create_self(const char *thread_name);\n+\n+/*\n+ * Get our TLS data.\n+ */\n+struct tr2tls_thread_ctx *tr2tls_get_self(void);\n+\n+/*\n+ * return true if the current thread is the main thread.\n+ */\n+int tr2tls_is_main_thread(void);\n+\n+/*\n+ * Free our TLS data.\n+ */\n+void tr2tls_unset_self(void);\n+\n+/*\n+ * Begin a new nested region and remember the start time.\n+ */\n+void tr2tls_push_self(uint64_t us_now);\n+\n+/*\n+ * End the innermost nested region.\n+ */\n+void tr2tls_pop_self(void);\n+\n+/*\n+ * Pop any extra (above the first) open regions on the current\n+ * thread and discard.  During a thread-exit, we should only\n+ * have region[0] that was pushed in trace2_thread_start() if\n+ * the thread exits normally.\n+ */\n+void tr2tls_pop_unwind_self(void);\n+\n+/*\n+ * Compute the elapsed time since the innermost region in the\n+ * current thread started and the given time (usually now).\n+ */\n+uint64_t tr2tls_region_elasped_self(uint64_t us);\n+\n+/*\n+ * Compute the elapsed time since the main thread started\n+ * and the given time (usually now).  This is assumed to\n+ * be the absolute run time of the process.\n+ */\n+uint64_t tr2tls_absolute_elapsed(uint64_t us);\n+\n+/*\n+ * Initialize the tr2 TLS system.\n+ */\n+void tr2tls_init(void);\n+\n+/*\n+ * Free all tr2 TLS resources.\n+ */\n+void tr2tls_release(void);\n+\n+/*\n+ * Protected increment of an integer.\n+ */\n+int tr2tls_locked_increment(int *p);\n+\n+#endif /* TR2_TLS_H */\ndiff --git a/trace2/tr2_verb.c b/trace2/tr2_verb.c\nnew file mode 100644\nindex 0000000000..52be27c29f\n--- /dev/null\n+++ b/trace2/tr2_verb.c\n@@ -0,0 +1,30 @@\n+#include \"cache.h\"\n+#include \"trace2/tr2_verb.h\"\n+\n+#define TR2_ENVVAR_PARENT_VERB \"GIT_TR2_PARENT_VERB\"\n+\n+static struct strbuf tr2verb_hierarchy = STRBUF_INIT;\n+\n+void tr2_verb_append_hierarchy(const char *verb)\n+{\n+\tconst char *parent_verb = getenv(TR2_ENVVAR_PARENT_VERB);\n+\n+\tstrbuf_reset(&tr2verb_hierarchy);\n+\tif (parent_verb && *parent_verb) {\n+\t\tstrbuf_addstr(&tr2verb_hierarchy, parent_verb);\n+\t\tstrbuf_addch(&tr2verb_hierarchy, '/');\n+\t}\n+\tstrbuf_addstr(&tr2verb_hierarchy, verb);\n+\n+\tsetenv(TR2_ENVVAR_PARENT_VERB, tr2verb_hierarchy.buf, 1);\n+}\n+\n+const char *tr2_verb_get_hierarchy(void)\n+{\n+\treturn tr2verb_hierarchy.buf;\n+}\n+\n+void tr2_verb_release(void)\n+{\n+\tstrbuf_release(&tr2verb_hierarchy);\n+}\ndiff --git a/trace2/tr2_verb.h b/trace2/tr2_verb.h\nnew file mode 100644\nindex 0000000000..f84f873463\n--- /dev/null\n+++ b/trace2/tr2_verb.h\n@@ -0,0 +1,24 @@\n+#ifndef TR2_VERB_H\n+#define TR2_VERB_H\n+\n+/*\n+ * Append the current git command's \"verb\" to the list being maintained\n+ * in the environment.\n+ *\n+ * The hierarchy for a top-level git command is just the current verb.\n+ * For a child git process, the hierarchy lists the verbs of the parent\n+ * git processes (much like the SID).\n+ *\n+ * The hierarchy for the current process will be exported to the environment\n+ * and inherited by child processes.\n+ */\n+void tr2_verb_append_hierarchy(const char *verb);\n+\n+/*\n+ * Get the verb hierarchy for the current process.\n+ */\n+const char *tr2_verb_get_hierarchy(void);\n+\n+void tr2_verb_release(void);\n+\n+#endif /* TR2_VERB_H */\ndiff --git a/usage.c b/usage.c\nindex cc803336bd..5ec7511e11 100644\n--- a/usage.c\n+++ b/usage.c\n@@ -22,17 +22,48 @@ void vreportf(const char *prefix, const char *err, va_list params)\n static NORETURN void usage_builtin(const char *err, va_list params)\n {\n \tvreportf(\"usage: \", err, params);\n+\n+\t/*\n+\t * When we detect a usage error *before* the command dispatch in\n+\t * cmd_main(), we don't know what verb to report.  Force it to this\n+\t * to facilitate post-processing.\n+\t */\n+\ttrace2_cmd_verb(\"_usage_\");\n+\n+\t/*\n+\t * Currently, the (err, params) are usually just the static usage\n+\t * string which isn't very useful here.  Usually, the call site\n+\t * manually calls fprintf(stderr,...) with the actual detailed\n+\t * syntax error before calling usage().\n+\t *\n+\t * TODO It would be nice to update the call sites to pass both\n+\t * the static usage string and the detailed error message.\n+\t */\n+\n \texit(129);\n }\n \n static NORETURN void die_builtin(const char *err, va_list params)\n {\n+\t/*\n+\t * We call this trace2 function first and expect it to va_copy 'params'\n+\t * before using it (because an 'ap' can only be walked once).\n+\t */\n+\ttrace2_cmd_error_va(err, params);\n+\n \tvreportf(\"fatal: \", err, params);\n+\n \texit(128);\n }\n \n static void error_builtin(const char *err, va_list params)\n {\n+\t/*\n+\t * We call this trace2 function first and expect it to va_copy 'params'\n+\t * before using it (because an 'ap' can only be walked once).\n+\t */\n+\ttrace2_cmd_error_va(err, params);\n+\n \tvreportf(\"error: \", err, params);\n }\n \n-- \ngitgitgadget\n\n"},{"id":"367370","messageId":"8446f69b5757c2193cefddfdea6fdfea355f30ff.1548192131.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.git.gitgitgadget@gmail.com","subject":"[PATCH 11/14] trace2:data: add subverb to checkout command","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-22T21:22:21Z","receivedAt":"2019-01-22T21:22:29Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/checkout.c | 7 +++++++\n 1 file changed, 7 insertions(+)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 6fadf412e8..8939ae99ed 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -262,6 +262,8 @@ static int checkout_paths(const struct checkout_opts *opts,\n \tstruct lock_file lock_file = LOCK_INIT;\n \tint nr_checkouts = 0;\n \n+\ttrace2_cmd_subverb(opts->patch_mode ? \"patch\" : \"path\");\n+\n \tif (opts->track != BRANCH_TRACK_UNSPECIFIED)\n \t\tdie(_(\"'%s' cannot be used with updating paths\"), \"--track\");\n \n@@ -952,6 +954,9 @@ static int switch_branches(const struct checkout_opts *opts,\n \tvoid *path_to_free;\n \tstruct object_id rev;\n \tint flag, writeout_error = 0;\n+\n+\ttrace2_cmd_subverb(\"branch\");\n+\n \tmemset(&old_branch_info, 0, sizeof(old_branch_info));\n \told_branch_info.path = path_to_free = resolve_refdup(\"HEAD\", 0, &rev, &flag);\n \tif (old_branch_info.path)\n@@ -1189,6 +1194,8 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)\n \tint status;\n \tstruct strbuf branch_ref = STRBUF_INIT;\n \n+\ttrace2_cmd_subverb(\"unborn\");\n+\n \tif (!opts->new_branch)\n \t\tdie(_(\"You are on a branch yet to be born\"));\n \tstrbuf_addf(&branch_ref, \"refs/heads/%s\", opts->new_branch);\n-- \ngitgitgadget\n\n"},{"id":"367369","messageId":"7eea9027f9e1073515b99ee296d742154ef14e26.1548192131.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.git.gitgitgadget@gmail.com","subject":"[PATCH 12/14] trace2:data: add subverb to reset command","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-22T21:22:22Z","receivedAt":"2019-01-22T21:22:30Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/reset.c | 6 ++++++\n 1 file changed, 6 insertions(+)\n\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex 59898c972e..b65b4a66db 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -340,6 +340,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \tif (patch_mode) {\n \t\tif (reset_type != NONE)\n \t\t\tdie(_(\"--patch is incompatible with --{hard,mixed,soft}\"));\n+\t\ttrace2_cmd_subverb(\"patch-interactive\");\n \t\treturn run_add_interactive(rev, \"--patch=reset\", &pathspec);\n \t}\n \n@@ -356,6 +357,11 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \tif (reset_type == NONE)\n \t\treset_type = MIXED; /* by default */\n \n+\tif (pathspec.nr)\n+\t\ttrace2_cmd_subverb(\"path\");\n+\telse\n+\t\ttrace2_cmd_subverb(reset_type_names[reset_type]);\n+\n \tif (reset_type != SOFT && (reset_type != MIXED || get_git_work_tree()))\n \t\tsetup_work_tree();\n \n-- \ngitgitgadget\n\n"},{"id":"367371","messageId":"33a1ca72221a7a8cf283569a831764a958755eda.1548192131.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.git.gitgitgadget@gmail.com","subject":"[PATCH 14/14] trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-22T21:22:24Z","receivedAt":"2019-01-22T21:22:32Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate unit tests for Trace2.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                  |   1 +\n t/helper/test-tool.c      |   1 +\n t/helper/test-tool.h      |   1 +\n t/helper/test-trace2.c    | 273 ++++++++++++++++++++++++++++++++++++++\n t/t0210-trace2-normal.sh  | 135 +++++++++++++++++++\n t/t0210/scrub_normal.perl |  48 +++++++\n t/t0211-trace2-perf.sh    | 153 +++++++++++++++++++++\n t/t0211/scrub_perf.perl   |  76 +++++++++++\n t/t0212-trace2-event.sh   | 237 +++++++++++++++++++++++++++++++++\n t/t0212/parse_events.perl | 251 +++++++++++++++++++++++++++++++++++\n 10 files changed, 1176 insertions(+)\n create mode 100644 t/helper/test-trace2.c\n create mode 100755 t/t0210-trace2-normal.sh\n create mode 100644 t/t0210/scrub_normal.perl\n create mode 100755 t/t0211-trace2-perf.sh\n create mode 100644 t/t0211/scrub_perf.perl\n create mode 100755 t/t0212-trace2-event.sh\n create mode 100644 t/t0212/parse_events.perl\n\ndiff --git a/Makefile b/Makefile\nindex 5d4e993cdc..8100364023 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -753,6 +753,7 @@ TEST_BUILTINS_OBJS += test-string-list.o\n TEST_BUILTINS_OBJS += test-submodule-config.o\n TEST_BUILTINS_OBJS += test-submodule-nested-repo-config.o\n TEST_BUILTINS_OBJS += test-subprocess.o\n+TEST_BUILTINS_OBJS += test-trace2.o\n TEST_BUILTINS_OBJS += test-urlmatch-normalization.o\n TEST_BUILTINS_OBJS += test-wildmatch.o\n TEST_BUILTINS_OBJS += test-windows-named-pipe.o\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 85d1f812fe..3d591c00ac 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -49,6 +49,7 @@ static struct test_cmd cmds[] = {\n \t{ \"submodule-config\", cmd__submodule_config },\n \t{ \"submodule-nested-repo-config\", cmd__submodule_nested_repo_config },\n \t{ \"subprocess\", cmd__subprocess },\n+\t{ \"trace2\", cmd__trace2 },\n \t{ \"urlmatch-normalization\", cmd__urlmatch_normalization },\n \t{ \"wildmatch\", cmd__wildmatch },\n #ifdef GIT_WINDOWS_NATIVE\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 042f12464b..226b86aca9 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -44,6 +44,7 @@ int cmd__string_list(int argc, const char **argv);\n int cmd__submodule_config(int argc, const char **argv);\n int cmd__submodule_nested_repo_config(int argc, const char **argv);\n int cmd__subprocess(int argc, const char **argv);\n+int cmd__trace2(int argc, const char **argv);\n int cmd__urlmatch_normalization(int argc, const char **argv);\n int cmd__wildmatch(int argc, const char **argv);\n #ifdef GIT_WINDOWS_NATIVE\ndiff --git a/t/helper/test-trace2.c b/t/helper/test-trace2.c\nnew file mode 100644\nindex 0000000000..b867f58d32\n--- /dev/null\n+++ b/t/helper/test-trace2.c\n@@ -0,0 +1,273 @@\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"argv-array.h\"\n+#include \"run-command.h\"\n+#include \"exec-cmd.h\"\n+#include \"config.h\"\n+\n+typedef int (fn_unit_test)(int argc, const char **argv);\n+\n+struct unit_test \n+{\n+\tfn_unit_test *ut_fn;\n+\tconst char   *ut_name;\n+\tconst char   *ut_usage;\n+};\n+\n+#define MyOk    0\n+#define MyError 1\n+\n+static int get_i(int *p_value, const char *data)\n+{\n+\tchar *endptr;\n+\n+\tif (!data || !*data)\n+\t\treturn MyError;\n+\n+\t*p_value = strtol(data, &endptr, 10);\n+\tif (*endptr || errno == ERANGE)\n+\t\treturn MyError;\n+\n+\treturn MyOk;\n+}\n+\n+/*\n+ * Cause process to exit with the requested value via \"return\".\n+ *\n+ * Rely on test-tool.c:cmd_main() to call trace2_cmd_exit()\n+ * with our result.\n+ *\n+ * Test harness can confirm:\n+ * [] the process-exit value.\n+ * [] the \"code\" field in the \"exit\" trace2 event.\n+ * [] the \"code\" field in the \"atexit\" trace2 event.\n+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n+ * [] \"def_param\" events for all of the \"interesting\" pre-defined config settings.\n+ */\n+static int ut_001return(int argc, const char **argv)\n+{\n+\tint rc;\n+\n+\tif (get_i(&rc, argv[0]))\n+\t\tdie(\"expect <exit_code>\");\n+\n+\treturn rc;\n+}\n+\n+/*\n+ * Cause the process to exit with the requested value via \"exit()\".\n+ *\n+ * Test harness can confirm:\n+ * [] the \"code\" field in the \"exit\" trace2 event.\n+ * [] the \"code\" field in the \"atexit\" trace2 event.\n+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n+ * [] \"def_param\" events for all of the \"interesting\" pre-defined config settings.\n+ */\n+static int ut_002exit(int argc, const char **argv)\n+{\n+\tint rc;\n+\n+\tif (get_i(&rc, argv[0]))\n+\t\tdie(\"expect <exit_code>\");\n+\n+\texit(rc);\n+}\n+\n+/*\n+ * Send an \"error\" event with each value in argv.  Normally, git only issues\n+ * a single \"error\" event immediately before issuing an \"exit\" event (such\n+ * as in die() or BUG()), but multiple \"error\" events are allowed.\n+ *\n+ * Test harness can confirm:\n+ * [] a trace2 \"error\" event for each value in argv.\n+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n+ * [] (optional) the file:line in the \"exit\" event refers to this function.\n+ */\n+static int ut_003error(int argc, const char **argv)\n+{\n+\tint k;\n+\n+\tif (!argv[0] || !*argv[0])\n+\t\tdie(\"expect <error_message>\");\n+\n+\tfor (k = 0; k < argc; k++)\n+\t\terror(\"%s\", argv[k]);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Run a child process and wait for it to finish and exit with its return code.\n+ * test-tool trace2 004child [<child-command-line>]\n+ *\n+ * For example:\n+ * test-tool trace2 004child git version\n+ * test-tool trace2 004child test-tool trace2 001return 0\n+ * test-tool trace2 004child test-tool trace2 004child test-tool trace2 004child\n+ * test-tool trace2 004child git -c alias.xyz=version xyz\n+ *\n+ * Test harness can confirm:\n+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n+ * [] that the outer process has a single component SID (or depth \"d0\" in\n+ *    the PERF stream).\n+ * [] that \"child_start\" and \"child_exit\" events are generated for the child.\n+ * [] if the child process is an instrumented executable:\n+ *    [] that \"version\", \"start\", ..., \"exit\", and \"atexit\" events are\n+ *       generated by the child process.\n+ *    [] that the child process events have a multiple component SID (or\n+ *       depth \"dN+1\" in the PERF stream).\n+ * [] that the child exit code is propagated to the parent process \"exit\"\n+ *    and \"atexit\" events..\n+ * [] (optional) that the \"t_abs\" field in the child process \"atexit\" event\n+ *    is less than the \"t_rel\" field in the \"child_exit\" event of the parent\n+ *    process.\n+ * [] if the child process is like the alias example above,\n+ *    [] (optional) the child process attempts to run \"git-xyx\" as a dashed\n+ *       command.\n+ *    [] the child process emits an \"alias\" event with \"xyz\" => \"version\"\n+ *    [] the child process runs \"git version\" as a child process.\n+ *    [] the child process has a 3 component SID (or depth \"d2\" in the PERF\n+ *       stream).\n+ */\n+static int ut_004child(int argc, const char **argv)\n+{\n+\tint result;\n+\n+\t/*\n+\t * Allow empty <child_command_line> so we can do arbitrarily deep\n+\t * command nesting and let the last one be null.\n+\t */\n+\tif (!argc)\n+\t\treturn 0;\n+\n+\tresult = run_command_v_opt(argv, 0);\n+\texit(result);\n+}\n+\n+/*\n+ * Exec a git command.  This may either create a child process (Windows)\n+ * or replace the existing process.\n+ * test-tool trace2 005exec <git_command_args>\n+ *\n+ * For example:\n+ * test-tool trace2 005exec version\n+ *\n+ * Test harness can confirm (on Windows):\n+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n+ * [] that the outer process has a single component SID (or depth \"d0\" in\n+ *    the PERF stream).\n+ * [] that \"exec\" and \"exec_result\" events are generated for the child\n+ *    process (since the Windows compatibility layer fakes an exec() with\n+ *    a CreateProcess(), WaitForSingleObject(), and exit()).\n+ * [] that the child process has multiple component SID (or depth \"dN+1\"\n+ *    in the PERF stream).\n+ *\n+ * Test harness can confirm (on platforms with a real exec() function):\n+ * [] TODO talk about process replacement and how it affects SID.\n+ */ \n+static int ut_005exec(int argc, const char **argv)\n+{\n+\tint result;\n+\n+\tif (!argc)\n+\t\treturn 0;\n+\n+\tresult = execv_git_cmd(argv);\n+\treturn result;\n+}\n+\n+static int ut_006data(int argc, const char **argv)\n+{\n+\tconst char *usage_error =\n+\t\t\"expect <cat0> <k0> <v0> [<cat1> <k1> <v1> [...]]\";\n+\n+\tif (argc % 3 != 0)\n+\t\tdie(\"%s\", usage_error);\n+\n+\twhile (argc) {\n+\t\tif (!argv[0] || !*argv[0] ||\n+\t\t    !argv[1] || !*argv[1] ||\n+\t\t    !argv[2] || !*argv[2])\n+\t\t\tdie(\"%s\", usage_error);\n+\n+\t\ttrace2_data_string(argv[0], the_repository, argv[1], argv[2]);\n+\t\targv += 3;\n+\t\targc -= 3;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Usage:\n+ *     test-tool trace2 <ut_name_1> <ut_usage_1>\n+ *     test-tool trace2 <ut_name_2> <ut_usage_2>\n+ *     ...\n+ */ \n+#define USAGE_PREFIX \"test-tool trace2\"\n+\n+static struct unit_test ut_table[] = {\n+\t{ ut_001return,   \"001return\", \"<exit_code>\" },\n+\t{ ut_002exit,     \"002exit\",   \"<exit_code>\" },\n+\t{ ut_003error,    \"003error\",  \"<error_message>+\" },\n+\t{ ut_004child,    \"004child\",  \"[<child_command_line>]\" },\n+\t{ ut_005exec,     \"005exec\",   \"<git_command_args>\" },\n+\t{ ut_006data,     \"006data\",   \"[<category> <key> <value>]+\" },\n+};\n+\n+#define for_each_ut(k, ut_k)\t\t\t\\\n+\tfor (k = 0, ut_k = &ut_table[k];\t\\\n+\t     k < ARRAY_SIZE(ut_table);\t\t\\\n+\t     k++, ut_k = &ut_table[k])\n+\n+static int print_usage(void)\n+{\n+\tint k;\n+\tstruct unit_test *ut_k;\n+\n+\tfprintf(stderr, \"usage:\\n\");\n+\tfor_each_ut(k, ut_k) {\n+\t\tfprintf(stderr, \"\\t%s %s %s\\n\",\n+\t\t\tUSAGE_PREFIX, ut_k->ut_name, ut_k->ut_usage);\n+\t}\n+\n+\treturn 129;\n+}\n+\n+/*\n+ * Issue various trace2 events for testing.\n+ *\n+ * We assume that these trace2 routines has already been called:\n+ *    [] trace2_initialize()      [common-main.c:main()]\n+ *    [] trace2_cmd_start()       [common-main.c:main()]\n+ *    [] trace2_cmd_verb()        [test-tool.c:cmd_main()]\n+ *    [] tracd2_cmd_list_config() [test-tool.c:cmd_main()]\n+ * So that:\n+ *    [] the various trace2 streams are open.\n+ *    [] the process SID has been created.\n+ *    [] the \"version\" event has been generated.\n+ *    [] the \"start\" event has been generated.\n+ *    [] the \"verb\" event has been generated.\n+ *    [] this writes various \"def_param\" events for interesting config values.\n+ *\n+ * We further assume that if we return (rather than exit()), trace2_cmd_exit()\n+ * will be called by test-tool.c:cmd_main().\n+ */\n+int cmd__trace2(int argc, const char **argv)\n+{\n+\tint k;\n+\tstruct unit_test *ut_k;\n+\n+\targc--;\t/* skip over \"trace2\" arg */\n+\targv++;\n+\n+\tif (argc) {\n+\t\tfor_each_ut(k, ut_k) {\n+\t\t\tif (!strcmp(argv[0], ut_k->ut_name))\n+\t\t\t\treturn ut_k->ut_fn(argc - 1, argv + 1);\n+\t\t}\n+\t}\n+\n+\treturn print_usage();\n+}\n+\ndiff --git a/t/t0210-trace2-normal.sh b/t/t0210-trace2-normal.sh\nnew file mode 100755\nindex 0000000000..0b5b45b263\n--- /dev/null\n+++ b/t/t0210-trace2-normal.sh\n@@ -0,0 +1,135 @@\n+#!/bin/sh\n+\n+test_description='test trace2 facility (normal target)'\n+. ./test-lib.sh\n+\n+# Add t/helper directory to PATH so that we can use a relative\n+# path to run nested instances of test-tool.exe (see 004child).\n+# This helps with HEREDOC comparisons later.\n+TTDIR=\"$GIT_BUILD_DIR/t/helper/\" && export TTDIR\n+PATH=\"$TTDIR:$PATH\" && export PATH\n+\n+TT=\"test-tool\" && export TT\n+\n+# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe\n+# Warning: to do the actual diff/comparison, so the HEREDOCs here\n+# Warning: only cover our actual calls to test-tool and/or git.\n+# Warning: So you may see extra lines in artifact files when\n+# Warning: interactively debugging.\n+\n+# Turn off any inherited trace2 settings for this test.\n+unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT\n+unset GIT_TR2_BRIEF\n+unset GIT_TR2_CONFIG_PARAMS\n+\n+V=$(git version | sed -e 's/^git version //') && export V\n+\n+# There are multiple trace2 targets: normal, perf, and event.\n+# Trace2 events will/can be written to each active target (subject\n+# to whatever filtering that target decides to do).\n+# This script tests the normal target in isolation.\n+\n+# Enable \"normal\" trace2 target.\n+GIT_TR2=\"$(pwd)/trace.normal\" && export GIT_TR2\n+# Enable \"brief\" feature which turns off \"<clock> <file>:<line> \" prefix.\n+GIT_TR2_BRIEF=1 && export GIT_TR2_BRIEF\n+\n+# Basic tests of the trace2 normal stream.  Since this stream is used\n+# primarily with printf-style debugging/tracing, we do limited testing\n+# here.\n+#\n+# We do confirm the following API features:\n+# [] the 'version <v>' event\n+# [] the 'start <argv>' event\n+# [] the 'cmd_verb <verb>' event\n+# [] the 'exit <time> code:<code>' event\n+# [] the 'atexit <time> code:<code>' event\n+#\n+# Fields of the form _FIELD_ are tokens that have been replaced (such\n+# as the elapsed time).\n+\n+# Verb 001return\n+#\n+# Implicit return from cmd_<verb> function propagates <code>.\n+\n+test_expect_success 'normal stream, return code 0' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\t$TT trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 001return 0\n+\t\tcmd_verb trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:0\n+\t\tatexit elapsed:_TIME_ code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'normal stream, return code 1' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\ttest_must_fail $TT trace2 001return 1 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 001return 1\n+\t\tcmd_verb trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:1\n+\t\tatexit elapsed:_TIME_ code:1\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 002exit\n+#\n+# Explicit exit(code) from within cmd_<verb> propagates <code>.\n+\n+test_expect_success 'normal stream, exit code 0' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\t$TT trace2 002exit 0 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 002exit 0\n+\t\tcmd_verb trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:0\n+\t\tatexit elapsed:_TIME_ code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'normal stream, exit code 1' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\ttest_must_fail $TT trace2 002exit 1 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 002exit 1\n+\t\tcmd_verb trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:1\n+\t\tatexit elapsed:_TIME_ code:1\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 003error\n+#\n+# To the above, add multiple 'error <msg>' events\n+\n+test_expect_success 'normal stream, error event' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\t$TT trace2 003error \"hello world\" \"this is a test\" &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 003error '\\''hello world'\\'' '\\''this is a test'\\''\n+\t\tcmd_verb trace2 (trace2)\n+\t\terror hello world\n+\t\terror this is a test\n+\t\texit elapsed:_TIME_ code:0\n+\t\tatexit elapsed:_TIME_ code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t0210/scrub_normal.perl b/t/t0210/scrub_normal.perl\nnew file mode 100644\nindex 0000000000..c65d1a815e\n--- /dev/null\n+++ b/t/t0210/scrub_normal.perl\n@@ -0,0 +1,48 @@\n+#!/usr/bin/perl\n+#\n+# Scrub the variable fields from the normal trace2 output to\n+# make testing easier.\n+\n+use strict;\n+use warnings;\n+\n+my $float = '[0-9]*\\.[0-9]+([eE][-+]?[0-9]+)?';\n+\n+# This code assumes that the trace2 data was written with bare\n+# turned on (which omits the \"<clock> <file>:<line>\" prefix.\n+\n+while (<>) {\n+    # Various messages include an elapsed time in the middle\n+    # of the message.  Replace the time with a placeholder to\n+    # simplify our HEREDOC in the test script.\n+    s/elapsed:$float/elapsed:_TIME_/g;\n+\n+    my $line = $_;\n+\n+    # we expect:\n+    #    start <argv0> [<argv1> [<argv2> [...]]]\n+    #\n+    # where argv0 might be a relative or absolute path, with\n+    # or without quotes, and platform dependent.  Replace argv0\n+    # with a token for HEREDOC matching in the test script.\n+\n+    if ($line =~ m/^start/) {\n+\t$line =~ /^start\\s+(.*)/;\n+\tmy $argv = $1;\n+\t$argv =~ m/(\\'[^\\']*\\'|[^ ]+)\\s+(.*)/;\n+\tmy $argv_0 = $1;\n+\tmy $argv_rest = $2;\n+\n+\tprint \"start _EXE_ $argv_rest\\n\";\n+    }\n+    elsif ($line =~ m/^cmd_path/) {\n+\t# Likewise, the 'cmd_path' message breaks out argv[0].\n+\t#\n+\t# This line is only emitted when RUNTIME_PREFIX is defined,\n+\t# so just omit it for testing purposes.\n+\t# print \"cmd_path _EXE_\\n\";\n+    }\n+    else {\n+\tprint \"$line\";\n+    }\n+}\ndiff --git a/t/t0211-trace2-perf.sh b/t/t0211-trace2-perf.sh\nnew file mode 100755\nindex 0000000000..6146c3f6e3\n--- /dev/null\n+++ b/t/t0211-trace2-perf.sh\n@@ -0,0 +1,153 @@\n+#!/bin/sh\n+\n+test_description='test trace2 facility (perf target)'\n+. ./test-lib.sh\n+\n+# Add t/helper directory to PATH so that we can use a relative\n+# path to run nested instances of test-tool.exe (see 004child).\n+# This helps with HEREDOC comparisons later.\n+TTDIR=\"$GIT_BUILD_DIR/t/helper/\" && export TTDIR\n+PATH=\"$TTDIR:$PATH\" && export PATH\n+\n+TT=\"test-tool\" && export TT\n+\n+# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe\n+# Warning: to do the actual diff/comparison, so the HEREDOCs here\n+# Warning: only cover our actual calls to test-tool and/or git.\n+# Warning: So you may see extra lines in artifact files when\n+# Warning: interactively debugging.\n+\n+# Turn off any inherited trace2 settings for this test.\n+unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT\n+unset GIT_TR2_PERF_BRIEF\n+unset GIT_TR2_CONFIG_PARAMS\n+\n+V=$(git version | sed -e 's/^git version //') && export V\n+\n+# There are multiple trace2 targets: normal, perf, and event.\n+# Trace2 events will/can be written to each active target (subject\n+# to whatever filtering that target decides to do).\n+# Test each target independently.\n+\n+# Enable \"perf\" trace2 target.\n+GIT_TR2_PERF=\"$(pwd)/trace.perf\" && export GIT_TR2_PERF\n+# Enable \"brief\" feature which turns off the prefix:\n+#     \"<clock> <file>:<line> | <nr_parents> | \"\n+GIT_TR2_PERF_BRIEF=1 && export GIT_TR2_PERF_BRIEF\n+\n+# Repeat some of the t0210 tests using the perf target stream instead of\n+# the normal stream.\n+#\n+# Tokens here of the form _FIELD_ have been replaced in the observed output.\n+\n+# Verb 001return\n+#\n+# Implicit return from cmd_<verb> function propagates <code>.\n+\n+test_expect_success 'perf stream, return code 0' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\t$TT trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 001return 0\n+\t\td0|main|cmd_verb|||||trace2 (trace2)\n+\t\td0|main|exit||_T_ABS_|||code:0\n+\t\td0|main|atexit||_T_ABS_|||code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'perf stream, return code 1' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\ttest_must_fail $TT trace2 001return 1 &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 001return 1\n+\t\td0|main|cmd_verb|||||trace2 (trace2)\n+\t\td0|main|exit||_T_ABS_|||code:1\n+\t\td0|main|atexit||_T_ABS_|||code:1\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 003error\n+#\n+# To the above, add multiple 'error <msg>' events\n+\n+test_expect_success 'perf stream, error event' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\t$TT trace2 003error \"hello world\" \"this is a test\" &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 003error '\\''hello world'\\'' '\\''this is a test'\\''\n+\t\td0|main|cmd_verb|||||trace2 (trace2)\n+\t\td0|main|error|||||hello world\n+\t\td0|main|error|||||this is a test\n+\t\td0|main|exit||_T_ABS_|||code:0\n+\t\td0|main|atexit||_T_ABS_|||code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 004child\n+#\n+# Test nested spawning of child processes.\n+#\n+# Conceptually, this looks like:\n+#    P1: TT trace2 004child\n+#    P2: |--- TT trace2 004child\n+#    P3:      |--- TT trace2 001return 0\n+#\n+# Which should generate events:\n+#    P1: version\n+#    P1: start\n+#    P1: cmd_verb\n+#    P1: child_start\n+#        P2: version\n+#        P2: start\n+#        P2: cmd_verb\n+#        P2: child_start\n+#            P3: version\n+#            P3: start\n+#            P3: cmd_verb\n+#            P3: exit\n+#            P3: atexit\n+#        P2: child_exit\n+#        P2: exit\n+#        P2: atexit\n+#    P1: child_exit\n+#    P1: exit\n+#    P1: atexit\n+\n+test_expect_success 'perf stream, child processes' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\t$TT trace2 004child $TT trace2 004child $TT trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 004child $TT trace2 004child $TT trace2 001return 0\n+\t\td0|main|cmd_verb|||||trace2 (trace2)\n+\t\td0|main|child_start||_T_ABS_|||[ch0] class:? argv: $TT trace2 004child $TT trace2 001return 0\n+\t\td1|main|version|||||$V\n+\t\td1|main|start|||||_EXE_ trace2 004child $TT trace2 001return 0\n+\t\td1|main|cmd_verb|||||trace2 (trace2/trace2)\n+\t\td1|main|child_start||_T_ABS_|||[ch0] class:? argv: $TT trace2 001return 0\n+\t\td2|main|version|||||$V\n+\t\td2|main|start|||||_EXE_ trace2 001return 0\n+\t\td2|main|cmd_verb|||||trace2 (trace2/trace2/trace2)\n+\t\td2|main|exit||_T_ABS_|||code:0\n+\t\td2|main|atexit||_T_ABS_|||code:0\n+\t\td1|main|child_exit||_T_ABS_|_T_REL_||[ch0] pid:_PID_ code:0\n+\t\td1|main|exit||_T_ABS_|||code:0\n+\t\td1|main|atexit||_T_ABS_|||code:0\n+\t\td0|main|child_exit||_T_ABS_|_T_REL_||[ch0] pid:_PID_ code:0\n+\t\td0|main|exit||_T_ABS_|||code:0\n+\t\td0|main|atexit||_T_ABS_|||code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t0211/scrub_perf.perl b/t/t0211/scrub_perf.perl\nnew file mode 100644\nindex 0000000000..351af7844e\n--- /dev/null\n+++ b/t/t0211/scrub_perf.perl\n@@ -0,0 +1,76 @@\n+#!/usr/bin/perl\n+#\n+# Scrub the variable fields from the perf trace2 output to\n+# make testing easier.\n+\n+use strict;\n+use warnings;\n+\n+my $qpath = '\\'[^\\']*\\'|[^ ]*';\n+\n+my $col_depth=0;\n+my $col_thread=1;\n+my $col_event=2;\n+my $col_repo=3;\n+my $col_t_abs=4;\n+my $col_t_rel=5;\n+my $col_category=6;\n+my $col_rest=7;\n+\n+# This code assumes that the trace2 data was written with bare\n+# turned on (which omits the \"<clock> <file>:<line> | <parents>\"\n+# prefix.\n+\n+while (<>) {\n+    my @tokens = split /\\|/;\n+\n+    foreach my $col (@tokens) { $col =~ s/^\\s+|\\s+$//g; }\n+\n+    if ($tokens[$col_event] =~ m/^start/) {\n+\t# The 'start' message lists the contents of argv in $col_rest.\n+\t# On some platforms (Windows), argv[0] is *sometimes* a canonical\n+\t# absolute path to the EXE rather than the value passed in the\n+\t# shell script.  Replace it with a placeholder to simplify our\n+\t# HEREDOC in the test script.\n+\tmy $argv0;\n+\tmy $argvRest;\n+\t$tokens[$col_rest] =~ s/^($qpath)\\W*(.*)/_EXE_ $2/;\n+    }\n+    elsif ($tokens[$col_event] =~ m/cmd_path/) {\n+\t# Likewise, the 'cmd_path' message breaks out argv[0].\n+\t#\n+\t# This line is only emitted when RUNTIME_PREFIX is defined,\n+\t# so just omit it for testing purposes.\n+\t# $tokens[$col_rest] = \"_EXE_\";\n+\tgoto SKIP_LINE;\n+    }\n+    elsif ($tokens[$col_event] =~ m/child_exit/) {\n+\t$tokens[$col_rest] =~ s/ pid:\\d* / pid:_PID_ /;\n+    }\n+    elsif ($tokens[$col_event] =~ m/data/) {\n+\tif ($tokens[$col_category] =~ m/process/) {\n+\t    # 'data' and 'data_json' events containing 'process'\n+\t    # category data are assumed to be platform-specific\n+\t    # and highly variable.  Just omit them.\n+\t    goto SKIP_LINE;\n+\t}\n+    }\n+\n+    # t_abs and t_rel are either blank or a float.  Replace the float\n+    # with a constant for matching the HEREDOC in the test script.\n+    if ($tokens[$col_t_abs] =~ m/\\d/) {\n+\t$tokens[$col_t_abs] = \"_T_ABS_\";\n+    }\n+    if ($tokens[$col_t_rel] =~ m/\\d/) {\n+\t$tokens[$col_t_rel] = \"_T_REL_\";\n+    }\n+\n+    my $out;\n+\n+    $out = join('|', @tokens);\n+    print \"$out\\n\";\n+\n+  SKIP_LINE:\n+}\n+\n+\ndiff --git a/t/t0212-trace2-event.sh b/t/t0212-trace2-event.sh\nnew file mode 100755\nindex 0000000000..5a9527c34f\n--- /dev/null\n+++ b/t/t0212-trace2-event.sh\n@@ -0,0 +1,237 @@\n+#!/bin/sh\n+\n+test_description='test trace2 facility'\n+. ./test-lib.sh\n+\n+# Add t/helper directory to PATH so that we can use a relative\n+# path to run nested instances of test-tool.exe (see 004child).\n+# This helps with HEREDOC comparisons later.\n+TTDIR=\"$GIT_BUILD_DIR/t/helper/\" && export TTDIR\n+PATH=\"$TTDIR:$PATH\" && export PATH\n+\n+TT=\"test-tool\" && export TT\n+\n+# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe\n+# Warning: to do the actual diff/comparison, so the HEREDOCs here\n+# Warning: only cover our actual calls to test-tool and/or git.\n+# Warning: So you may see extra lines in artifact files when\n+# Warning: interactively debugging.\n+\n+# Turn off any inherited trace2 settings for this test.\n+unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT\n+unset GIT_TR2_BARE\n+unset GIT_TR2_CONFIG_PARAMS\n+\n+V=$(git version | sed -e 's/^git version //') && export V\n+\n+# There are multiple trace2 targets: normal, perf, and event.\n+# Trace2 events will/can be written to each active target (subject\n+# to whatever filtering that target decides to do).\n+# Test each target independently.\n+\n+# Enable \"event\" trace2 target.\n+GIT_TR2_EVENT=\"$(pwd)/trace.event\" && export GIT_TR2_EVENT\n+\n+# We don't bother repeating the 001return and 002exit tests, since they\n+# have coverage in the normal and perf targets.\n+\n+# Verb 003error\n+#\n+# To the above, add multiple 'error <msg>' events\n+\n+test_expect_success 'event stream, error event' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\t$TT trace2 003error \"hello world\" \"this is a test\" &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"003error\",\n+\t|      \"hello world\",\n+\t|      \"this is a test\"\n+\t|    ],\n+\t|    \"errors\":[\n+\t|      \"%s\",\n+\t|      \"%s\"\n+\t|    ],\n+\t|    \"exit_code\":0,\n+\t|    \"verb\":\"trace2\",\n+\t|    \"verb_hierarchy\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 004child\n+#\n+# Test nested spawning of child processes.\n+#\n+# Conceptually, this looks like:\n+#    P1: TT trace2 004child\n+#    P2: |--- TT trace2 004child\n+#    P3:      |--- TT trace2 001return 0\n+\n+test_expect_success 'event stream, return code 0' '\n+#\ttest_when_finished \"rm trace.event actual expect\" &&\n+\t$TT trace2 004child $TT trace2 004child $TT trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"004child\",\n+\t|      \"$TT\",\n+\t|      \"trace2\",\n+\t|      \"004child\",\n+\t|      \"$TT\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"child\":{\n+\t|      \"0\":{\n+\t|        \"child_argv\":[\n+\t|          \"_EXE_\",\n+\t|          \"trace2\",\n+\t|          \"004child\",\n+\t|          \"$TT\",\n+\t|          \"trace2\",\n+\t|          \"001return\",\n+\t|          \"0\"\n+\t|        ],\n+\t|        \"child_class\":\"?\",\n+\t|        \"child_code\":0,\n+\t|        \"use_shell\":0\n+\t|      }\n+\t|    },\n+\t|    \"exit_code\":0,\n+\t|    \"verb\":\"trace2\",\n+\t|    \"verb_hierarchy\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  },\n+\t|  \"_SID0_/_SID1_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"004child\",\n+\t|      \"$TT\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"child\":{\n+\t|      \"0\":{\n+\t|        \"child_argv\":[\n+\t|          \"_EXE_\",\n+\t|          \"trace2\",\n+\t|          \"001return\",\n+\t|          \"0\"\n+\t|        ],\n+\t|        \"child_class\":\"?\",\n+\t|        \"child_code\":0,\n+\t|        \"use_shell\":0\n+\t|      }\n+\t|    },\n+\t|    \"exit_code\":0,\n+\t|    \"verb\":\"trace2\",\n+\t|    \"verb_hierarchy\":\"trace2/trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  },\n+\t|  \"_SID0_/_SID1_/_SID2_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"exit_code\":0,\n+\t|    \"verb\":\"trace2\",\n+\t|    \"verb_hierarchy\":\"trace2/trace2/trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Test listing of all \"interesting\" config settings.\n+\n+test_expect_success 'event stream, list config' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tgit config --local t0212.abc 1 &&\n+\tgit config --local t0212.def \"hello world\" &&\n+\t# delete events generated by the above config commands\n+\trm trace.event &&\n+\tGIT_TR2_CONFIG_PARAMS=\"t0212.*\" $TT trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"exit_code\":0,\n+\t|    \"params\":[\n+\t|      {\n+\t|        \"param\":\"t0212.abc\",\n+\t|        \"value\":\"1\"\n+\t|      },\n+\t|      {\n+\t|        \"param\":\"t0212.def\",\n+\t|        \"value\":\"hello world\"\n+\t|      }\n+\t|    ],\n+\t|    \"verb\":\"trace2\",\n+\t|    \"verb_hierarchy\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'basic trace2_data' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\t$TT trace2 006data test_category k1 v1 test_category k2 v2 &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"006data\",\n+\t|      \"test_category\",\n+\t|      \"k1\",\n+\t|      \"v1\",\n+\t|      \"test_category\",\n+\t|      \"k2\",\n+\t|      \"v2\"\n+\t|    ],\n+\t|    \"data\":{\n+\t|      \"test_category\":{\n+\t|        \"k1\":\"v1\",\n+\t|        \"k2\":\"v2\"\n+\t|      }\n+\t|    },\n+\t|    \"exit_code\":0,\n+\t|    \"verb\":\"trace2\",\n+\t|    \"verb_hierarchy\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t0212/parse_events.perl b/t/t0212/parse_events.perl\nnew file mode 100644\nindex 0000000000..0e38fa6db6\n--- /dev/null\n+++ b/t/t0212/parse_events.perl\n@@ -0,0 +1,251 @@\n+#!/usr/bin/perl\n+#\n+# Parse event stream and convert individual events into a summary\n+# record for the process.\n+#\n+# Git.exe generates one or more \"event\" records for each API method,\n+# such as \"start <argv>\" and \"exit <code>\", during the life of the git\n+# process.  Additionally, the input may contain interleaved events\n+# from multiple concurrent git processes and/or multiple threads from\n+# within a git process.\n+#\n+# Accumulate events for each process (based on its unique SID) in a\n+# dictionary and emit process summary records.\n+#\n+# Convert some of the variable fields (such as elapsed time) into\n+# placeholders (or omit them) to make HEREDOC comparisons easier in\n+# the test scripts.\n+#\n+# We may also omit fields not (currently) useful for testing purposes.\n+\n+use strict;\n+use warnings;\n+use JSON::PP;\n+use Data::Dumper;\n+use Getopt::Long;\n+\n+# The version of the trace2 event target format that we understand.\n+# This is reported in the 'version' event in the 'evt' field.\n+# It comes from the GIT_TR2_EVENT_VERSION macro in trace2/tr2_tgt_event.c\n+my $evt_version = '1';\n+\n+my $show_children = 1;\n+my $show_exec     = 1;\n+my $show_threads  = 1;\n+\n+# A hack to generate test HEREDOC data for pasting into the test script.\n+# Usage:\n+#    cd \"t/trash directory.t0212-trace2-event\"\n+#    $TT trace ... >trace.event\n+#    VV=$(../../git.exe version | sed -e 's/^git version //')\n+#    perl ../t0212/parse_events.perl --HEREDOC --VERSION=$VV <trace.event >heredoc\n+# Then paste heredoc into your new test.\n+\n+my $gen_heredoc = 0;\n+my $gen_version = '';\n+\n+GetOptions(\"children!\" => \\$show_children,\n+\t   \"exec!\"     => \\$show_exec,\n+\t   \"threads!\"  => \\$show_threads,\n+\t   \"HEREDOC!\"  => \\$gen_heredoc,\n+\t   \"VERSION=s\" => \\$gen_version    )\n+    or die(\"Error in command line arguments\\n\");\n+\n+\n+# SIDs contains timestamps and PIDs of the process and its parents.\n+# This makes it difficult to match up in a HEREDOC in the test script.\n+# Build a map from actual SIDs to predictable constant values and yet\n+# keep the parent/child relationships.  For example:\n+# {..., \"sid\":\"1539706952458276-8652\", ...}\n+# {..., \"sid\":\"1539706952458276-8652/1539706952649493-15452\", ...}\n+# becomes:\n+# {..., \"sid\":\"_SID1_\", ...}\n+# {..., \"sid\":\"_SID1_/_SID2_\", ...}\n+my $sid_map;\n+my $sid_count = 0;\n+\n+my $processes;\n+\n+while (<>) {\n+    my $line = decode_json( $_ );\n+\n+    my $sid = \"\";\n+    my $sid_sep = \"\";\n+\n+    my $raw_sid = $line->{'sid'};\n+    my @raw_sid_parts = split /\\//, $raw_sid;\n+    foreach my $raw_sid_k (@raw_sid_parts) {\n+\tif (!exists $sid_map->{$raw_sid_k}) {\n+\t    $sid_map->{$raw_sid_k} = '_SID' . $sid_count . '_';\n+\t    $sid_count++;\n+\t}\n+\t$sid = $sid . $sid_sep . $sid_map->{$raw_sid_k};\n+\t$sid_sep = '/';\n+    }\n+    \n+    my $event = $line->{'event'};\n+\n+    if ($event eq 'version') {\n+\t$processes->{$sid}->{'version'} = $line->{'exe'};\n+\tif ($gen_heredoc == 1 && $gen_version eq $line->{'exe'}) {\n+\t    # If we are generating data FOR the test script, replace\n+\t    # the reported git.exe version with a reference to an\n+\t    # environment variable.  When our output is pasted into\n+\t    # the test script, it will then be expanded in future\n+\t    # test runs to the THEN current version of git.exe.\n+\t    # We assume that the test script uses env var $V.\n+\t    $processes->{$sid}->{'version'} = \"\\$V\";\n+\t}\n+    }\n+\n+    elsif ($event eq 'start') {\n+\t$processes->{$sid}->{'argv'} = $line->{'argv'};\n+\t$processes->{$sid}->{'argv'}[0] = \"_EXE_\";\n+    }\n+\n+    elsif ($event eq 'exit') {\n+\t$processes->{$sid}->{'exit_code'} = $line->{'code'};\n+    }\n+\n+    elsif ($event eq 'atexit') {\n+\t$processes->{$sid}->{'exit_code'} = $line->{'code'};\n+    }\n+\n+    elsif ($event eq 'error') {\n+\t# For HEREDOC purposes, use the error message format string if\n+\t# available, rather than the formatted message (which probably\n+\t# has an absolute pathname).\n+\tif (exists $line->{'fmt'}) {\n+\t    push( @{$processes->{$sid}->{'errors'}}, $line->{'fmt'} );\n+\t}\n+\telsif (exists $line->{'msg'}) {\n+\t    push( @{$processes->{$sid}->{'errors'}}, $line->{'msg'} );\n+\t}\n+    }\n+\n+    elsif ($event eq 'cmd_path') {\n+\t## $processes->{$sid}->{'path'} = $line->{'path'};\n+\t#\n+\t# Like in the 'start' event, we need to replace the value of\n+\t# argv[0] with a token for HEREDOC purposes.  However, the\n+\t# event is only emitted when RUNTIME_PREFIX is defined, so\n+\t# just omit it for testing purposes.\n+\t# $processes->{$sid}->{'path'} = \"_EXE_\";\n+    }\n+    \n+    elsif ($event eq 'cmd_verb') {\n+\t$processes->{$sid}->{'verb'} = $line->{'name'};\n+\t$processes->{$sid}->{'verb_hierarchy'} = $line->{'hierarchy'};\n+    }\n+\n+    elsif ($event eq 'alias') {\n+\t$processes->{$sid}->{'alias'}->{'key'} = $line->{'alias'};\n+\t$processes->{$sid}->{'alias'}->{'argv'} = $line->{'argv'};\n+    }\n+\n+    elsif ($event eq 'def_param') {\n+\tmy $kv;\n+\t$kv->{'param'} = $line->{'param'};\n+\t$kv->{'value'} = $line->{'value'};\n+\tpush( @{$processes->{$sid}->{'params'}}, $kv );\n+    }\n+\n+    elsif ($event eq 'child_start') {\n+\tif ($show_children == 1) {\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_class'} = $line->{'child_class'};\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_argv'} = $line->{'argv'};\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_argv'}[0] = \"_EXE_\";\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'use_shell'} = $line->{'use_shell'} ? 1 : 0;\n+\t}\n+    }\n+\n+    elsif ($event eq 'child_exit') {\n+\tif ($show_children == 1) {\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_code'} = $line->{'code'};\n+\t}\n+    }\n+\n+    # TODO decide what information we want to test from thread events.\n+\n+    elsif ($event eq 'thread_start') {\n+\tif ($show_threads == 1) {\n+\t}\n+    }\n+\n+    elsif ($event eq 'thread_exit') {\n+\tif ($show_threads == 1) {\n+\t}\n+    }\n+\n+    # TODO decide what information we want to test from exec events.\n+\n+    elsif ($event eq 'exec') {\n+\tif ($show_exec == 1) {\n+\t}\n+    }\n+\n+    elsif ($event eq 'exec_result') {\n+\tif ($show_exec == 1) {\n+\t}\n+    }\n+\n+    elsif ($event eq 'def_param') {\n+\t# Accumulate parameter key/value pairs by key rather than in an array\n+\t# so that we get overwrite (last one wins) effects.\n+\t$processes->{$sid}->{'params'}->{$line->{'param'}} = $line->{'value'};\n+    }\n+\n+    elsif ($event eq 'def_repo') {\n+\t# $processes->{$sid}->{'repos'}->{$line->{'repo'}} = $line->{'worktree'};\n+\t$processes->{$sid}->{'repos'}->{$line->{'repo'}} = \"_WORKTREE_\";\n+    }\n+\n+    # A series of potentially nested and threaded region and data events\n+    # is fundamentally incompatibile with the type of summary record we\n+    # are building in this script.  Since they are intended for\n+    # perf-trace-like analysis rather than a result summary, we ignore\n+    # most of them here.\n+\n+    # elsif ($event eq 'region_enter') {\n+    # }\n+    # elsif ($event eq 'region_leave') {\n+    # }\n+\n+    elsif ($event eq 'data') {\n+\tmy $cat = $line->{'category'};\n+\tif ($cat eq 'test_category') {\n+\t    \n+\t    my $key = $line->{'key'};\n+\t    my $value = $line->{'value'};\n+\t    $processes->{$sid}->{'data'}->{$cat}->{$key} = $value;\n+\t}\n+    }\n+\n+    # This trace2 target does not emit 'printf' events.\n+    #\n+    # elsif ($event eq 'printf') {\n+    # }\n+}\n+\n+# Dump the resulting hash into something that we can compare against\n+# in the test script.  These options make Dumper output look a little\n+# bit like JSON.  Also convert variable references of the form \"$VAR*\"\n+# so that the matching HEREDOC doesn't need to escape it.\n+\n+$Data::Dumper::Sortkeys = 1;\n+$Data::Dumper::Indent = 1;\n+$Data::Dumper::Purity = 1;\n+$Data::Dumper::Pair = ':';\n+\n+my $out = Dumper($processes);\n+$out =~ s/'/\"/g;\n+$out =~ s/\\$VAR/VAR/g;\n+\n+# Finally, if we're running this script to generate (manually confirmed)\n+# data to add to the test script, guard the indentation.\n+\n+if ($gen_heredoc == 1) {\n+    $out =~ s/^/\\t\\|/gms;\n+}\n+\n+print $out;\n-- \ngitgitgadget\n"},{"id":"367372","messageId":"1aa79cb126ec43dae1c01d3ce740d9b687d69517.1548192131.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.git.gitgitgadget@gmail.com","subject":"[PATCH 10/14] pack-objects: add trace2 regions","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-22T21:22:20Z","receivedAt":"2019-01-22T21:22:33Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWhen studying the performance of 'git push' we would like to know\nhow much time is spent at various parts of the command. One area\nthat could cause performance trouble is 'git pack-objects'.\n\nAdd trace2 regions around the three main actions taken in this\ncommand:\n\n1. Enumerate objects.\n2. Prepare pack.\n3. Write pack-file.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/pack-objects.c | 12 +++++++++++-\n 1 file changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 889df2c755..6708529e3c 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -33,6 +33,7 @@\n #include \"object-store.h\"\n #include \"dir.h\"\n #include \"midx.h\"\n+#include \"trace2.h\"\n \n #define IN_PACK(obj) oe_in_pack(&to_pack, obj)\n #define SIZE(obj) oe_size(&to_pack, obj)\n@@ -3472,6 +3473,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n+\ttrace2_region_enter(\"pack-objects\", \"enumerate-objects\", the_repository);\n \tprepare_packing_data(the_repository, &to_pack);\n \n \tif (progress)\n@@ -3486,12 +3488,20 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)\n \tif (include_tag && nr_result)\n \t\tfor_each_ref(add_ref_tag, NULL);\n \tstop_progress(&progress_state);\n+\ttrace2_region_leave(\"pack-objects\", \"enumerate-objects\", the_repository);\n \n \tif (non_empty && !nr_result)\n \t\treturn 0;\n-\tif (nr_result)\n+\tif (nr_result) {\n+\t\ttrace2_region_enter(\"pack-objects\", \"prepare-pack\", the_repository);\n \t\tprepare_pack(window, depth);\n+\t\ttrace2_region_leave(\"pack-objects\", \"prepare-pack\", the_repository);\n+\t}\n+\n+\ttrace2_region_enter(\"pack-objects\", \"write-pack-file\", the_repository);\n \twrite_pack_file();\n+\ttrace2_region_leave(\"pack-objects\", \"write-pack-file\", the_repository);\n+\n \tif (progress)\n \t\tfprintf_ln(stderr,\n \t\t\t   _(\"Total %\"PRIu32\" (delta %\"PRIu32\"),\"\n-- \ngitgitgadget\n\n"},{"id":"367374","messageId":"705c2ece6db6e054a6436f8d6549a2eff97a7155.1548192131.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.git.gitgitgadget@gmail.com","subject":"[PATCH 09/14] trace2:data: add trace2 instrumentation to index read/write","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-22T21:22:19Z","receivedAt":"2019-01-22T21:22:36Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 events to measure reading and writing the index.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n read-cache.c | 47 ++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 46 insertions(+), 1 deletion(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex bfff271a3d..f6d84bd7ae 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -2232,6 +2232,14 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)\n \t\tload_index_extensions(&p);\n \t}\n \tmunmap((void *)mmap, mmap_size);\n+\n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/version\", istate->version);\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/cache_nr\", istate->cache_nr);\n+\n \treturn istate->cache_nr;\n \n unmap:\n@@ -2263,9 +2271,17 @@ int read_index_from(struct index_state *istate, const char *path,\n \tif (istate->initialized)\n \t\treturn istate->cache_nr;\n \n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_region_enter_printf(\"index\", \"do_read_index\", the_repository,\n+\t\t\t\t   \"%s\", path);\n \ttrace_performance_enter();\n \tret = do_read_index(istate, path, 0);\n \ttrace_performance_leave(\"read cache %s\", path);\n+\ttrace2_region_leave_printf(\"index\", \"do_read_index\", the_repository,\n+\t\t\t\t   \"%s\", path);\n \n \tsplit_index = istate->split_index;\n \tif (!split_index || is_null_oid(&split_index->base_oid)) {\n@@ -2281,7 +2297,11 @@ int read_index_from(struct index_state *istate, const char *path,\n \n \tbase_oid_hex = oid_to_hex(&split_index->base_oid);\n \tbase_path = xstrfmt(\"%s/sharedindex.%s\", gitdir, base_oid_hex);\n+\ttrace2_region_enter_printf(\"index\", \"shared/do_read_index\", the_repository,\n+\t\t\t\t   \"%s\", base_path);\n \tret = do_read_index(split_index->base, base_path, 1);\n+\ttrace2_region_leave_printf(\"index\", \"shared/do_read_index\", the_repository,\n+\t\t\t\t   \"%s\", base_path);\n \tif (!oideq(&split_index->base_oid, &split_index->base->oid))\n \t\tdie(_(\"broken index, expect %s in %s, got %s\"),\n \t\t    base_oid_hex, base_path,\n@@ -2988,6 +3008,14 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \tistate->timestamp.sec = (unsigned int)st.st_mtime;\n \tistate->timestamp.nsec = ST_MTIME_NSEC(st);\n \ttrace_performance_since(start, \"write index, changed mask = %x\", istate->cache_changed);\n+\n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_data_intmax(\"index\", the_repository, \"write/version\", istate->version);\n+\ttrace2_data_intmax(\"index\", the_repository, \"write/cache_nr\", istate->cache_nr);\n+\n \treturn 0;\n }\n \n@@ -3007,7 +3035,18 @@ static int commit_locked_index(struct lock_file *lk)\n static int do_write_locked_index(struct index_state *istate, struct lock_file *lock,\n \t\t\t\t unsigned flags)\n {\n-\tint ret = do_write_index(istate, lock->tempfile, 0);\n+\tint ret;\n+\n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_region_enter_printf(\"index\", \"do_write_index\", the_repository,\n+\t\t\t\t   \"%s\", lock->tempfile->filename.buf);\n+\tret = do_write_index(istate, lock->tempfile, 0);\n+\ttrace2_region_leave_printf(\"index\", \"do_write_index\", the_repository,\n+\t\t\t\t   \"%s\", lock->tempfile->filename.buf);\n+\n \tif (ret)\n \t\treturn ret;\n \tif (flags & COMMIT_LOCK)\n@@ -3092,7 +3131,13 @@ static int write_shared_index(struct index_state *istate,\n \tint ret;\n \n \tmove_cache_to_base_index(istate);\n+\n+\ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n+\t\t\t\t   the_repository, \"%s\", (*temp)->filename.buf);\n \tret = do_write_index(si->base, *temp, 1);\n+\ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n+\t\t\t\t   the_repository, \"%s\", (*temp)->filename.buf);\n+\n \tif (ret)\n \t\treturn ret;\n \tret = adjust_shared_perm(get_tempfile_path(*temp));\n-- \ngitgitgadget\n\n"},{"id":"367373","messageId":"618e5ccb0ba5662c1f5d581d0dc50a0f64b51775.1548192131.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.git.gitgitgadget@gmail.com","subject":"[PATCH 13/14] trace2:data: add subverb for rebase","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-22T21:22:23Z","receivedAt":"2019-01-22T21:22:37Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/rebase.c | 19 +++++++++++++++++++\n 1 file changed, 19 insertions(+)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 00de70365e..aac0d52ade 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -819,6 +819,16 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tACTION_EDIT_TODO,\n \t\tACTION_SHOW_CURRENT_PATCH,\n \t} action = NO_ACTION;\n+\tstatic const char *action_names[] = {\n+\t\tN_(\"undefined\"),\n+\t\tN_(\"continue\"),\n+\t\tN_(\"skip\"),\n+\t\tN_(\"abort\"),\n+\t\tN_(\"quit\"),\n+\t\tN_(\"edit_todo\"),\n+\t\tN_(\"show_current_patch\"),\n+\t\tNULL\n+\t};\n \tconst char *gpg_sign = NULL;\n \tstruct string_list exec = STRING_LIST_INIT_NODUP;\n \tconst char *rebase_merges = NULL;\n@@ -1001,6 +1011,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tdie(_(\"The --edit-todo action can only be used during \"\n \t\t      \"interactive rebase.\"));\n \n+\tif (trace2_is_enabled()) {\n+\t\tif (is_interactive(&options))\n+\t\t\ttrace2_cmd_subverb(\"interactive\");\n+\t\telse if (exec.nr)\n+\t\t\ttrace2_cmd_subverb(\"interactive-exec\");\n+\t\telse\n+\t\t\ttrace2_cmd_subverb(action_names[action]);\n+\t}\n+\n \tswitch (action) {\n \tcase ACTION_CONTINUE: {\n \t\tstruct object_id head;\n-- \ngitgitgadget\n\n"},{"id":"367375","messageId":"a20cf139eed7d19f0faa82981d77467c9e34d239.1548192131.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.git.gitgitgadget@gmail.com","subject":"[PATCH 06/14] trace2:data: add trace2 sub-process classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-22T21:22:17Z","receivedAt":"2019-01-22T21:22:38Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 classification for long-running processes\nstarted in sub-process.c\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n sub-process.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/sub-process.c b/sub-process.c\nindex 8d2a1707cf..3f4af93555 100644\n--- a/sub-process.c\n+++ b/sub-process.c\n@@ -88,6 +88,7 @@ int subprocess_start(struct hashmap *hashmap, struct subprocess_entry *entry, co\n \tprocess->out = -1;\n \tprocess->clean_on_exit = 1;\n \tprocess->clean_on_exit_handler = subprocess_exit_handler;\n+\tprocess->trace2_child_class = \"subprocess\";\n \n \terr = start_command(process);\n \tif (err) {\n-- \ngitgitgadget\n\n"},{"id":"367376","messageId":"1a90de9dab0dd836e54fee9e08ab9e2284e1027a.1548192131.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.git.gitgitgadget@gmail.com","subject":"[PATCH 01/14] trace2: Documentation/technical/api-trace2.txt","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-22T21:22:12Z","receivedAt":"2019-01-22T21:22:38Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreated design document for Trace2 feature.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Documentation/technical/api-trace2.txt | 1158 ++++++++++++++++++++++++\n 1 file changed, 1158 insertions(+)\n create mode 100644 Documentation/technical/api-trace2.txt\n\ndiff --git a/Documentation/technical/api-trace2.txt b/Documentation/technical/api-trace2.txt\nnew file mode 100644\nindex 0000000000..501fd770f2\n--- /dev/null\n+++ b/Documentation/technical/api-trace2.txt\n@@ -0,0 +1,1158 @@\n+Trace2 API\n+==========\n+\n+The Trace2 API can be used to print debug, performance, and telemetry\n+information to stderr or a file.  The Trace2 feature is inactive unless\n+explicitly enabled by setting one or more of the `GIT_TR2`, `GIT_TR2_PERF`,\n+or `GIT_TR2_EVENT` environment variables.\n+\n+The Trace2 API is intended to replace the existing (Trace1)\n+printf-style tracing provided by the existing `GIT_TRACE` and\n+`GIT_TRACE_PERFORMANCE` facilities.  During initial implementation,\n+Trace2 and Trace1 may operate in parallel.\n+\n+The Trace2 API defines a set of high-level events with known fields,\n+such as (`start`: `argv`) and (`exit`: {`exit-code`, `elapsed-time`}).\n+\n+These high-level events are written to one or more Trace2 Targets\n+in a target-specific format.  Each Trace2 Target defines a different\n+purpose-specific view onto the event data stream.  In this mannor,\n+a single set of Trace2 API event calls in the Git source can drive\n+different types of analysis.\n+\n+Trace2 Targets\n+--------------\n+\n+Trace2 defines the following set of Trace2 Targets.  These can be\n+independently enabled and disabled.  Trace2 API events are sent to\n+each enabled target.  Each enabled target writes an event message\n+in a target-specific format (described later).\n+\n+`GIT_TR2` (NORMAL)::\n+\n+\ta simple printf format like GIT_TRACE.\n++\n+------------\n+$ export GIT_TR2=~/log.normal\n+$ git version\n+git version 2.20.1.155.g426c96fcdb\n+------------\n++\n+------------\n+$ cat ~/log.normal\n+12:28:42.620009 common-main.c:38                  version 2.20.1.155.g426c96fcdb\n+12:28:42.620989 common-main.c:39                  start git version\n+12:28:42.621101 git.c:432                         cmd_verb version (version)\n+12:28:42.621215 git.c:662                         exit elapsed:0.001227 code:0\n+12:28:42.621250 trace2/tr2_tgt_normal.c:124       atexit elapsed:0.001265 code:0\n+------------\n+\n+`GIT_TR2_PERF` (PERF)::\n+\n+\ta column-based format to replace GIT_TRACE_PERFORMANCE suitable for\n+\tdevelopment and testing, possibly to complement tools like gprof.\n++\n+------------\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git version\n+git version 2.20.1.155.g426c96fcdb\n+------------\n++\n+------------\n+$ cat ~/log.perf\n+12:28:42.620675 common-main.c:38                  | d0 | main                     | version      |     |           |           |            | 2.20.1.155.g426c96fcdb\n+12:28:42.621001 common-main.c:39                  | d0 | main                     | start        |     |           |           |            | git version\n+12:28:42.621111 git.c:432                         | d0 | main                     | cmd_verb     |     |           |           |            | version (version)\n+12:28:42.621225 git.c:662                         | d0 | main                     | exit         |     |  0.001227 |           |            | code:0\n+12:28:42.621259 trace2/tr2_tgt_perf.c:211         | d0 | main                     | atexit       |     |  0.001265 |           |            | code:0\n+------------\n+\n+`GIT_TR2_EVENT` (EVENT)::\n+\n+\ta JSON-based format of event data suitable for telemetry analysis.\n++\n+------------\n+$ export GIT_TR2_EVENT=~/log.event\n+$ git version\n+git version 2.20.1.155.g426c96fcdb\n+------------\n++\n+------------\n+$ cat ~/log.event\n+{\"event\":\"version\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.620713\",\"file\":\"common-main.c\",\"line\":38,\"evt\":\"1\",\"exe\":\"2.20.1.155.g426c96fcdb\"}\n+{\"event\":\"start\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621027\",\"file\":\"common-main.c\",\"line\":39,\"argv\":[\"git\",\"version\"]}\n+{\"event\":\"cmd_verb\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621122\",\"file\":\"git.c\",\"line\":432,\"name\":\"version\",\"hierarchy\":\"version\"}\n+{\"event\":\"exit\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621236\",\"file\":\"git.c\",\"line\":662,\"t_abs\":0.001227,\"code\":0}\n+{\"event\":\"atexit\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621268\",\"file\":\"trace2/tr2_tgt_event.c\",\"line\":163,\"t_abs\":0.001265,\"code\":0}\n+------------\n+\n+Trace2 API Definitions\n+----------------------\n+\n+All public Trace2 functions and macros are defined in `trace2.h` and\n+`trace2.c`.  These are documented in detail in `trace2.h`.  All\n+public symbols are prefixed with `trace2_`.\n+\n+There are no public Trace2 data structures.\n+\n+The Trace2 code also defines a set of private functions and data types\n+in the `trace2/` directory.  These symbols are prefixed with `tr2_`\n+and should only be used by functions in `trace2.c`.\n+\n+The functions defined by the Trace2 API can be grouped into the\n+following groups.\n+\n+Basic Command Events::\n+\n+\tThese are concerned with the lifetime of the overall git process.\n++\n+----------------\n+trace2_initialize(...)\n+\n+trace2_cmd_start(...)\n+trace2_cmd_exit(...)\n+trace2_cmd_error(...)\n+trace2_cmd_path(...)\n+----------------\n+\n+Git Command Detail Events::\n+\n+\tThese are concerned with describing the specific Git command\n+\tafter the command line, config, and environment are inspected.\n++\n+----------------\n+trace2_cmd_verb(...)\n+trace2_cmd_subverb(...)\n+trace2_cmd_alias(...)\n+\n+trace2_cmd_list_config(...)\n+trace2_cmd_set_config(...)\n+\n+trace2_def_param(...)\n+trace2_def_repo(...)\n+----------------\n+\n+Git Child Process Events::\n+\n+\tThese are concerned with the various spawned child processes,\n+\tincluding sub-git processes and hooks,\n++\n+----------------\n+trace2_child_start(...)\n+trace2_child_exit(...)\n+\n+trace2_exec(...)\n+trace2_exec_result(...)\n+----------------\n+\n+Git Thread Events::\n+\n+\tThese are concerned with Git thread usage.\n++\n+----------------\n+trace2_thread_start(...)\n+trace2_thread_exit(...)\n+----------------\n+\n+Region and Data Events::\n+\n+\tThese are concerned with recording performance data\n+\tover regions or spans of code.\n++\n+----------------\n+trace2_region_enter(...)\n+trace2_region_enter_printf(...)\n+trace2_region_leave(...)\n+trace2_region_leave_printf(...)\n+\n+trace2_data_string(...)\n+trace2_data_intmax(...)\n+trace2_data_json(...)\n+\n+trace2_printf(...)\n+----------------\n+\n+Trace2 API Usage\n+----------------\n+\n+Here is a hypothetical usage of the Trace2 API showing the intended\n+usage (without worrying about the actual Git details).\n+\n+Initialization::\n+\n+\tInitialization happens in `main()`.  The initialization code\n+\tdetermines which, if any, Trace2 Targets should be enabled and\n+\temits the `version`, `start`, and `exit` events.  It causes an\n+\t`atexit` function and `signal` handler to be registered that\n+\twill emit `atexit` and `signal` events.\n++\n+----------------\n+int main(int argc, const char **argv)\n+{\n+\tint exit_code;\n+\n+\ttrace2_initialize();\n+\ttrace2_cmd_start(argv);\n+\n+\texit_code = cmd_main(argc, argv);\n+\n+\ttrace2_cmd_exit(exit_code);\n+\n+\treturn exit_code;\n+}\n+----------------\n+\n+Command Details::\n+\n+\tAfter the basics are established, additional process\n+\tinformation can be sent to Trace2 as it is discovered, such as\n+\tthe command verb, alias expansion, interesting config\n+\tsettings, the repository worktree, error messages, and etc.\n++\n+----------------\n+int cmd_checkout(int argc, const char **argv)\n+{\n+\t// emit a single \"def_param\" event\n+\ttrace2_def_param(\"core.editor\", \"emacs\");\n+\n+\t// emit def_params for any config setting matching a pattern\n+\t// in GIT_TR2_CONFIG_PARAMS.\n+\ttrace2_cmd_list_config();\n+\n+\ttrace2_cmd_verb(\"checkout\");\n+\ttrace2_cmd_subverb(\"branch\");\n+\ttrace2_def_repo(the_repository);\n+\n+\tif (do_something(...))\n+\t    trace2_cmd_error(\"Path '%s': cannot do something\", path);\n+\n+\treturn 0;\n+}\n+----------------\n+\n+Child Processes::\n+\n+\tGit typically spawns many sub-processes, such as sub-git\n+\tcommands, shell scripts, editors, pagers, and hooks.\n+\tTrace2 child events record time spent waiting for child\n+\tprocesses and help track performance problems across\n+\tmultiple layers of child processes.\n++\n+----------------\n+void run_child(...)\n+{\n+\tint child_exit_code;\n+\tstruct child_process cmd = CHILD_PROCESS_INIT;\n+\t...\n+\n+\ttrace2_child_start(&cmd);\n+\tchild_exit_code = ...();\n+\ttrace2_child_exit(&cmd, child_exit_code);\n+}\n+----------------\n++\n+For example, the following fetch command spawned ssh, index-pack,\n+rev-list, and gc.  This example also shows that fetch took\n+5.199 seconds and of that 4.932 was in ssh.\n++\n+----------------\n+$ export GIT_TR2_BRIEF=1\n+$ export GIT_TR2=~/log.normal\n+$ git fetch origin\n+...\n+\n+$ cat ~/log.normal\n+version 2.20.1.vfs.1.1.47.g534dbe1ad1\n+start git fetch origin\n+worktree /Users/jeffhost/work/gfw\n+cmd_verb fetch (fetch)\n+child_start[0] ssh git@github.com ...\n+child_start[1] git index-pack ...\n+... (Trace2 events from child processes omitted)\n+child_exit[1] pid:14707 code:0 elapsed:0.076353\n+child_exit[0] pid:14706 code:0 elapsed:4.931869\n+child_start[2] git rev-list ...\n+... (Trace2 events from child process omitted)\n+child_exit[2] pid:14708 code:0 elapsed:0.110605\n+child_start[3] git gc --auto\n+... (Trace2 events from child process omitted)\n+child_exit[3] pid:14709 code:0 elapsed:0.006240\n+exit elapsed:5.198503 code:0\n+atexit elapsed:5.198541 code:0\n+----------------\n++\n+When a git process is a (direct or indirect) child of another\n+git process, it inherits Trace2 context information.  This\n+allows the child to print a verb hierarchy.  This example\n+shows gc as child[3] of fetch.  When the gc process reports\n+its verb as \"gc\", it also reports the hierarchy as \"fetch/gc\".\n+(In this example, trace2 messages from the child process is\n+indented for clarity.)\n++\n+----------------\n+$ export GIT_TR2_BRIEF=1\n+$ export GIT_TR2=~/log.normal\n+$ git fetch origin\n+...\n+\n+$ cat ~/log.normal\n+version 2.20.1.160.g5676107ecd.dirty\n+start git fetch official\n+worktree /Users/jeffhost/work/gfw\n+cmd_verb fetch (fetch)\n+...\n+child_start[3] git gc --auto\n+    version 2.20.1.160.g5676107ecd.dirty\n+    start /Users/jeffhost/work/gfw/git gc --auto\n+    worktree /Users/jeffhost/work/gfw\n+    cmd_verb gc (fetch/gc)\n+    exit elapsed:0.001959 code:0\n+    atexit elapsed:0.001997 code:0\n+child_exit[3] pid:20303 code:0 elapsed:0.007564\n+exit elapsed:3.868938 code:0\n+atexit elapsed:3.868970 code:0\n+----------------\n+\n+Region Events::\n+\n+\tTrace2 defines a region as a timed span of code.\n++\n+----------------\n+void wt_status_collect(struct wt_status *s)\n+{\n+\ttrace2_region_enter(\"status\", \"worktrees\", s->repo);\n+\twt_status_collect_changes_worktree(s);\n+\ttrace2_region_leave(\"status\", \"worktrees\", s->repo);\n+\n+\ttrace2_region_enter(\"status\", \"index\", s->repo);\n+\twt_status_collect_changes_index(s);\n+\ttrace2_region_leave(\"status\", \"index\", s->repo);\n+\n+\ttrace2_region_enter(\"status\", \"untracked\", s->repo);\n+\twt_status_collect_untracked(s);\n+\ttrace2_region_leave(\"status\", \"untracked\", s->repo);\n+}\n+\n+void wt_status_print(struct wt_status *s)\n+{\n+\ttrace2_region_enter(\"status\", \"print\", s->repo);\n+\tswitch (s->status_format) {\n+\t    ...\n+\t}\n+\ttrace2_region_leave(\"status\", \"print\", s->repo);\n+}\n+----------------\n++\n+In this example, scanning for untracked files ran from +0.012568 to\n++0.027149 (since the process started) and took 0.014581 seconds.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+\n+$ cat ~/log.perf\n+d0 | main                     | version      |     |           |           |            | 2.20.1.160.g5676107ecd.dirty\n+d0 | main                     | start        |     |           |           |            | git status\n+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n+d0 | main                     | cmd_verb     |     |           |           |            | status (status)\n+...\n+d0 | main                     | region_enter | r1  |  0.010988 |           | status     | label:worktrees\n+d0 | main                     | region_leave | r1  |  0.011236 |  0.000248 | status     | label:worktrees\n+d0 | main                     | region_enter | r1  |  0.011260 |           | status     | label:index\n+d0 | main                     | region_leave | r1  |  0.012542 |  0.001282 | status     | label:index\n+d0 | main                     | region_enter | r1  |  0.012568 |           | status     | label:untracked\n+d0 | main                     | region_leave | r1  |  0.027149 |  0.014581 | status     | label:untracked\n+d0 | main                     | region_enter | r1  |  0.027411 |           | status     | label:print\n+d0 | main                     | region_leave | r1  |  0.028741 |  0.001330 | status     | label:print\n+d0 | main                     | exit         |     |  0.028778 |           |            | code:0\n+d0 | main                     | atexit       |     |  0.028809 |           |            | code:0\n+----------------\n++\n+Regions may be nested.  This causes event messages to be indented.\n+Elapsed times are relative to the start of the correpsonding nesting\n+level as expected.  For example, if we add region events to:\n++\n+----------------\n+static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n+\tstruct index_state *istate, const char *base, int baselen,\n+\tstruct untracked_cache_dir *untracked, int check_only,\n+\tint stop_at_first_file, const struct pathspec *pathspec)\n+{\n+\tenum path_treatment state, subdir_state, dir_state = path_none;\n+\n+\ttrace2_region_enter_printf(\"dir\", \"read_recursive\", NULL, \"%.*s\", baselen, base);\n+\t...\n+\ttrace2_region_leave_printf(\"dir\", \"read_recursive\", NULL, \"%.*s\", baselen, base);\n+\treturn dir_state;\n+}\n+----------------\n++\n+We can further investigate the time spent scanning for untracked files.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+$ cat ~/log.perf\n+d0 | main                     | version      |     |           |           |            | 2.20.1.162.gb4ccea44db.dirty\n+d0 | main                     | start        |     |           |           |            | git status\n+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n+d0 | main                     | cmd_verb     |     |           |           |            | status (status)\n+...\n+d0 | main                     | region_enter | r1  |  0.015047 |           | status     | label:untracked\n+d0 | main                     | region_enter |     |  0.015132 |           | dir        | ..label:read_recursive\n+d0 | main                     | region_enter |     |  0.016341 |           | dir        | ....label:read_recursive vcs-svn/\n+d0 | main                     | region_leave |     |  0.016422 |  0.000081 | dir        | ....label:read_recursive vcs-svn/\n+d0 | main                     | region_enter |     |  0.016446 |           | dir        | ....label:read_recursive xdiff/\n+d0 | main                     | region_leave |     |  0.016522 |  0.000076 | dir        | ....label:read_recursive xdiff/\n+d0 | main                     | region_enter |     |  0.016612 |           | dir        | ....label:read_recursive git-gui/\n+d0 | main                     | region_enter |     |  0.016698 |           | dir        | ......label:read_recursive git-gui/po/\n+d0 | main                     | region_enter |     |  0.016810 |           | dir        | ........label:read_recursive git-gui/po/glossary/\n+d0 | main                     | region_leave |     |  0.016863 |  0.000053 | dir        | ........label:read_recursive git-gui/po/glossary/\n+...\n+d0 | main                     | region_enter |     |  0.031876 |           | dir        | ....label:read_recursive builtin/\n+d0 | main                     | region_leave |     |  0.032270 |  0.000394 | dir        | ....label:read_recursive builtin/\n+d0 | main                     | region_leave |     |  0.032414 |  0.017282 | dir        | ..label:read_recursive\n+d0 | main                     | region_leave | r1  |  0.032454 |  0.017407 | status     | label:untracked\n+...\n+d0 | main                     | exit         |     |  0.034279 |           |            | code:0\n+d0 | main                     | atexit       |     |  0.034322 |           |            | code:0\n+----------------\n++\n+Trace2 regions are similar to the existing trace_performance_enter()\n+and trace_performance_leave() routines, but are thread safe and\n+maintain per-thread stacks of timers.\n+\n+Data Events::\n+\n+\tTrace2 defines a data event to report important variables.\n+\tThese events contain the active thread and are indented\n+\trelative to the current thread's open region stack.\n++\n+----------------\n+int read_index_from(struct index_state *istate, const char *path,\n+\tconst char *gitdir)\n+{\n+\ttrace2_region_enter_printf(\"index\", \"do_read_index\", the_repository, \"%s\", path);\n+\n+\t...\n+\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/version\", istate->version);\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/cache_nr\", istate->cache_nr);\n+\n+\ttrace2_region_leave_printf(\"index\", \"do_read_index\", the_repository, \"%s\", path);\n+}\n+----------------\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+$ cat ~/log.perf\n+d0 | main                     | version      |     |           |           |            | 2.20.1.156.gf9916ae094.dirty\n+d0 | main                     | start        |     |           |           |            | git status\n+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n+d0 | main                     | cmd_verb     |     |           |           |            | status (status)\n+d0 | main                     | region_enter | r1  |  0.001791 |           | index      | label:do_read_index .git/index\n+d0 | main                     | data         | r1  |  0.002494 |  0.000703 | index      | ..read/version:2\n+d0 | main                     | data         | r1  |  0.002520 |  0.000729 | index      | ..read/cache_nr:3552\n+d0 | main                     | region_leave | r1  |  0.002539 |  0.000748 | index      | label:do_read_index .git/index\n+...\n+----------------\n+\n+Thread Events::\n+\n+\tTrace2 defines events to mark the start and end of a thread.\n+\tA thread name constructed during the start event. This name is\n+\tadded to subsequent thread-specific events.  The thread exit\n+\tevent reports the elapsed time in the thread.\n++\n+For example, the multithreaded preload-index code can be\n+instrumented with a region around the thread pool and then\n+per-thread start and exit events within the threadproc.\n++\n+----------------\n+static void *preload_thread(void *_data)\n+{\n+\ttrace2_thread_start(\"preload_thread\");\n+\n+\ttrace2_data_intmax(\"index\", the_repository, \"offset\", p->offset);\n+\ttrace2_data_intmax(\"index\", the_repository, \"count\", nr);\n+\n+\tdo {\n+\t    ...\n+\t} while (--nr > 0);\n+\t...\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+void preload_index(struct index_state *index,\n+\tconst struct pathspec *pathspec,\n+\tunsigned int refresh_flags)\n+{\n+\ttrace2_region_enter(\"index\", \"preload\", the_repository);\n+\n+\tfor (i = 0; i < threads; i++) {\n+\t    ... /* create thread */\n+\t}\n+\n+\tfor (i = 0; i < threads; i++) {\n+\t    ... /* join thread */\n+\t}\n+\n+\ttrace2_region_leave(\"index\", \"preload\", the_repository);\n+}\n+----------------\n++\n+In this example preload_index() was executed by the `main` thread\n+and started the `preload` region.  Seven threads, named\n+`th01:preload_thread` through `th07:preload_thread`, were started.\n+Events from each thread are atomically appended to the shared target\n+stream as they occur so they may appear in random order with respect\n+other threads. Finally, the main thread waits for the threads to\n+finish and leaves the region.\n++\n+Data events are tagged with the active thread name.  They are used\n+to report the per-thread parameters.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+$ cat ~/log.perf\n+d0 | main                     | version      |     |           |           |            | 2.20.1.156.gf9916ae094.dirty\n+d0 | main                     | start        |     |           |           |            | git status\n+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n+d0 | main                     | cmd_verb     |     |           |           |            | status (status)\n+...\n+d0 | main                     | region_enter | r1  |  0.002595 |           | index      | label:preload\n+d0 | th01:preload_thread      | thread_start |     |  0.002699 |           |            |\n+d0 | th02:preload_thread      | thread_start |     |  0.002721 |           |            |\n+d0 | th01:preload_thread      | data         | r1  |  0.002736 |  0.000037 | index      | offset:0\n+d0 | th02:preload_thread      | data         | r1  |  0.002751 |  0.000030 | index      | offset:2032\n+d0 | th03:preload_thread      | thread_start |     |  0.002711 |           |            |\n+d0 | th06:preload_thread      | thread_start |     |  0.002739 |           |            |\n+d0 | th01:preload_thread      | data         | r1  |  0.002766 |  0.000067 | index      | count:508\n+d0 | th06:preload_thread      | data         | r1  |  0.002856 |  0.000117 | index      | offset:2540\n+d0 | th03:preload_thread      | data         | r1  |  0.002824 |  0.000113 | index      | offset:1016\n+d0 | th04:preload_thread      | thread_start |     |  0.002710 |           |            |\n+d0 | th02:preload_thread      | data         | r1  |  0.002779 |  0.000058 | index      | count:508\n+d0 | th06:preload_thread      | data         | r1  |  0.002966 |  0.000227 | index      | count:508\n+d0 | th07:preload_thread      | thread_start |     |  0.002741 |           |            |\n+d0 | th07:preload_thread      | data         | r1  |  0.003017 |  0.000276 | index      | offset:3048\n+d0 | th05:preload_thread      | thread_start |     |  0.002712 |           |            |\n+d0 | th05:preload_thread      | data         | r1  |  0.003067 |  0.000355 | index      | offset:1524\n+d0 | th05:preload_thread      | data         | r1  |  0.003090 |  0.000378 | index      | count:508\n+d0 | th07:preload_thread      | data         | r1  |  0.003037 |  0.000296 | index      | count:504\n+d0 | th03:preload_thread      | data         | r1  |  0.002971 |  0.000260 | index      | count:508\n+d0 | th04:preload_thread      | data         | r1  |  0.002983 |  0.000273 | index      | offset:508\n+d0 | th04:preload_thread      | data         | r1  |  0.007311 |  0.004601 | index      | count:508\n+d0 | th05:preload_thread      | thread_exit  |     |  0.008781 |  0.006069 |            |\n+d0 | th01:preload_thread      | thread_exit  |     |  0.009561 |  0.006862 |            |\n+d0 | th03:preload_thread      | thread_exit  |     |  0.009742 |  0.007031 |            |\n+d0 | th06:preload_thread      | thread_exit  |     |  0.009820 |  0.007081 |            |\n+d0 | th02:preload_thread      | thread_exit  |     |  0.010274 |  0.007553 |            |\n+d0 | th07:preload_thread      | thread_exit  |     |  0.010477 |  0.007736 |            |\n+d0 | th04:preload_thread      | thread_exit  |     |  0.011657 |  0.008947 |            |\n+d0 | main                     | region_leave | r1  |  0.011717 |  0.009122 | index      | label:preload\n+...\n+d0 | main                     | exit         |     |  0.029996 |           |            | code:0\n+d0 | main                     | atexit       |     |  0.030027 |           |            | code:0\n+----------------\n++\n+In this example, the preload region took 0.009122 seconds.  The 7 threads\n+took between 0.006069 and 0.008947 seconds to work on their portion of\n+the index.  Thread \"th01\" worked on 508 items at offset 0.  Thread \"th02\"\n+worked on 508 items at offset 2032.  Thread \"th04\" worked on 508 itemts\n+at offset 508.\n++\n+This example also shows that thread names are assigned in a racy manner\n+as each thread starts and allocates TLS storage.\n+\n+Printf Events::\n+\n+\tTrace2 defines a printf interface to write arbitrary messages\n+\tto the targets.  These are convenient for development, but\n+\tmay more difficult to post-process.\n+\n+Trace2 Target Formats\n+---------------------\n+\n+NORMAL Target Format\n+~~~~~~~~~~~~~~~~~~~~\n+\n+NORMAL format is enabled when the `GIT_TR2` environment variable\n+is set to \"1\" or an absolute pathname.\n+\n+Events are written as lines of the form:\n+\n+------------\n+[<time> SP <filename>:<line> SP+] <event-name> [[SP] <event-message>] LF\n+------------\n+\n+`<event-name>`::\n+\n+\tis the event name.\n+`<event-message>`::\n+\tis a free-form printf message intended for human consumption.\n++\n+Note that this may contain embedded LF or CRLF characters that are\n+not escaped, so the event may spill across multiple lines.\n+\n+If `GIT_TR2_BRIEF` is true, the time, filename, and line number fields\n+are omitted.\n+\n+This target is intended to be more of a summary (like GIT_TRACE) and\n+less detailed than the other targets, and therefore does not emit\n+messages for some of the Trace2 API verbs.  For example, thread,\n+region, and data events are omitted.\n+\n+PERF Target Format\n+~~~~~~~~~~~~~~~~~~\n+\n+PERF format is enabled when the `GIT_TR2_PERF` environment variable\n+is set to \"1\" or an absolute pathname.\n+\n+Events are written as lines of the form:\n+\n+------------\n+[<time> SP <filename>:<line> SP+\n+    BAR SP] d<depth> SP\n+    BAR SP <thread-name> SP+\n+    BAR SP <event-name> SP+\n+    BAR SP [r<repo-id>] SP+\n+    BAR SP [<t_abs>] SP+\n+    BAR SP [<t_rel>] SP+\n+    BAR SP [<category>] SP+\n+    BAR SP DOTS* <perf-event-message>\n+    LF\n+------------\n+\n+`<depth>`::\n+\tis the git process depth.  This is the number of parent\n+\tgit processes.  A top-level git command has depth value 0.\n+\tA child of it has depth value 1.\n+\n+`<thread-name>`::\n+\tis a unique name for the thread.  The primary thread\n+\tis called \"main\".  Other thread names are of the form \"th%d:%s\"\n+\tand include a unique number and the name of the thread-proc.\n+\n+`<event-name>`::\n+\tis the event name.\n+\n+`<repo-id>`::\n+\twhen present, is a number indicating the repository\n+\tin use.  A `def_repo` event is emitted when a repository is\n+\topened.  This defines the repo-id and associated worktree.\n+\tSubsequent repo-specific events will reference this repo-id.\n++\n+Currently, this is always \"r1\" for the main repository.\n+This field is in anticipation of in-proc submodules in the future.\n+\n+`<t_abs>`::\n+\twhen present, is the absolute time in seconds since the\n+\tprogram started.\n+\n+`<t_rel>`::\n+\twhen present, is time in seconds relative to the start of\n+\tthe current region.  For a thread-exit event, it is the elapsed\n+\ttime of the thread.\n+\n+`<category>`::\n+\tis present on region and data events and is used to\n+\tindicate a broad category, such as \"index\" or \"status\".\n++\n+Currently, this is advisory.  This field is in anticipation\n+of category-based filtering, similar to the `GIT_TRACE_<key>`\n+facility in Trace1.\n+\n+`<perf-event-message>`::\n+\tis a free-form printf message intended for human consumption.\n+\n+If `GIT_TR2_PERF_BRIEF` is true, the time, filename, and line number\n+fields are omitted.\n+\n+This target is intended for interactive performance analysis\n+during development and is quite noisy.\n+\n+EVENT Target Format\n+~~~~~~~~~~~~~~~~~~~\n+\n+EVENT format is enabled when the `GIT_TR2_EVENT` environment\n+variable is set to \"1\" or an absolute pathname.\n+\n+Each event is a JSON-object containing multiple key/value pairs\n+written as a single line and followed by a LF.\n+\n+------------\n+'{' <key> ':' <value> [',' <key> ':' <value>]* '}' LF\n+------------\n+\n+Some key/value pairs are common to all events and some are\n+event-specific.\n+\n+Common Key/Value Pairs\n+^^^^^^^^^^^^^^^^^^^^^^\n+\n+The following key/value pairs are common to all events:\n+\n+------------\n+{\n+\t\"event\":\"version\",\n+\t\"sid\":\"1547659722619736-11614\",\n+\t\"thread\":\"main\",\n+\t\"time\":\"2019-01-16 17:28:42.620713\",\n+\t\"file\":\"common-main.c\",\n+\t\"line\":38,\n+\t...\n+}\n+------------\n+\n+`\"event\":<event>`::\n+\tis the event name.\n+\n+`\"sid\":<sid>`::\n+\tis the session-id.  This is a unique string to identify the\n+\tprocess instance to allow all events emitted by a process to\n+\tbe identified.  A session-id is used instead of a PID because\n+\tPIDs are recycled by the OS.  For child git processes, the\n+\tsession-id is prepended with the session-id of the parent git\n+\tprocess to allow parent-child relationships to be identified\n+\tduring post-processing.\n+\n+`\"thread\":<thread>`::\n+\tis the thread name.\n+\n+`\"time\":<time>`::\n+\tis the UTC time of the event.\n+\n+`\"file\":<filename>`::\n+\tis source file generating the event.\n+\n+`\"line\":<line-number>`::\n+\tis the integer source line number generating the event.\n+\n+`\"repo\":<repo-id>`::\n+\twhen present, is the integer repo-id as described previously.\n+\n+If `GIT_TR2_EVENT_BRIEF` is true, the file and line fields are omitted\n+from all events and the time field is only present on the `start` and\n+`atexit` events.\n+\n+Event-Specific Key/Value Pairs\n+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n+\n+`version`::\n+\tThis event gives the version of the executable and the EVENT format.\n++\n+------------\n+{\n+\t\"event\":\"version\",\n+\t...\n+\t\"evt\":\"1\",\t\t       # EVENT format version\n+\t\"exe\":\"2.20.1.155.g426c96fcdb\" # git version\n+}\n+------------\n+\n+`start`::\n+\tThis event contains the complete argv received by main().\n++\n+------------\n+{\n+\t\"event\":\"start\",\n+\t...\n+\t\"argv\":[\"git\",\"version\"]\n+}\n+------------\n+\n+`exit`::\n+\tThis event is emitted when git calls `exit()`.\n++\n+------------\n+{\n+\t\"event\":\"exit\",\n+\t...\n+\t\"t_abs\":0.001227, # elapsed time in seconds\n+\t\"code\":0\t  # exit code\n+}\n+------------\n+\n+`atexit`::\n+\tThis event is emitted by the Trace2 `atexit` routine during\n+\tfinal shutdown.  It should be the last event emitted by the\n+\tprocess.  (The elapsed time reported here is greater than the\n+\ttime reported in the `exit` event because it runs after all\n+\tother atexit tasks have completed.)\n++\n+------------\n+{\n+\t\"event\":\"atexit\",\n+\t...\n+\t\"t_abs\":0.001227,\n+\t\"code\":0\n+}\n+------------\n+\n+`signal`::\n+\tThis event is emitted when the program is terminated by a user\n+\tsignal.  Depending on the platform, the signal event may\n+\tprevent the `atexit` event from being generated.\n++\n+------------\n+{\n+\t\"event\":\"signal\",\n+\t...\n+\t\"t_abs\":0.001227,\n+\t\"signal\":13\n+}\n+------------\n+\n+`error`::\n+\tThis event is emitted when one of the `error()`, `die()`,\n+\tor `usage()` functions are called.\n++\n+------------\n+{\n+\t\"event\":\"error\",\n+\t...\n+\t\"msg\":\"invalid option: --cahced\", # formatted error message\n+\t\"fmt\":\"invalid option: %s\"\t  # error format string\n+}\n+------------\n++\n+The error event may be emitted more than once.  The format string\n+allows post-processors to group errors by type without worrying\n+about specific error arguments.\n+\n+`cmd_path`::\n+\tThis event contains the discovered full path of the git\n+\texecutable (on platforms that are configured to resolve it).\n++\n+------------\n+{\n+\t\"event\":\"cmd_path\",\n+\t...\n+\t\"path\":\"C:/work/gfw/git.exe\"\n+}\n+------------\n+\n+`cmd_verb`::\n+\tThis event contains the primary verb for this command\n+\tand the hierarchy of command verbs.\n++\n+------------\n+{\n+\t\"event\":\"cmd_verb\",\n+\t...\n+\t\"name\":\"pack-objects\",\n+\t\"hierarchy\":\"push/pack-objects\"\n+}\n+------------\n++\n+Normally, the `name` field contains the canonical name of the\n+command.  When a canonical command verb is not available, one of\n+these special values are used:\n++\n+------------\n+\"_query_\"            # \"git --html-path\"\n+\"_run_dashed_\"       # when \"git foo\" tries to run \"git-foo\"\n+\"_run_shell_alias_\"  # alias expansion to a shell command\n+\"_run_git_alias_\"    # alias expansion to a git command\n+\"_usage_\"            # usage error\n+------------\n+\n+`cmd_subverb`::\n+\tThis event, when present, describes the variant of the main\n+\tcommand verb.  This event may be emitted more than once.\n++\n+------------\n+{\n+\t\"event\":\"cmd_subverb\",\n+\t...\n+\t\"name\":\"branch\"\n+}\n+------------\n++\n+The `name` field is an arbitrary string that makes sense in the\n+context of the primary verb. For example, checkout can checkout a\n+branch or an individual file.  And these variations typically have\n+different performance characteristics that are not comparable.\n+\n+`alias`::\n+\tThis event is present when an alias is expanded.\n++\n+------------\n+{\n+\t\"event\":\"alias\",\n+\t...\n+\t\"alias\":\"l\",\t\t # registered alias\n+\t\"argv\":[\"log\",\"--graph\"] # alias expansion\n+}\n+------------\n+\n+`child_start`::\n+\tThis event describes a child process that is about to be\n+\tspawned.\n++\n+------------\n+{\n+\t\"event\":\"child_start\",\n+\t...\n+\t\"child_id\":2,\n+\t\"child_class\":\"?\",\n+\t\"use_shell\":false,\n+\t\"argv\":[\"git\",\"rev-list\",\"--objects\",\"--stdin\",\"--not\",\"--all\",\"--quiet\"]\n+\n+\t\"hook_name\":\"<hook_name>\"  # present when child_class is \"hook\"\n+\t\"cd\":\"<path>\"\t\t   # present when cd is required\n+}\n+------------\n++\n+The `child_id` field can be used to match this child_start with the\n+corresponding child_exit event.\n++\n+The `child_class` field is a rough classification, such as \"editor\",\n+\"pager\", \"transport/*\", and \"hook\".  Unclassified children are classified\n+with \"?\".\n+\n+`child_exit`::\n+\tThis event is generated after the git process has returned\n+\tfrom the waitpid() and collected the exit information from the\n+\tchild.\n++\n+------------\n+{\n+\t\"event\":\"child_exit\",\n+\t...\n+\t\"child_id\":2,\n+\t\"pid\":14708,\t # child PID\n+\t\"code\":0,\t # child exit-code\n+\t\"t_rel\":0.110605 # observed run-time of child process\n+}\n+------------\n++\n+Note that the session-id of the child process is not available to\n+the current/spawning process, so the child's PID is reported here as\n+a hint for post-processing.  (But it is only a hint because the child\n+proces may be a shell script which doesn't have a session-id.)\n++\n+Note that the `t_rel` field contains the observed run time in seconds\n+for the child process (starting before the fork/exec/spawn and\n+stopping after the waitpid() and includes OS process creation overhead).\n+So this time will be slightly larger than the atexit time reported by\n+the child process itself.\n+\n+`exec`::\n+\tThis event is generated before git attempts to `exec()`\n+\tanother command rather than starting a child process.\n++\n+------------\n+{\n+\t\"event\":\"exec\",\n+\t...\n+\t\"exec_id\":0,\n+\t\"exe\":\"git\",\n+\t\"argv\":[\"foo\", \"bar\"]\n+}\n+------------\n++\n+Since `exec()` does not return if successful, this will be the last\n+event generated for the current git command before the new git commit\n+starts.\n++\n+The `exec_id` field is a command-unique id and is only useful if the\n+`exec()` fails and a corresponding exec_result event is generated.\n+\n+`exec_result`::\n+\tThis event is generated if the `exec()` fails and control\n+\treturns to the current git command.\n++\n+------------\n+{\n+\t\"event\":\"exec_result\",\n+\t...\n+\t\"exec_id\":0,\n+\t\"code\":1      # error code (errno) from exec()\n+}\n+------------\n+\n+`thread_start`::\n+\tThis event is generated when a thread is started.  It is\n+\tgenerated from *within* the new thread's thread-proc (for TLS\n+\treasons).\n++\n+------------\n+{\n+\t\"event\":\"thread_start\",\n+\t...\n+\t\"thread\":\"th02:preload_thread\"\n+}\n+------------\n+\n+`thread_exit`::\n+\tThis event is generated when a thread exits.  It is generated\n+\tfrom *within* the thread's thread-proc (for TLS reasons).\n++\n+------------\n+{\n+\t\"event\":\"thread_exit\",\n+\t...\n+\t\"thread\":\"th02:preload_thread\",\n+\t\"t_rel\":0.007328                # thread elapsed time\n+}\n+------------\n+\n+`def_param`::\n+\tThis event is generated to log a global key/value pair for the\n+\tcurrent command.\n++\n+------------\n+{\n+\t\"event\":\"def_param\",\n+\t...\n+\t\"param\":\"core.editor\",\n+\t\"value\":\"emacs\"\n+}\n+------------\n++\n+This is intended to log \"values of interest\" for post-processing.  For\n+example, this might be configuration settings known to affect\n+performance or the remote URL so that different repos can be grouped\n+differently.  For thread- or region-specific key/values pairs, see the\n+`data` and `data_json` events.\n+\n+`def_repo`::\n+\tThis event defines a repo-id and associates it with the root\n+\tof the worktree.  Subsequent repo-relative events will refer\n+\tto this repo-id.  Repo-id number 1 refers to the main\n+\trepository.\n++\n+------------\n+{\n+\t\"event\":\"def_repo\",\n+\t...\n+\t\"repo\":1,\n+\t\"worktree\":\"/Users/jeffhost/work/gfw\"\n+}\n+------------\n++\n+As stated earlier, the repo-id is currently always 1, so there will\n+only be one def_repo event.  Later, if in-proc submodules are\n+supported, a def_repo event should be emitted for each submodule\n+visited.\n+\n+`region_enter`::\n+\tThis event is generated when entering a region.\n+\tIt serves as a marker in the event stream.  It starts an activity timer\n+\tfor the region.  And it pushes a frame on the region stack for the\n+\tcurrent thread.\n++\n+------------\n+{\n+\t\"event\":\"region_enter\",\n+\t...\n+\t\"repo\":1,\n+\t\"nesting\":1,\t\t # current region stack depth\n+\t\"category\":\"index\",\t # optional\n+\t\"label\":\"do_read_index\", # optional\n+\t\"msg\":\".git/index\"\t # optional\n+}\n+------------\n++\n+The `category` field may be used in a future enhancement to\n+do category-based filtering.\n++\n+The `GIT_TR2_EVENT_NESTING` environment variable can be used to\n+filter deeply nested regions and data events.  It defaults to \"2\".\n+\n+`region_leave`::\n+\tThis event is generated when leaving a region.  It serves as a\n+\tmarker in the event stream and reports the elapsed time spent\n+\tin the region.  It pops a frame off of the region stack for\n+\tthe current thread.\n++\n+------------\n+{\n+\t\"event\":\"region_leave\",\n+\t...\n+\t\"repo\":1,\n+\t\"t_rel\":0.002876,\t # time spent in region in seconds\n+\t\"nesting\":1,\t\t # region stack depth\n+\t\"category\":\"index\",\t # optional\n+\t\"label\":\"do_read_index\", # optional\n+\t\"msg\":\".git/index\"\t # optional\n+}\n+------------\n++\n+The examples in this document have generally shown the region_enter\n+and region_leave events with the same category and label fields.  This\n+is not a requirement, but makes it easier to understand the logs.\n+\n+`data`::\n+\tThis event is generated to log a thread- and region-local\n+\tkey/value pair.  For example, during a recursive treewalk,\n+\tlogging the relative path and the number of entries in the\n+\ttree.\n++\n+------------\n+{\n+\t\"event\":\"data\",\n+\t...\n+\t\"repo\":1,              # optional\n+\t\"t_abs\":0.024107,      # absolute elapsed time\n+\t\"t_rel\":0.001031,      # elapsed time in region/thread\n+\t\"nesting\":2,\n+\t\"category\":\"index\",\n+\t\"key\":\"read/cache_nr\",\n+\t\"value\":\"3552\"}\n+}\n+------------\n++\n+The `value` field may be an integer or a string.\n+\n+`data-json`::\n+\tThis event is generated to log a pre-formatted JSON string\n+\tcontaining structured data.\n++\n+------------\n+{\n+\t\"event\":\"data_json\",\n+\t...\n+\t\"repo\":1,              # optional\n+\t\"t_abs\":0.015905,\n+\t\"t_rel\":0.015905,\n+\t\"nesting\":1,\n+\t\"category\":\"process\",\n+\t\"key\":\"windows/ancestry\",\n+\t\"value\":[\"bash.exe\",\"bash.exe\"]\n+}\n+------------\n+\n+Appendix: Relationship to the Existing Trace Api (api-trace.txt)\n+----------------------------------------------------------------\n+\n+Eventually, I would like Trace2 to replace the existing Trace1 API.\n+\n+Trace2 is a superset of Trace1 and the existing Trace1 API calls could\n+be converted to Trace2, but that work has not yet been attempted.\n+\n+There are a few issues here:\n+\n+* Updating existing tests that assume GIT_TRACE format messages.\n+\n+* How to best handle custom GIT_TRACE_<key> messages?\n+\n+** The GIT_TRACE_<key> mechanism allows each <key> to write to a\n+different file (in addition to just stderr).\n+\n+** Do we want to maintain that ability or simply write to the existing\n+Trace2 targets (and convert <key> to a \"category\").\n+\n-- \ngitgitgadget\n\n"},{"id":"367390","messageId":"xmqqlg3ccm8f.fsf@gitster-ct.c.googlers.com","threadId":"50293","inReplyTo":"pull.108.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 00/14] Trace2 tracing facility","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-01-22T23:21:20Z","receivedAt":"2019-01-22T23:21:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> This patch series contains a greatly refactored version of my original\n> Trace2 series [1] from August 2018.\n\nGreat to see this series revived.  Looking forward to reading it\nthru.\n\nThanks.\n\n> A new design doc in Documentation/technical/api-trace2.txt (in the first\n> commit) explains the relationship of Trace2 to the current tracing facility.\n> Calls to the current tracing facility have not been changed, rather new\n> trace2 calls have been added so that both continue to work in parallel for\n> the time being.\n\n"},{"id":"367397","messageId":"75ceaf05-d479-67f8-3684-c8ba948f2bda@gmail.com","threadId":"50293","inReplyTo":"1aa79cb126ec43dae1c01d3ce740d9b687d69517.1548192131.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 10/14] pack-objects: add trace2 regions","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-01-23T01:19:25Z","receivedAt":"2019-01-23T01:19:31Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 1/22/2019 4:22 PM, Derrick Stolee via GitGitGadget wrote:\n> From: Derrick Stolee <dstolee@microsoft.com>\n>\n> When studying the performance of 'git push' we would like to know\n> how much time is spent at various parts of the command. One area\n> that could cause performance trouble is 'git pack-objects'.\n>\n> Add trace2 regions around the three main actions taken in this\n> command:\n>\n> 1. Enumerate objects.\n> 2. Prepare pack.\n> 3. Write pack-file.\n>\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n\nWe've had this patch in our private branch for a while, and is how we \ndiscovered the need for the sparse push algorithm [1]. We will use it to \nmeasure it's effectiveness as new Microsoft users onboard to the new \nalgorithm.\n\nThanks,\n\n-Stolee\n\n[1] https://public-inbox.org/git/pull.89.v5.git.gitgitgadget@gmail.com/\n\n"},{"id":"367462","messageId":"xmqqbm47ayhv.fsf@gitster-ct.c.googlers.com","threadId":"50293","inReplyTo":"1a90de9dab0dd836e54fee9e08ab9e2284e1027a.1548192131.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 01/14] trace2: Documentation/technical/api-trace2.txt","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-01-23T20:51:40Z","receivedAt":"2019-01-23T20:51:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +These high-level events are written to one or more Trace2 Targets\n> +in a target-specific format.  Each Trace2 Target defines a different\n> +purpose-specific view onto the event data stream.  In this mannor,\n\n\"In this manner\"\n\n> +a single set of Trace2 API event calls in the Git source can drive\n> +different types of analysis.\n> +\n> ...\n> +$ cat ~/log.perf\n> +12:28:42.620675 common-main.c:38                  | d0 | main                     | version      |     |           |           |            | 2.20.1.155.g426c96fcdb\n> +12:28:42.621001 common-main.c:39                  | d0 | main                     | start        |     |           |           |            | git version\n> +12:28:42.621111 git.c:432                         | d0 | main                     | cmd_verb     |     |           |           |            | version (version)\n> +12:28:42.621225 git.c:662                         | d0 | main                     | exit         |     |  0.001227 |           |            | code:0\n> +12:28:42.621259 trace2/tr2_tgt_perf.c:211         | d0 | main                     | atexit       |     |  0.001265 |           |            | code:0\n> +------------\n> ...\n> +\n> +trace2_def_param(...)\n\nNot limited to this single one, but please either\n\n - omit \"...\" in parens, unless all of these functions take varargs\n   of unspecified type (which I do not think is the case), or\n\n - write a proper prototype for these functions, explain what the\n   function itself and what the parameters are for.\n\nI'll complain about lack of info around here later X-<.\n\n> +trace2_def_repo(...)\n\n> +----------------\n> +\n> +Git Child Process Events::\n> +\n> +\tThese are concerned with the various spawned child processes,\n> +\tincluding sub-git processes and hooks,\n> ++\n> +----------------\n> +trace2_child_start(...)\n> +trace2_child_exit(...)\n> +\n> +trace2_exec(...)\n> +trace2_exec_result(...)\n> +----------------\n> +\n> +Git Thread Events::\n> +\n> +\tThese are concerned with Git thread usage.\n> ++\n> +----------------\n> +trace2_thread_start(...)\n> +trace2_thread_exit(...)\n> +----------------\n\nLack of _wait()/_join() feels a bit curious, but _exit() from what\nis being waited would suffice as a substitute.\n\n> +Initialization::\n> +\n> +\tInitialization happens in `main()`.  The initialization code\n> +\tdetermines which, if any, Trace2 Targets should be enabled and\n> +\temits the `version`, `start`, and `exit` events.  It causes an\n> +\t`atexit` function and `signal` handler to be registered that\n> +\twill emit `atexit` and `signal` events.\n> ++\n> +----------------\n> +int main(int argc, const char **argv)\n> +{\n> +\tint exit_code;\n> +\n> +\ttrace2_initialize();\n> +\ttrace2_cmd_start(argv);\n\nNobody other than trace2 integration would make a call to this\nhelper, so it may not matter, but sending av alone without ac, even\nthough ac is almost always redundant, feels somewhat unexpected.\n\n> +Command Details::\n> +\n> +\tAfter the basics are established, additional process\n> +\tinformation can be sent to Trace2 as it is discovered, such as\n> +\tthe command verb, alias expansion, interesting config\n> +\tsettings, the repository worktree, error messages, and etc.\n> ++\n> +----------------\n> +int cmd_checkout(int argc, const char **argv)\n> +{\n> +\t// emit a single \"def_param\" event\n> +\ttrace2_def_param(\"core.editor\", \"emacs\");\n\nWithout knowing what \"def_param event\" is, this example is hard to\nfathom.  At this point in the doc, the reader does not even know\nwhat \"def\" stands for.  Is this call to define a param called\ncore.editor?  Is it reporting that the default value for core.editor\nis emacs?\n\n> +\t// emit def_params for any config setting matching a pattern\n> +\t// in GIT_TR2_CONFIG_PARAMS.\n> +\ttrace2_cmd_list_config();\n\nAs the reader does not know what def_param is, this is also hard to\nfollow.\n\n> +\ttrace2_cmd_verb(\"checkout\");\n> +\ttrace2_cmd_subverb(\"branch\");\n\nThese are guessable.  It probably reports what the codepath is\ndoing.\n\n> +\ttrace2_def_repo(the_repository);\n\nAgain, this is not easy to guess.  Is it reporting what the default\nrepository is?\n\n> +\tif (do_something(...))\n> +\t    trace2_cmd_error(\"Path '%s': cannot do something\", path);\n\nThis is guessable, which is good.\n\n> +void run_child(...)\n> +{\n> +\tint child_exit_code;\n> +\tstruct child_process cmd = CHILD_PROCESS_INIT;\n> +\t...\n> +\n> +\ttrace2_child_start(&cmd);\n> +\tchild_exit_code = ...();\n> +\ttrace2_child_exit(&cmd, child_exit_code);\n\nEven though child_exit() has not been explained just like\ndef_param(), this is far more guessable.  I wonder why it is.  The\nname of the variable passed to it in the example certainly helps, as\nit is totally unclear where the string constant \"emacs\" in the\nearlier example came from (e.g. is it hardcoded in the program?  is\nit merely illustrating \"here is how you report the value you have\ndecided to use for 'core.editor' variable\"?).\n\n> ...\n> +$ cat ~/log.perf\n> +d0 | main                     | version      |     |           |           |            | 2.20.1.160.g5676107ecd.dirty\n> +d0 | main                     | start        |     |           |           |            | git status\n> +d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n> +d0 | main                     | cmd_verb     |     |           |           |            | status (status)\n> +...\n> +d0 | main                     | region_enter | r1  |  0.010988 |           | status     | label:worktrees\n\nIt is hard to guess what \"d0\" and \"r1\" stand for here.  \n\nIn an earlier example we also saw an unexplained \"d0\" there, which I\nthink was OK because the illustration was merely to give the \"feel\"\nof the format up there.  But now we are giving a bit more detailed\nexplanation, we probably would want to at least briefly mention what\neach column means.\n\nI'd stop here for now, as I am more interested in reading the code\n;-)\n\n"},{"id":"367642","messageId":"20190125112203.GB6702@szeder.dev","threadId":"50293","inReplyTo":"4aaf4834bfa9f2169e2c00f7cdc6c75281567c15.1548192131.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 02/14] trace2: create new combined trace facility","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2019-01-25T11:22:03Z","receivedAt":"2019-01-25T11:22:10Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Tue, Jan 22, 2019 at 01:22:14PM -0800, Jeff Hostetler via GitGitGadget wrote:\n> diff --git a/trace2/tr2_tgt_perf.c b/trace2/tr2_tgt_perf.c\n> new file mode 100644\n> index 0000000000..035855d883\n> --- /dev/null\n> +++ b/trace2/tr2_tgt_perf.c\n> @@ -0,0 +1,573 @@\n\n> +static struct strbuf dots = STRBUF_INIT;\n\n> +static void perf_fmt_prepare(\n> +\tconst char *event_name, struct tr2tls_thread_ctx *ctx,\n> +\tconst char *file, int line, const struct repository *repo,\n> +\tuint64_t *p_us_elapsed_absolute, uint64_t *p_us_elapsed_relative,\n> +\tconst char *category, struct strbuf *buf)\n> +{\n> +\tint len;\n> +\n> +\tstrbuf_setlen(buf,0);\n> +\n> +\tif (!tr2env_perf_brief) {\n> +\n> +\t\tstruct tr2_tbuf tb_now;\n> +\n> +\t\ttr2_tbuf_local_time(&tb_now);\n> +\t\tstrbuf_addstr(buf, tb_now.buf);\n> +\t\tstrbuf_addch(buf, ' ');\n> +\n> +\t\tif (file && *file)\n> +\t\t\tstrbuf_addf(buf, \"%s:%d \", file, line);\n> +\t\twhile (buf->len < TR2FMT_PERF_FL_WIDTH)\n> +\t\t\tstrbuf_addch(buf, ' ');\n> +\n> +\t\tstrbuf_addstr(buf, \"| \");\n> +\t}\n> +\n> +\tstrbuf_addf(buf, \"d%d | \", tr2_sid_depth());\n> +\tstrbuf_addf(buf, \"%-*s | %-*s | \",\n> +\t\t    TR2_MAX_THREAD_NAME, ctx->thread_name.buf,\n> +\t\t    TR2FMT_PERF_MAX_EVENT_NAME, event_name);\n> +\n> +\tlen = buf->len + TR2FMT_PERF_REPO_WIDTH;\n> +\tif (repo)\n> +\t\tstrbuf_addf(buf, \"r%d \", repo->trace2_repo_id);\n> +\twhile (buf->len < len)\n> +\t\tstrbuf_addch(buf, ' ' );\n> +\tstrbuf_addstr(buf, \"| \");\n> +\n> +\tif (p_us_elapsed_absolute)\n> +\t\tstrbuf_addf(buf, \"%9.6f | \",\n> +\t\t\t    ((double)(*p_us_elapsed_absolute)) / 1000000.0);\n> +\telse\n> +\t\tstrbuf_addf(buf, \"%9s | \", \" \");\n> +\n> +\tif (p_us_elapsed_relative)\n> +\t\tstrbuf_addf(buf, \"%9.6f | \",\n> +\t\t\t    ((double)(*p_us_elapsed_relative)) / 1000000.0);\n> +\telse\n> +\t\tstrbuf_addf(buf, \"%9s | \", \" \");\n> +\t\t\n> +\tstrbuf_addf(buf, \"%-*s | \", TR2FMT_PERF_CATEGORY_WIDTH,\n> +\t\t    (category ? category : \"\"));\n> +\n> +\tif (ctx->nr_open_regions > 0) {\n> +\t\tint len_indent = TR2_INDENT_LENGTH(ctx);\n> +\t\twhile (len_indent > dots.len) {\n> +\t\t\tstrbuf_addf(buf, \"%s\", dots.buf);\n\nPlease use the much simpler are more idiomatic strbuf_addbuf()\nfunction instead.\n\n> +\t\t\tlen_indent -= dots.len;\n> +\t\t}\n> +\t\tstrbuf_addf(buf, \"%.*s\", len_indent, dots.buf);\n> +\t}\n> +}\n\n> +static void fn_version_fl(const char *file, int line)\n> +{\n> +\tconst char *event_name = \"version\";\n> +\tstruct strbuf buf_payload = STRBUF_INIT;\n> +\n> +\tstrbuf_addf(&buf_payload, \"%s\", git_version_string);\n\nstrbuf_addstr()\n\n> +\n> +\tperf_io_write_fl(file, line, event_name, NULL,\n> +\t\t\t NULL, NULL, NULL,\n> +\t\t\t &buf_payload);\n> +\tstrbuf_release(&buf_payload);\n> +}\n"},{"id":"367656","messageId":"20190125131929.GC6702@szeder.dev","threadId":"50293","inReplyTo":"1a90de9dab0dd836e54fee9e08ab9e2284e1027a.1548192131.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 01/14] trace2: Documentation/technical/api-trace2.txt","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2019-01-25T13:19:29Z","receivedAt":"2019-01-25T13:19:36Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Tue, Jan 22, 2019 at 01:22:12PM -0800, Jeff Hostetler via GitGitGadget wrote:\n> From: Jeff Hostetler <jeffhost@microsoft.com>\n> \n> Created design document for Trace2 feature.\n> \n> Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n> ---\n>  Documentation/technical/api-trace2.txt | 1158 ++++++++++++++++++++++++\n>  1 file changed, 1158 insertions(+)\n>  create mode 100644 Documentation/technical/api-trace2.txt\n> \n> diff --git a/Documentation/technical/api-trace2.txt b/Documentation/technical/api-trace2.txt\n> new file mode 100644\n> index 0000000000..501fd770f2\n> --- /dev/null\n> +++ b/Documentation/technical/api-trace2.txt\n> @@ -0,0 +1,1158 @@\n> +Trace2 API\n> +==========\n> +\n> +The Trace2 API can be used to print debug, performance, and telemetry\n> +information to stderr or a file.  The Trace2 feature is inactive unless\n> +explicitly enabled by setting one or more of the `GIT_TR2`, `GIT_TR2_PERF`,\n> +or `GIT_TR2_EVENT` environment variables.\n> +\n> +The Trace2 API is intended to replace the existing (Trace1)\n> +printf-style tracing provided by the existing `GIT_TRACE` and\n> +`GIT_TRACE_PERFORMANCE` facilities.  During initial implementation,\n> +Trace2 and Trace1 may operate in parallel.\n\nSpeaking of replacing Trace1, I couldn't find (or managed to overlook)\nthe Trace2 equivalent of the good old \"plain\"\n\n  trace_printf(\"Uh-oh!\");\n\nwhich is my go-to tool when chasing elusive heisenbugs and attempting\nto understand racy situations and flaky tests.\n\n\n> +Git Command Detail Events::\n> +\n> +\tThese are concerned with describing the specific Git command\n> +\tafter the command line, config, and environment are inspected.\n> ++\n> +----------------\n> +trace2_cmd_verb(...)\n\nWhat is a \"verb\"?\n\nIf it means \"command\", then just call it so.  Please stick to\nestablished Git terminology instead of introducing unnecessary new\nterms.\n\n> +trace2_cmd_subverb(...)\n\nWhat is a \"subverb\"?\n\nLooking at the strings passed to this function in later patches, I am\nonly sure in one thing: that it is not, in fact, a verb :)\n\nI think that in general we are better off without the word \"verb\",\ne.g.:\n\n> +\tAfter the basics are established, additional process\n> +\tinformation can be sent to Trace2 as it is discovered, such as\n> +\tthe command verb, alias expansion, interesting config\n\nThe word 'verb' doesn't add any value to the sentence, but s/ verb//\ndoes make it clearer.\n\n> +When a git process is a (direct or indirect) child of another\n> +git process, it inherits Trace2 context information.  This\n> +allows the child to print a verb hierarchy.\n\nWithout s/verb/command/ this sentence doesn't make much sense to begin\nwith.\n\n"},{"id":"367671","messageId":"20190125175342.GA72177@google.com","threadId":"50293","inReplyTo":"20190125131929.GC6702@szeder.dev","subject":"Re: [PATCH 01/14] trace2: Documentation/technical/api-trace2.txt","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2019-01-25T17:53:42Z","receivedAt":"2019-01-25T17:53:53Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2019.01.25 14:19, SZEDER Gábor wrote:\n> On Tue, Jan 22, 2019 at 01:22:12PM -0800, Jeff Hostetler via GitGitGadget wrote:\n> > From: Jeff Hostetler <jeffhost@microsoft.com>\n> > \n> > Created design document for Trace2 feature.\n> > \n> > Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n> > ---\n> >  Documentation/technical/api-trace2.txt | 1158 ++++++++++++++++++++++++\n> >  1 file changed, 1158 insertions(+)\n> >  create mode 100644 Documentation/technical/api-trace2.txt\n> > \n> > diff --git a/Documentation/technical/api-trace2.txt b/Documentation/technical/api-trace2.txt\n> > new file mode 100644\n> > index 0000000000..501fd770f2\n> > --- /dev/null\n> > +++ b/Documentation/technical/api-trace2.txt\n> > @@ -0,0 +1,1158 @@\n> > +Trace2 API\n> > +==========\n> > +\n> > +The Trace2 API can be used to print debug, performance, and telemetry\n> > +information to stderr or a file.  The Trace2 feature is inactive unless\n> > +explicitly enabled by setting one or more of the `GIT_TR2`, `GIT_TR2_PERF`,\n> > +or `GIT_TR2_EVENT` environment variables.\n> > +\n> > +The Trace2 API is intended to replace the existing (Trace1)\n> > +printf-style tracing provided by the existing `GIT_TRACE` and\n> > +`GIT_TRACE_PERFORMANCE` facilities.  During initial implementation,\n> > +Trace2 and Trace1 may operate in parallel.\n> \n> Speaking of replacing Trace1, I couldn't find (or managed to overlook)\n> the Trace2 equivalent of the good old \"plain\"\n> \n>   trace_printf(\"Uh-oh!\");\n> \n> which is my go-to tool when chasing elusive heisenbugs and attempting\n> to understand racy situations and flaky tests.\n\ntrace2_printf() is declared in trace2.h (line 387) and defined in\ntrace2.c (line 801).\n"},{"id":"367673","messageId":"xmqqr2d0ft43.fsf@gitster-ct.c.googlers.com","threadId":"50293","inReplyTo":"20190125112203.GB6702@szeder.dev","subject":"Re: [PATCH 02/14] trace2: create new combined trace facility","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-01-25T19:13:32Z","receivedAt":"2019-01-25T19:13:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"SZEDER Gábor <szeder.dev@gmail.com> writes:\n\n>> +\tif (ctx->nr_open_regions > 0) {\n>> +\t\tint len_indent = TR2_INDENT_LENGTH(ctx);\n>> +\t\twhile (len_indent > dots.len) {\n>> +\t\t\tstrbuf_addf(buf, \"%s\", dots.buf);\n>\n> Please use the much simpler are more idiomatic strbuf_addbuf()\n> function instead.\n\nAhh, contrib/coccinelle/strbuf.cocci would help ;-)\n"},{"id":"367681","messageId":"20190125200309.GB72177@google.com","threadId":"50293","inReplyTo":"pull.108.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 00/14] Trace2 tracing facility","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2019-01-25T20:03:09Z","receivedAt":"2019-01-25T20:03:22Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2019.01.22 13:22, Jeff Hostetler via GitGitGadget wrote:\n> This patch series contains a greatly refactored version of my original\n> Trace2 series [1] from August 2018.\n> \n> A new design doc in Documentation/technical/api-trace2.txt (in the first\n> commit) explains the relationship of Trace2 to the current tracing facility.\n> Calls to the current tracing facility have not been changed, rather new\n> trace2 calls have been added so that both continue to work in parallel for\n> the time being.\n> \n> [1] https://public-inbox.org/git/pull.29.git.gitgitgadget@gmail.com/\n> \n> Cc: gitster@pobox.comCc: peff@peff.netCc: jrnieder@gmail.com\n> \n> Derrick Stolee (1):\n>   pack-objects: add trace2 regions\n> \n> Jeff Hostetler (13):\n>   trace2: Documentation/technical/api-trace2.txt\n>   trace2: create new combined trace facility\n>   trace2: collect platform-specific process information\n>   trace2:data: add trace2 regions to wt-status\n>   trace2:data: add editor/pager child classification\n>   trace2:data: add trace2 sub-process classification\n>   trace2:data: add trace2 transport child classification\n>   trace2:data: add trace2 hook classification\n>   trace2:data: add trace2 instrumentation to index read/write\n>   trace2:data: add subverb to checkout command\n>   trace2:data: add subverb to reset command\n>   trace2:data: add subverb for rebase\n>   trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh\n> \n>  Documentation/technical/api-trace2.txt | 1158 ++++++++++++++++++++++++\n>  Makefile                               |   11 +\n>  builtin/am.c                           |    1 +\n>  builtin/checkout.c                     |    7 +\n>  builtin/pack-objects.c                 |   12 +-\n>  builtin/rebase.c                       |   19 +\n>  builtin/receive-pack.c                 |    4 +\n>  builtin/reset.c                        |    6 +\n>  builtin/submodule--helper.c            |   11 +-\n>  builtin/worktree.c                     |    1 +\n>  cache.h                                |    1 +\n>  common-main.c                          |   13 +-\n>  compat/mingw.c                         |   11 +-\n>  compat/mingw.h                         |    3 +-\n>  compat/win32/ancestry.c                |  102 +++\n>  config.c                               |    2 +\n>  config.mak.uname                       |    2 +\n>  connect.c                              |    3 +\n>  editor.c                               |    1 +\n>  exec-cmd.c                             |    2 +\n>  git-compat-util.h                      |    7 +\n>  git.c                                  |   65 ++\n>  pager.c                                |    1 +\n>  read-cache.c                           |   47 +-\n>  remote-curl.c                          |    7 +\n>  repository.c                           |    2 +\n>  repository.h                           |    3 +\n>  run-command.c                          |   63 +-\n>  run-command.h                          |   17 +-\n>  sequencer.c                            |    2 +\n>  sh-i18n--envsubst.c                    |    3 +\n>  sub-process.c                          |    1 +\n>  submodule.c                            |   11 +-\n>  t/helper/test-parse-options.c          |    3 +\n>  t/helper/test-tool.c                   |    4 +\n>  t/helper/test-tool.h                   |    1 +\n>  t/helper/test-trace2.c                 |  273 ++++++\n>  t/t0001-init.sh                        |    1 +\n>  t/t0210-trace2-normal.sh               |  135 +++\n>  t/t0210/scrub_normal.perl              |   48 +\n>  t/t0211-trace2-perf.sh                 |  153 ++++\n>  t/t0211/scrub_perf.perl                |   76 ++\n>  t/t0212-trace2-event.sh                |  237 +++++\n>  t/t0212/parse_events.perl              |  251 +++++\n>  trace2.c                               |  809 +++++++++++++++++\n>  trace2.h                               |  403 +++++++++\n>  trace2/tr2_cfg.c                       |   92 ++\n>  trace2/tr2_cfg.h                       |   19 +\n>  trace2/tr2_dst.c                       |   90 ++\n>  trace2/tr2_dst.h                       |   34 +\n>  trace2/tr2_sid.c                       |   67 ++\n>  trace2/tr2_sid.h                       |   18 +\n>  trace2/tr2_tbuf.c                      |   32 +\n>  trace2/tr2_tbuf.h                      |   23 +\n>  trace2/tr2_tgt.h                       |  126 +++\n>  trace2/tr2_tgt_event.c                 |  606 +++++++++++++\n>  trace2/tr2_tgt_normal.c                |  331 +++++++\n>  trace2/tr2_tgt_perf.c                  |  573 ++++++++++++\n>  trace2/tr2_tls.c                       |  164 ++++\n>  trace2/tr2_tls.h                       |   95 ++\n>  trace2/tr2_verb.c                      |   30 +\n>  trace2/tr2_verb.h                      |   24 +\n>  transport-helper.c                     |    2 +\n>  transport.c                            |    1 +\n>  usage.c                                |   31 +\n>  wt-status.c                            |   23 +-\n>  66 files changed, 6353 insertions(+), 21 deletions(-)\n>  create mode 100644 Documentation/technical/api-trace2.txt\n>  create mode 100644 compat/win32/ancestry.c\n>  create mode 100644 t/helper/test-trace2.c\n>  create mode 100755 t/t0210-trace2-normal.sh\n>  create mode 100644 t/t0210/scrub_normal.perl\n>  create mode 100755 t/t0211-trace2-perf.sh\n>  create mode 100644 t/t0211/scrub_perf.perl\n>  create mode 100755 t/t0212-trace2-event.sh\n>  create mode 100644 t/t0212/parse_events.perl\n>  create mode 100644 trace2.c\n>  create mode 100644 trace2.h\n>  create mode 100644 trace2/tr2_cfg.c\n>  create mode 100644 trace2/tr2_cfg.h\n>  create mode 100644 trace2/tr2_dst.c\n>  create mode 100644 trace2/tr2_dst.h\n>  create mode 100644 trace2/tr2_sid.c\n>  create mode 100644 trace2/tr2_sid.h\n>  create mode 100644 trace2/tr2_tbuf.c\n>  create mode 100644 trace2/tr2_tbuf.h\n>  create mode 100644 trace2/tr2_tgt.h\n>  create mode 100644 trace2/tr2_tgt_event.c\n>  create mode 100644 trace2/tr2_tgt_normal.c\n>  create mode 100644 trace2/tr2_tgt_perf.c\n>  create mode 100644 trace2/tr2_tls.c\n>  create mode 100644 trace2/tr2_tls.h\n>  create mode 100644 trace2/tr2_verb.c\n>  create mode 100644 trace2/tr2_verb.h\n> \n> \n> base-commit: 77556354bb7ac50450e3b28999e3576969869068\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-108%2Fjeffhostetler%2Fcore-trace2-2019-v0-v1\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-108/jeffhostetler/core-trace2-2019-v0-v1\n> Pull-Request: https://github.com/gitgitgadget/git/pull/108\n> -- \n> gitgitgadget\n\nSeveral patches in this series have many style diffs as reported by\nclang-format. Not all the diffs actually improve readability, but many\ndo. If you have clang-format installed, you can run:\n\ngit clang-format --style file --diff --extensions c,h ${commit}^ ${commit}\n\nfor each commit in the series to see what it thinks needs to be changed.\n\n\nOther than that, I don't have any comments apart from what the other\nreviewers have already mentioned.\n\nThanks for the series!\n"},{"id":"367683","messageId":"0ed19c21-4844-79b3-3428-54b2ecb56b0f@jeffhostetler.com","threadId":"50293","inReplyTo":"xmqqbm47ayhv.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH 01/14] trace2: Documentation/technical/api-trace2.txt","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2019-01-25T21:13:58Z","receivedAt":"2019-01-25T21:14:04Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 1/23/2019 3:51 PM, Junio C Hamano wrote:\n> \"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> +These high-level events are written to one or more Trace2 Targets\n>> +in a target-specific format.  Each Trace2 Target defines a different\n>> +purpose-specific view onto the event data stream.  In this mannor,\n> \n> \"In this manner\"\n> \n>> +a single set of Trace2 API event calls in the Git source can drive\n>> +different types of analysis.\n>> +\n>> ...\n>> +$ cat ~/log.perf\n>> +12:28:42.620675 common-main.c:38                  | d0 | main                     | version      |     |           |           |            | 2.20.1.155.g426c96fcdb\n>> +12:28:42.621001 common-main.c:39                  | d0 | main                     | start        |     |           |           |            | git version\n>> +12:28:42.621111 git.c:432                         | d0 | main                     | cmd_verb     |     |           |           |            | version (version)\n>> +12:28:42.621225 git.c:662                         | d0 | main                     | exit         |     |  0.001227 |           |            | code:0\n>> +12:28:42.621259 trace2/tr2_tgt_perf.c:211         | d0 | main                     | atexit       |     |  0.001265 |           |            | code:0\n>> +------------\n>> ...\n>> +\n>> +trace2_def_param(...)\n> \n> Not limited to this single one, but please either\n> \n>   - omit \"...\" in parens, unless all of these functions take varargs\n>     of unspecified type (which I do not think is the case), or\n> \n>   - write a proper prototype for these functions, explain what the\n>     function itself and what the parameters are for.\n> \n> I'll complain about lack of info around here later X-<.\n\nI documented the prototypes in trace2.h and was hoping to avoid\nduplicating all that text here in this document.  The list of\nfunctions here in this document was more of an overview of the\ngroups of concepts covered.  I'll revisit this.\n\n\n> \n>> +trace2_def_repo(...)\n> \n>> +----------------\n>> +\n>> +Git Child Process Events::\n>> +\n>> +\tThese are concerned with the various spawned child processes,\n>> +\tincluding sub-git processes and hooks,\n>> ++\n>> +----------------\n>> +trace2_child_start(...)\n>> +trace2_child_exit(...)\n>> +\n>> +trace2_exec(...)\n>> +trace2_exec_result(...)\n>> +----------------\n>> +\n>> +Git Thread Events::\n>> +\n>> +\tThese are concerned with Git thread usage.\n>> ++\n>> +----------------\n>> +trace2_thread_start(...)\n>> +trace2_thread_exit(...)\n>> +----------------\n> \n> Lack of _wait()/_join() feels a bit curious, but _exit() from what\n> is being waited would suffice as a substitute.\n\nThe 2 trace2_thread_ functions have to be run by the thread-proc itself\nrather than by the code calling the pthread functions.  So to me it made\nmore sense inside the thread-proc for them to be named _start and _exit.\nThis gives us an event with the elapsed time of the thread in isolation\nusing TLS data within each thread.\n\nIf I understand your question about _wait/_join, adding trace2 calls\nnear the pthread_create and pthread_join, would be a bit of a mess\nbecause of the usual create loop and then later the join loop.  And\nthe exit times would be less accurate, since we typically _join them\nin array order rather in the order they finish.\n\nAnd having the trace2_thread_ calls in the pthread caller doesn't let\nme access the thread's private TLS data.\n\nWrapping a region (like I show later in the preload_index() example)\naround both of the pthread loops gives us the overall time for the\nthreading and insight into the thread overhead.  (One could even\nhave a region around the pthread_create loop and another around the\npthread_join loop, but that might not be worth the trouble.\n\n> \n>> +Initialization::\n>> +\n>> +\tInitialization happens in `main()`.  The initialization code\n>> +\tdetermines which, if any, Trace2 Targets should be enabled and\n>> +\temits the `version`, `start`, and `exit` events.  It causes an\n>> +\t`atexit` function and `signal` handler to be registered that\n>> +\twill emit `atexit` and `signal` events.\n>> ++\n>> +----------------\n>> +int main(int argc, const char **argv)\n>> +{\n>> +\tint exit_code;\n>> +\n>> +\ttrace2_initialize();\n>> +\ttrace2_cmd_start(argv);\n> \n> Nobody other than trace2 integration would make a call to this\n> helper, so it may not matter, but sending av alone without ac, even\n> though ac is almost always redundant, feels somewhat unexpected.\n\nAgreed.  There were other places that took an argv that didn't have\nan argc on hand in the calling code.  So rather than fake up one for\nthem, I just omitted it from all the calls in my API.\n\n\n> \n>> +Command Details::\n>> +\n>> +\tAfter the basics are established, additional process\n>> +\tinformation can be sent to Trace2 as it is discovered, such as\n>> +\tthe command verb, alias expansion, interesting config\n>> +\tsettings, the repository worktree, error messages, and etc.\n>> ++\n>> +----------------\n>> +int cmd_checkout(int argc, const char **argv)\n>> +{\n>> +\t// emit a single \"def_param\" event\n>> +\ttrace2_def_param(\"core.editor\", \"emacs\");\n> \n> Without knowing what \"def_param event\" is, this example is hard to\n> fathom.  At this point in the doc, the reader does not even know\n> what \"def\" stands for.  Is this call to define a param called\n> core.editor?  Is it reporting that the default value for core.editor\n> is emacs?\n> \n>> +\t// emit def_params for any config setting matching a pattern\n>> +\t// in GIT_TR2_CONFIG_PARAMS.\n>> +\ttrace2_cmd_list_config();\n> \n> As the reader does not know what def_param is, this is also hard to\n> follow.\n\nI'll rewrite that section.  The idea is to define a parameter that\na perf-tester might consider important.  For example, \"core.abbrev\"\nused to cause massive perf problems on our mega repo where it might\ntake minutes to compute 7 or 8 digit abbreviations for a log or\nbranch command (completely dominating the time to actually compute\nthe log or branch list).\n\n\n> \n>> +\ttrace2_cmd_verb(\"checkout\");\n>> +\ttrace2_cmd_subverb(\"branch\");\n> \n> These are guessable.  It probably reports what the codepath is\n> doing.\n\nThese are to help post-processing.  The git command line is parsed\nin 2 or 3 different phases and it takes quite a bit of work to get\nto cmd_*() function (such as skipping over the -c, -C, --exec-path,\nand other such args, and handling the commands where there isn't\na cmd_*() function (such as \"git --exec-path\")).  Then within the\ncmd_*() function, a lot of work to see which variant of the command\nis (such as is this checkout checking out a branch or a single\nfile).  And there are short and long forms of many arguments. And then \nthere is the alias expansion which wants to rewrite the command line\n(possibly recursively).  And then there is the code to try to run the\nnon-builtin dashed form.\n\nFor post-processing, you want to be able to do something like this:\n\n\tselect elapsed_time where verb='checkout' and subverb='branch'\n\tselect elapsed_time where verb='checkout' and subverb='path'\n\nand report averages over those 2 sets independently without having\nto recreate all that argv parsing inside the database query (or perl\nscript on the trace log).\n\n> \n>> +\ttrace2_def_repo(the_repository);\n> \n> Again, this is not easy to guess.  Is it reporting what the default\n> repository is?\n\nI'll reword this.\n\n\n> \n>> +\tif (do_something(...))\n>> +\t    trace2_cmd_error(\"Path '%s': cannot do something\", path);\n> \n> This is guessable, which is good.\n> \n>> +void run_child(...)\n>> +{\n>> +\tint child_exit_code;\n>> +\tstruct child_process cmd = CHILD_PROCESS_INIT;\n>> +\t...\n>> +\n>> +\ttrace2_child_start(&cmd);\n>> +\tchild_exit_code = ...();\n>> +\ttrace2_child_exit(&cmd, child_exit_code);\n> \n> Even though child_exit() has not been explained just like\n> def_param(), this is far more guessable.  I wonder why it is.  The\n> name of the variable passed to it in the example certainly helps, as\n> it is totally unclear where the string constant \"emacs\" in the\n> earlier example came from (e.g. is it hardcoded in the program?  is\n> it merely illustrating \"here is how you report the value you have\n> decided to use for 'core.editor' variable\"?).\n\nyes, some of these are synthetic uses of the API to illustrate\nthe trace2 concepts.  I just picked 'core.editor' at random.  I'll\nrevisit the examples.\n\n\n> \n>> ...\n>> +$ cat ~/log.perf\n>> +d0 | main                     | version      |     |           |           |            | 2.20.1.160.g5676107ecd.dirty\n>> +d0 | main                     | start        |     |           |           |            | git status\n>> +d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n>> +d0 | main                     | cmd_verb     |     |           |           |            | status (status)\n>> +...\n>> +d0 | main                     | region_enter | r1  |  0.010988 |           | status     | label:worktrees\n> \n> It is hard to guess what \"d0\" and \"r1\" stand for here.\n> \n> In an earlier example we also saw an unexplained \"d0\" there, which I\n> think was OK because the illustration was merely to give the \"feel\"\n> of the format up there.  But now we are giving a bit more detailed\n> explanation, we probably would want to at least briefly mention what\n> each column means.\n\nyes, the example at the top was just to preview the formats.\nI'll add more info here.\n\n> I'd stop here for now, as I am more interested in reading the code\n> ;-)\n> \n\nThanks\nJeff\n"},{"id":"367833","messageId":"e1bd52e3-1ac5-b08c-d1f6-a7fa63ebf0cf@jeffhostetler.com","threadId":"50293","inReplyTo":"pull.108.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 00/14] Trace2 tracing facility","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2019-01-28T16:15:29Z","receivedAt":"2019-01-28T16:15:33Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 1/22/2019 4:22 PM, Jeff Hostetler via GitGitGadget wrote:\n> This patch series contains a greatly refactored version of my original\n> Trace2 series [1] from August 2018.\n\n\nMy Trace2 series \"jh/trace2\" has a bad interaction with \"js/vsts-ci\"\ncausing some unit tests to fail in \"pu\".  I'll post a new version\nshortly.\n\nJeff\n\n"},{"id":"367847","messageId":"xmqqpnsgbqqh.fsf@gitster-ct.c.googlers.com","threadId":"50293","inReplyTo":"e1bd52e3-1ac5-b08c-d1f6-a7fa63ebf0cf@jeffhostetler.com","subject":"Re: [PATCH 00/14] Trace2 tracing facility","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-01-28T18:07:34Z","receivedAt":"2019-01-28T18:07:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff Hostetler <git@jeffhostetler.com> writes:\n\n> On 1/22/2019 4:22 PM, Jeff Hostetler via GitGitGadget wrote:\n>> This patch series contains a greatly refactored version of my original\n>> Trace2 series [1] from August 2018.\n>\n>\n> My Trace2 series \"jh/trace2\" has a bad interaction with \"js/vsts-ci\"\n> causing some unit tests to fail in \"pu\".  I'll post a new version\n> shortly.\n\nThanks for a heads-up.\n"},{"id":"367868","messageId":"pull.108.v2.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.git.gitgitgadget@gmail.com","subject":"[PATCH v2 00/14] Trace2 tracing facility","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-28T21:47:40Z","receivedAt":"2019-01-28T21:47:46Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"V2 addresses: [] \"jh/trace2\" bad interaction with \"js/vsts-ci\" in \"pu\". []\ncoccinelle warnings in trace2/tr2_tgt_perf.c reported during CI testing.\n\n\n----------------------------------------------------------------------------\n\nThis patch series contains a greatly refactored version of my original\nTrace2 series [1] from August 2018.\n\nA new design doc in Documentation/technical/api-trace2.txt (in the first\ncommit) explains the relationship of Trace2 to the current tracing facility.\nCalls to the current tracing facility have not been changed, rather new\ntrace2 calls have been added so that both continue to work in parallel for\nthe time being.\n\n[1] https://public-inbox.org/git/pull.29.git.gitgitgadget@gmail.com/\n\nCc: gitster@pobox.comCc: peff@peff.netCc: jrnieder@gmail.com\n\nDerrick Stolee (1):\n  pack-objects: add trace2 regions\n\nJeff Hostetler (13):\n  trace2: Documentation/technical/api-trace2.txt\n  trace2: create new combined trace facility\n  trace2: collect platform-specific process information\n  trace2:data: add trace2 regions to wt-status\n  trace2:data: add editor/pager child classification\n  trace2:data: add trace2 sub-process classification\n  trace2:data: add trace2 transport child classification\n  trace2:data: add trace2 hook classification\n  trace2:data: add trace2 instrumentation to index read/write\n  trace2:data: add subverb to checkout command\n  trace2:data: add subverb to reset command\n  trace2:data: add subverb for rebase\n  trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh\n\n Documentation/technical/api-trace2.txt | 1158 ++++++++++++++++++++++++\n Makefile                               |   11 +\n builtin/am.c                           |    1 +\n builtin/checkout.c                     |    7 +\n builtin/pack-objects.c                 |   12 +-\n builtin/rebase.c                       |   19 +\n builtin/receive-pack.c                 |    4 +\n builtin/reset.c                        |    6 +\n builtin/submodule--helper.c            |   11 +-\n builtin/worktree.c                     |    1 +\n cache.h                                |    1 +\n common-main.c                          |   13 +-\n compat/mingw.c                         |   11 +-\n compat/mingw.h                         |    3 +-\n compat/win32/ancestry.c                |  102 +++\n config.c                               |    2 +\n config.mak.uname                       |    2 +\n connect.c                              |    3 +\n editor.c                               |    1 +\n exec-cmd.c                             |    2 +\n git-compat-util.h                      |    7 +\n git.c                                  |   65 ++\n pager.c                                |    1 +\n read-cache.c                           |   47 +-\n remote-curl.c                          |    7 +\n repository.c                           |    2 +\n repository.h                           |    3 +\n run-command.c                          |   63 +-\n run-command.h                          |   17 +-\n sequencer.c                            |    2 +\n sh-i18n--envsubst.c                    |    3 +\n sub-process.c                          |    1 +\n submodule.c                            |   11 +-\n t/helper/test-parse-options.c          |    3 +\n t/helper/test-tool.c                   |    4 +\n t/helper/test-tool.h                   |    1 +\n t/helper/test-trace2.c                 |  273 ++++++\n t/t0001-init.sh                        |    1 +\n t/t0210-trace2-normal.sh               |  147 +++\n t/t0210/scrub_normal.perl              |   48 +\n t/t0211-trace2-perf.sh                 |  163 ++++\n t/t0211/scrub_perf.perl                |   76 ++\n t/t0212-trace2-event.sh                |  246 +++++\n t/t0212/parse_events.perl              |  251 +++++\n trace2.c                               |  809 +++++++++++++++++\n trace2.h                               |  403 +++++++++\n trace2/tr2_cfg.c                       |   92 ++\n trace2/tr2_cfg.h                       |   19 +\n trace2/tr2_dst.c                       |   90 ++\n trace2/tr2_dst.h                       |   34 +\n trace2/tr2_sid.c                       |   67 ++\n trace2/tr2_sid.h                       |   18 +\n trace2/tr2_tbuf.c                      |   32 +\n trace2/tr2_tbuf.h                      |   23 +\n trace2/tr2_tgt.h                       |  126 +++\n trace2/tr2_tgt_event.c                 |  606 +++++++++++++\n trace2/tr2_tgt_normal.c                |  331 +++++++\n trace2/tr2_tgt_perf.c                  |  573 ++++++++++++\n trace2/tr2_tls.c                       |  164 ++++\n trace2/tr2_tls.h                       |   95 ++\n trace2/tr2_verb.c                      |   30 +\n trace2/tr2_verb.h                      |   24 +\n transport-helper.c                     |    2 +\n transport.c                            |    1 +\n usage.c                                |   31 +\n wt-status.c                            |   23 +-\n 66 files changed, 6384 insertions(+), 21 deletions(-)\n create mode 100644 Documentation/technical/api-trace2.txt\n create mode 100644 compat/win32/ancestry.c\n create mode 100644 t/helper/test-trace2.c\n create mode 100755 t/t0210-trace2-normal.sh\n create mode 100644 t/t0210/scrub_normal.perl\n create mode 100755 t/t0211-trace2-perf.sh\n create mode 100644 t/t0211/scrub_perf.perl\n create mode 100755 t/t0212-trace2-event.sh\n create mode 100644 t/t0212/parse_events.perl\n create mode 100644 trace2.c\n create mode 100644 trace2.h\n create mode 100644 trace2/tr2_cfg.c\n create mode 100644 trace2/tr2_cfg.h\n create mode 100644 trace2/tr2_dst.c\n create mode 100644 trace2/tr2_dst.h\n create mode 100644 trace2/tr2_sid.c\n create mode 100644 trace2/tr2_sid.h\n create mode 100644 trace2/tr2_tbuf.c\n create mode 100644 trace2/tr2_tbuf.h\n create mode 100644 trace2/tr2_tgt.h\n create mode 100644 trace2/tr2_tgt_event.c\n create mode 100644 trace2/tr2_tgt_normal.c\n create mode 100644 trace2/tr2_tgt_perf.c\n create mode 100644 trace2/tr2_tls.c\n create mode 100644 trace2/tr2_tls.h\n create mode 100644 trace2/tr2_verb.c\n create mode 100644 trace2/tr2_verb.h\n\n\nbase-commit: 77556354bb7ac50450e3b28999e3576969869068\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-108%2Fjeffhostetler%2Fcore-trace2-2019-v0-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-108/jeffhostetler/core-trace2-2019-v0-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/108\n\nRange-diff vs v1:\n\n  1:  1a90de9dab =  1:  1a90de9dab trace2: Documentation/technical/api-trace2.txt\n  2:  4aaf4834bf !  2:  ea39b76d31 trace2: create new combined trace facility\n     @@ -3527,7 +3527,7 @@\n      +\tif (ctx->nr_open_regions > 0) {\n      +\t\tint len_indent = TR2_INDENT_LENGTH(ctx);\n      +\t\twhile (len_indent > dots.len) {\n     -+\t\t\tstrbuf_addf(buf, \"%s\", dots.buf);\n     ++\t\t\tstrbuf_addbuf(buf, &dots);\n      +\t\t\tlen_indent -= dots.len;\n      +\t\t}\n      +\t\tstrbuf_addf(buf, \"%.*s\", len_indent, dots.buf);\n     @@ -3559,7 +3559,7 @@\n      +\tconst char *event_name = \"version\";\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n      +\n     -+\tstrbuf_addf(&buf_payload, \"%s\", git_version_string);\n     ++\tstrbuf_addstr(&buf_payload, git_version_string);\n      +\n      +\tperf_io_write_fl(file, line, event_name, NULL,\n      +\t\t\t NULL, NULL, NULL,\n  3:  5baee8295e =  3:  5ac061e14a trace2: collect platform-specific process information\n  4:  978b9ebf0d =  4:  f9d689a54b trace2:data: add trace2 regions to wt-status\n  5:  3941647097 =  5:  6be101d520 trace2:data: add editor/pager child classification\n  6:  a20cf139ee =  6:  b590f19100 trace2:data: add trace2 sub-process classification\n  7:  49b6b9bd24 =  7:  68192b8dfb trace2:data: add trace2 transport child classification\n  8:  0f8f366bbe =  8:  b373ab640b trace2:data: add trace2 hook classification\n  9:  705c2ece6d =  9:  548ea52742 trace2:data: add trace2 instrumentation to index read/write\n 10:  1aa79cb126 = 10:  3458917811 pack-objects: add trace2 regions\n 11:  8446f69b57 = 11:  86feec03e2 trace2:data: add subverb to checkout command\n 12:  7eea9027f9 = 12:  9abbdf9ccd trace2:data: add subverb to reset command\n 13:  618e5ccb0b = 13:  06ccce9632 trace2:data: add subverb for rebase\n 14:  33a1ca7222 ! 14:  851aa8f34d trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh\n     @@ -356,9 +356,11 @@\n      +# Trace2 events will/can be written to each active target (subject\n      +# to whatever filtering that target decides to do).\n      +# This script tests the normal target in isolation.\n     ++#\n     ++# Defer setting GIT_TR2 until the actual command line we want to test\n     ++# because hidden git and test-tool commands run by the test harness\n     ++# can contaminate our output.\n      +\n     -+# Enable \"normal\" trace2 target.\n     -+GIT_TR2=\"$(pwd)/trace.normal\" && export GIT_TR2\n      +# Enable \"brief\" feature which turns off \"<clock> <file>:<line> \" prefix.\n      +GIT_TR2_BRIEF=1 && export GIT_TR2_BRIEF\n      +\n     @@ -382,7 +384,9 @@\n      +\n      +test_expect_success 'normal stream, return code 0' '\n      +\ttest_when_finished \"rm trace.normal actual expect\" &&\n     ++\tGIT_TR2=\"$(pwd)/trace.normal\" && export GIT_TR2 &&\n      +\t$TT trace2 001return 0 &&\n     ++\tunset GIT_TR2 &&\n      +\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n      +\tcat >expect <<-EOF &&\n      +\t\tversion $V\n     @@ -396,7 +400,9 @@\n      +\n      +test_expect_success 'normal stream, return code 1' '\n      +\ttest_when_finished \"rm trace.normal actual expect\" &&\n     ++\tGIT_TR2=\"$(pwd)/trace.normal\" && export GIT_TR2 &&\n      +\ttest_must_fail $TT trace2 001return 1 &&\n     ++\tunset GIT_TR2 &&\n      +\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n      +\tcat >expect <<-EOF &&\n      +\t\tversion $V\n     @@ -414,7 +420,9 @@\n      +\n      +test_expect_success 'normal stream, exit code 0' '\n      +\ttest_when_finished \"rm trace.normal actual expect\" &&\n     ++\tGIT_TR2=\"$(pwd)/trace.normal\" && export GIT_TR2 &&\n      +\t$TT trace2 002exit 0 &&\n     ++\tunset GIT_TR2 &&\n      +\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n      +\tcat >expect <<-EOF &&\n      +\t\tversion $V\n     @@ -428,7 +436,9 @@\n      +\n      +test_expect_success 'normal stream, exit code 1' '\n      +\ttest_when_finished \"rm trace.normal actual expect\" &&\n     ++\tGIT_TR2=\"$(pwd)/trace.normal\" && export GIT_TR2 &&\n      +\ttest_must_fail $TT trace2 002exit 1 &&\n     ++\tunset GIT_TR2 &&\n      +\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n      +\tcat >expect <<-EOF &&\n      +\t\tversion $V\n     @@ -446,7 +456,9 @@\n      +\n      +test_expect_success 'normal stream, error event' '\n      +\ttest_when_finished \"rm trace.normal actual expect\" &&\n     ++\tGIT_TR2=\"$(pwd)/trace.normal\" && export GIT_TR2 &&\n      +\t$TT trace2 003error \"hello world\" \"this is a test\" &&\n     ++\tunset GIT_TR2 &&\n      +\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n      +\tcat >expect <<-EOF &&\n      +\t\tversion $V\n     @@ -551,9 +563,11 @@\n      +# Trace2 events will/can be written to each active target (subject\n      +# to whatever filtering that target decides to do).\n      +# Test each target independently.\n     ++#\n     ++# Defer setting GIT_TR2_PERF until the actual command we want to\n     ++# test because hidden git and test-tool commands in the test\n     ++# harness can contaminate our output.\n      +\n     -+# Enable \"perf\" trace2 target.\n     -+GIT_TR2_PERF=\"$(pwd)/trace.perf\" && export GIT_TR2_PERF\n      +# Enable \"brief\" feature which turns off the prefix:\n      +#     \"<clock> <file>:<line> | <nr_parents> | \"\n      +GIT_TR2_PERF_BRIEF=1 && export GIT_TR2_PERF_BRIEF\n     @@ -569,7 +583,9 @@\n      +\n      +test_expect_success 'perf stream, return code 0' '\n      +\ttest_when_finished \"rm trace.perf actual expect\" &&\n     ++\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" && export GIT_TR2_PERF &&\n      +\t$TT trace2 001return 0 &&\n     ++\tunset GIT_TR2_PERF &&\n      +\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n      +\tcat >expect <<-EOF &&\n      +\t\td0|main|version|||||$V\n     @@ -583,7 +599,9 @@\n      +\n      +test_expect_success 'perf stream, return code 1' '\n      +\ttest_when_finished \"rm trace.perf actual expect\" &&\n     ++\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" && export GIT_TR2_PERF &&\n      +\ttest_must_fail $TT trace2 001return 1 &&\n     ++\tunset GIT_TR2_PERF &&\n      +\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n      +\tcat >expect <<-EOF &&\n      +\t\td0|main|version|||||$V\n     @@ -601,7 +619,9 @@\n      +\n      +test_expect_success 'perf stream, error event' '\n      +\ttest_when_finished \"rm trace.perf actual expect\" &&\n     ++\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" && export GIT_TR2_PERF &&\n      +\t$TT trace2 003error \"hello world\" \"this is a test\" &&\n     ++\tunset GIT_TR2_PERF &&\n      +\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n      +\tcat >expect <<-EOF &&\n      +\t\td0|main|version|||||$V\n     @@ -647,7 +667,9 @@\n      +\n      +test_expect_success 'perf stream, child processes' '\n      +\ttest_when_finished \"rm trace.perf actual expect\" &&\n     ++\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" && export GIT_TR2_PERF &&\n      +\t$TT trace2 004child $TT trace2 004child $TT trace2 001return 0 &&\n     ++\tunset GIT_TR2_PERF &&\n      +\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n      +\tcat >expect <<-EOF &&\n      +\t\td0|main|version|||||$V\n     @@ -792,9 +814,10 @@\n      +# Trace2 events will/can be written to each active target (subject\n      +# to whatever filtering that target decides to do).\n      +# Test each target independently.\n     -+\n     -+# Enable \"event\" trace2 target.\n     -+GIT_TR2_EVENT=\"$(pwd)/trace.event\" && export GIT_TR2_EVENT\n     ++#\n     ++# Defer setting GIT_TR2_PERF until the actual command we want to\n     ++# test because hidden git and test-tool commands in the test\n     ++# harness can contaminate our output.\n      +\n      +# We don't bother repeating the 001return and 002exit tests, since they\n      +# have coverage in the normal and perf targets.\n     @@ -805,7 +828,9 @@\n      +\n      +test_expect_success 'event stream, error event' '\n      +\ttest_when_finished \"rm trace.event actual expect\" &&\n     ++\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" && export GIT_TR2_EVENT &&\n      +\t$TT trace2 003error \"hello world\" \"this is a test\" &&\n     ++\tunset GIT_TR2_EVENT &&\n      +\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n      +\tsed -e \"s/^|//\" >expect <<-EOF &&\n      +\t|VAR1 = {\n     @@ -842,7 +867,9 @@\n      +\n      +test_expect_success 'event stream, return code 0' '\n      +#\ttest_when_finished \"rm trace.event actual expect\" &&\n     ++\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" && export GIT_TR2_EVENT &&\n      +\t$TT trace2 004child $TT trace2 004child $TT trace2 001return 0 &&\n     ++\tunset GIT_TR2_EVENT &&\n      +\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n      +\tsed -e \"s/^|//\" >expect <<-EOF &&\n      +\t|VAR1 = {\n     @@ -933,7 +960,9 @@\n      +\tgit config --local t0212.def \"hello world\" &&\n      +\t# delete events generated by the above config commands\n      +\trm trace.event &&\n     ++\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" && export GIT_TR2_EVENT &&\n      +\tGIT_TR2_CONFIG_PARAMS=\"t0212.*\" $TT trace2 001return 0 &&\n     ++\tunset GIT_TR2_EVENT &&\n      +\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n      +\tsed -e \"s/^|//\" >expect <<-EOF &&\n      +\t|VAR1 = {\n     @@ -966,7 +995,9 @@\n      +\n      +test_expect_success 'basic trace2_data' '\n      +\ttest_when_finished \"rm trace.event actual expect\" &&\n     ++\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" && export GIT_TR2_EVENT &&\n      +\t$TT trace2 006data test_category k1 v1 test_category k2 v2 &&\n     ++\tunset GIT_TR2_EVENT &&\n      +\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n      +\tsed -e \"s/^|//\" >expect <<-EOF &&\n      +\t|VAR1 = {\n\n-- \ngitgitgadget\n"},{"id":"367869","messageId":"5ac061e14a3a0e8cdd81fd0d56c3c70e6a22d171.1548712060.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 03/14] trace2: collect platform-specific process information","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-28T21:47:43Z","receivedAt":"2019-01-28T21:47:48Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd optional platform-specific code to log information about\nthe current process.\n\nOn Windows, this includes whether git.exe is running under a\ndebugger and information about the ancestors of the process.\n\nThe purpose of this information is to help indicate if the\nprocess was launched interactively or in the background by\nan IDE, for example.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n common-main.c           |   1 +\n compat/win32/ancestry.c | 102 ++++++++++++++++++++++++++++++++++++++++\n config.mak.uname        |   2 +\n trace2.h                |  13 +++++\n 4 files changed, 118 insertions(+)\n create mode 100644 compat/win32/ancestry.c\n\ndiff --git a/common-main.c b/common-main.c\nindex 6dbdc4adf2..d484aec209 100644\n--- a/common-main.c\n+++ b/common-main.c\n@@ -37,6 +37,7 @@ int main(int argc, const char **argv)\n \n \ttrace2_initialize();\n \ttrace2_cmd_start(argv);\n+\ttrace2_collect_process_info();\n \n \tgit_resolve_executable_dir(argv[0]);\n \ndiff --git a/compat/win32/ancestry.c b/compat/win32/ancestry.c\nnew file mode 100644\nindex 0000000000..629d8214b9\n--- /dev/null\n+++ b/compat/win32/ancestry.c\n@@ -0,0 +1,102 @@\n+#include \"../../cache.h\"\n+#include \"../../json-writer.h\"\n+#include <Psapi.h>\n+#include <tlHelp32.h>\n+\n+/*\n+ * Find the process data for the given PID in the given snapshot\n+ * and update the PROCESSENTRY32 data.\n+ */\n+static int find_pid(DWORD pid, HANDLE hSnapshot, PROCESSENTRY32 *pe32)\n+{\n+\tpe32->dwSize = sizeof(PROCESSENTRY32);\n+\n+\tif (Process32First(hSnapshot, pe32)) {\n+\t\tdo {\n+\t\t\tif (pe32->th32ProcessID == pid)\n+\t\t\t\treturn 1;\n+\t\t} while (Process32Next(hSnapshot, pe32));\n+\t}\n+\treturn 0;\n+}\n+\n+/*\n+ * Accumulate JSON array:\n+ *     [\n+ *         exe-name-parent,\n+ *         exe-name-grand-parent,\n+ *         ...\n+ *     ]\n+ *\n+ * Note: we only report the filename of the process executable; the\n+ *       only way to get its full pathname is to use OpenProcess()\n+ *       and GetModuleFileNameEx() or QueryfullProcessImageName()\n+ *       and that seems rather expensive (on top of the cost of\n+ *       getting the snapshot).\n+ */\n+static void get_processes(struct json_writer *jw, HANDLE hSnapshot)\n+{\n+\tPROCESSENTRY32 pe32;\n+\tDWORD pid;\n+\n+\tpid = GetCurrentProcessId();\n+\n+\t/* We only want parent processes, so skip self. */\n+\tif (!find_pid(pid, hSnapshot, &pe32))\n+\t\treturn;\n+\tpid = pe32.th32ParentProcessID;\n+\n+\twhile (find_pid(pid, hSnapshot, &pe32)) {\n+\n+\t\tjw_array_string(jw, pe32.szExeFile);\n+\n+\t\tpid = pe32.th32ParentProcessID;\n+\t}\n+}\n+\n+/*\n+ * Emit JSON data for the current and parent processes.  Individual\n+ * trace2 targets can decide how to actually print it.\n+ */\n+static void get_ancestry(void)\n+{\n+\tHANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);\n+\n+\tif (hSnapshot != INVALID_HANDLE_VALUE) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\t\tjw_array_begin(&jw, 0);\n+\t\tget_processes(&jw, hSnapshot);\n+\t\tjw_end(&jw);\n+\n+\t\ttrace2_data_json(\"process\", the_repository,\n+\t\t\t\t \"windows/ancestry\", &jw);\n+\n+\t\tjw_release(&jw);\n+\t\tCloseHandle(hSnapshot);\n+\t}\n+}\n+\n+/*\n+ * Is a debugger attached to the current process?\n+ *\n+ * This will catch debug runs (where the debugger started the process).\n+ * This is the normal case.  Since this code is called during our startup,\n+ * it will not report instances where a debugger is attached dynamically\n+ * to a running git process, but that is relatively rare.\n+ */\n+static void get_is_being_debugged(void)\n+{\n+\tif (IsDebuggerPresent())\n+\t\ttrace2_data_intmax(\"process\", the_repository,\n+\t\t\t   \"windows/debugger_present\", 1);\n+}\n+\n+void trace2_collect_process_info(void)\n+{\n+\tif (!trace2_is_enabled())\n+\t\treturn;\n+\n+\tget_is_being_debugged();\n+\tget_ancestry();\n+}\ndiff --git a/config.mak.uname b/config.mak.uname\nindex e391431041..13d7c5410f 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -391,6 +391,7 @@ ifeq ($(uname_S),Windows)\n \tBASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE\n \tCOMPAT_OBJS = compat/msvc.o compat/winansi.o \\\n \t\tcompat/win32/pthread.o compat/win32/syslog.o \\\n+\t\tcompat/win32/ancestry.o \\\n \t\tcompat/win32/dirent.o\n \tCOMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\\\".exe\\\"\n \tBASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE\n@@ -528,6 +529,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))\n \tCOMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32\n \tCOMPAT_CFLAGS += -DSTRIP_EXTENSION=\\\".exe\\\"\n \tCOMPAT_OBJS += compat/mingw.o compat/winansi.o \\\n+\t\tcompat/win32/ancestry.o \\\n \t\tcompat/win32/path-utils.o \\\n \t\tcompat/win32/pthread.o compat/win32/syslog.o \\\n \t\tcompat/win32/dirent.o\ndiff --git a/trace2.h b/trace2.h\nindex 7ab9f355f3..a4a075d060 100644\n--- a/trace2.h\n+++ b/trace2.h\n@@ -387,4 +387,17 @@ __attribute__((format (printf, 1, 2)))\n void trace2_printf(const char *fmt, ...);\n #endif\n \n+/*\n+ * Optional platform-specific code to dump information about the\n+ * current and any parent process(es).  This is intended to allow\n+ * post-processors to know who spawned this git instance and anything\n+ * else the platform may be able to tell us about the current process.\n+ */\n+#if defined(GIT_WINDOWS_NATIVE)\n+void trace2_collect_process_info(void);\n+#else\n+#define trace2_collect_process_info() \\\n+\tdo {} while (0)\n+#endif\n+\n #endif /* TRACE2_H */\n-- \ngitgitgadget\n\n"},{"id":"367870","messageId":"1a90de9dab0dd836e54fee9e08ab9e2284e1027a.1548712060.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 01/14] trace2: Documentation/technical/api-trace2.txt","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-28T21:47:41Z","receivedAt":"2019-01-28T21:47:48Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreated design document for Trace2 feature.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Documentation/technical/api-trace2.txt | 1158 ++++++++++++++++++++++++\n 1 file changed, 1158 insertions(+)\n create mode 100644 Documentation/technical/api-trace2.txt\n\ndiff --git a/Documentation/technical/api-trace2.txt b/Documentation/technical/api-trace2.txt\nnew file mode 100644\nindex 0000000000..501fd770f2\n--- /dev/null\n+++ b/Documentation/technical/api-trace2.txt\n@@ -0,0 +1,1158 @@\n+Trace2 API\n+==========\n+\n+The Trace2 API can be used to print debug, performance, and telemetry\n+information to stderr or a file.  The Trace2 feature is inactive unless\n+explicitly enabled by setting one or more of the `GIT_TR2`, `GIT_TR2_PERF`,\n+or `GIT_TR2_EVENT` environment variables.\n+\n+The Trace2 API is intended to replace the existing (Trace1)\n+printf-style tracing provided by the existing `GIT_TRACE` and\n+`GIT_TRACE_PERFORMANCE` facilities.  During initial implementation,\n+Trace2 and Trace1 may operate in parallel.\n+\n+The Trace2 API defines a set of high-level events with known fields,\n+such as (`start`: `argv`) and (`exit`: {`exit-code`, `elapsed-time`}).\n+\n+These high-level events are written to one or more Trace2 Targets\n+in a target-specific format.  Each Trace2 Target defines a different\n+purpose-specific view onto the event data stream.  In this mannor,\n+a single set of Trace2 API event calls in the Git source can drive\n+different types of analysis.\n+\n+Trace2 Targets\n+--------------\n+\n+Trace2 defines the following set of Trace2 Targets.  These can be\n+independently enabled and disabled.  Trace2 API events are sent to\n+each enabled target.  Each enabled target writes an event message\n+in a target-specific format (described later).\n+\n+`GIT_TR2` (NORMAL)::\n+\n+\ta simple printf format like GIT_TRACE.\n++\n+------------\n+$ export GIT_TR2=~/log.normal\n+$ git version\n+git version 2.20.1.155.g426c96fcdb\n+------------\n++\n+------------\n+$ cat ~/log.normal\n+12:28:42.620009 common-main.c:38                  version 2.20.1.155.g426c96fcdb\n+12:28:42.620989 common-main.c:39                  start git version\n+12:28:42.621101 git.c:432                         cmd_verb version (version)\n+12:28:42.621215 git.c:662                         exit elapsed:0.001227 code:0\n+12:28:42.621250 trace2/tr2_tgt_normal.c:124       atexit elapsed:0.001265 code:0\n+------------\n+\n+`GIT_TR2_PERF` (PERF)::\n+\n+\ta column-based format to replace GIT_TRACE_PERFORMANCE suitable for\n+\tdevelopment and testing, possibly to complement tools like gprof.\n++\n+------------\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git version\n+git version 2.20.1.155.g426c96fcdb\n+------------\n++\n+------------\n+$ cat ~/log.perf\n+12:28:42.620675 common-main.c:38                  | d0 | main                     | version      |     |           |           |            | 2.20.1.155.g426c96fcdb\n+12:28:42.621001 common-main.c:39                  | d0 | main                     | start        |     |           |           |            | git version\n+12:28:42.621111 git.c:432                         | d0 | main                     | cmd_verb     |     |           |           |            | version (version)\n+12:28:42.621225 git.c:662                         | d0 | main                     | exit         |     |  0.001227 |           |            | code:0\n+12:28:42.621259 trace2/tr2_tgt_perf.c:211         | d0 | main                     | atexit       |     |  0.001265 |           |            | code:0\n+------------\n+\n+`GIT_TR2_EVENT` (EVENT)::\n+\n+\ta JSON-based format of event data suitable for telemetry analysis.\n++\n+------------\n+$ export GIT_TR2_EVENT=~/log.event\n+$ git version\n+git version 2.20.1.155.g426c96fcdb\n+------------\n++\n+------------\n+$ cat ~/log.event\n+{\"event\":\"version\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.620713\",\"file\":\"common-main.c\",\"line\":38,\"evt\":\"1\",\"exe\":\"2.20.1.155.g426c96fcdb\"}\n+{\"event\":\"start\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621027\",\"file\":\"common-main.c\",\"line\":39,\"argv\":[\"git\",\"version\"]}\n+{\"event\":\"cmd_verb\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621122\",\"file\":\"git.c\",\"line\":432,\"name\":\"version\",\"hierarchy\":\"version\"}\n+{\"event\":\"exit\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621236\",\"file\":\"git.c\",\"line\":662,\"t_abs\":0.001227,\"code\":0}\n+{\"event\":\"atexit\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621268\",\"file\":\"trace2/tr2_tgt_event.c\",\"line\":163,\"t_abs\":0.001265,\"code\":0}\n+------------\n+\n+Trace2 API Definitions\n+----------------------\n+\n+All public Trace2 functions and macros are defined in `trace2.h` and\n+`trace2.c`.  These are documented in detail in `trace2.h`.  All\n+public symbols are prefixed with `trace2_`.\n+\n+There are no public Trace2 data structures.\n+\n+The Trace2 code also defines a set of private functions and data types\n+in the `trace2/` directory.  These symbols are prefixed with `tr2_`\n+and should only be used by functions in `trace2.c`.\n+\n+The functions defined by the Trace2 API can be grouped into the\n+following groups.\n+\n+Basic Command Events::\n+\n+\tThese are concerned with the lifetime of the overall git process.\n++\n+----------------\n+trace2_initialize(...)\n+\n+trace2_cmd_start(...)\n+trace2_cmd_exit(...)\n+trace2_cmd_error(...)\n+trace2_cmd_path(...)\n+----------------\n+\n+Git Command Detail Events::\n+\n+\tThese are concerned with describing the specific Git command\n+\tafter the command line, config, and environment are inspected.\n++\n+----------------\n+trace2_cmd_verb(...)\n+trace2_cmd_subverb(...)\n+trace2_cmd_alias(...)\n+\n+trace2_cmd_list_config(...)\n+trace2_cmd_set_config(...)\n+\n+trace2_def_param(...)\n+trace2_def_repo(...)\n+----------------\n+\n+Git Child Process Events::\n+\n+\tThese are concerned with the various spawned child processes,\n+\tincluding sub-git processes and hooks,\n++\n+----------------\n+trace2_child_start(...)\n+trace2_child_exit(...)\n+\n+trace2_exec(...)\n+trace2_exec_result(...)\n+----------------\n+\n+Git Thread Events::\n+\n+\tThese are concerned with Git thread usage.\n++\n+----------------\n+trace2_thread_start(...)\n+trace2_thread_exit(...)\n+----------------\n+\n+Region and Data Events::\n+\n+\tThese are concerned with recording performance data\n+\tover regions or spans of code.\n++\n+----------------\n+trace2_region_enter(...)\n+trace2_region_enter_printf(...)\n+trace2_region_leave(...)\n+trace2_region_leave_printf(...)\n+\n+trace2_data_string(...)\n+trace2_data_intmax(...)\n+trace2_data_json(...)\n+\n+trace2_printf(...)\n+----------------\n+\n+Trace2 API Usage\n+----------------\n+\n+Here is a hypothetical usage of the Trace2 API showing the intended\n+usage (without worrying about the actual Git details).\n+\n+Initialization::\n+\n+\tInitialization happens in `main()`.  The initialization code\n+\tdetermines which, if any, Trace2 Targets should be enabled and\n+\temits the `version`, `start`, and `exit` events.  It causes an\n+\t`atexit` function and `signal` handler to be registered that\n+\twill emit `atexit` and `signal` events.\n++\n+----------------\n+int main(int argc, const char **argv)\n+{\n+\tint exit_code;\n+\n+\ttrace2_initialize();\n+\ttrace2_cmd_start(argv);\n+\n+\texit_code = cmd_main(argc, argv);\n+\n+\ttrace2_cmd_exit(exit_code);\n+\n+\treturn exit_code;\n+}\n+----------------\n+\n+Command Details::\n+\n+\tAfter the basics are established, additional process\n+\tinformation can be sent to Trace2 as it is discovered, such as\n+\tthe command verb, alias expansion, interesting config\n+\tsettings, the repository worktree, error messages, and etc.\n++\n+----------------\n+int cmd_checkout(int argc, const char **argv)\n+{\n+\t// emit a single \"def_param\" event\n+\ttrace2_def_param(\"core.editor\", \"emacs\");\n+\n+\t// emit def_params for any config setting matching a pattern\n+\t// in GIT_TR2_CONFIG_PARAMS.\n+\ttrace2_cmd_list_config();\n+\n+\ttrace2_cmd_verb(\"checkout\");\n+\ttrace2_cmd_subverb(\"branch\");\n+\ttrace2_def_repo(the_repository);\n+\n+\tif (do_something(...))\n+\t    trace2_cmd_error(\"Path '%s': cannot do something\", path);\n+\n+\treturn 0;\n+}\n+----------------\n+\n+Child Processes::\n+\n+\tGit typically spawns many sub-processes, such as sub-git\n+\tcommands, shell scripts, editors, pagers, and hooks.\n+\tTrace2 child events record time spent waiting for child\n+\tprocesses and help track performance problems across\n+\tmultiple layers of child processes.\n++\n+----------------\n+void run_child(...)\n+{\n+\tint child_exit_code;\n+\tstruct child_process cmd = CHILD_PROCESS_INIT;\n+\t...\n+\n+\ttrace2_child_start(&cmd);\n+\tchild_exit_code = ...();\n+\ttrace2_child_exit(&cmd, child_exit_code);\n+}\n+----------------\n++\n+For example, the following fetch command spawned ssh, index-pack,\n+rev-list, and gc.  This example also shows that fetch took\n+5.199 seconds and of that 4.932 was in ssh.\n++\n+----------------\n+$ export GIT_TR2_BRIEF=1\n+$ export GIT_TR2=~/log.normal\n+$ git fetch origin\n+...\n+\n+$ cat ~/log.normal\n+version 2.20.1.vfs.1.1.47.g534dbe1ad1\n+start git fetch origin\n+worktree /Users/jeffhost/work/gfw\n+cmd_verb fetch (fetch)\n+child_start[0] ssh git@github.com ...\n+child_start[1] git index-pack ...\n+... (Trace2 events from child processes omitted)\n+child_exit[1] pid:14707 code:0 elapsed:0.076353\n+child_exit[0] pid:14706 code:0 elapsed:4.931869\n+child_start[2] git rev-list ...\n+... (Trace2 events from child process omitted)\n+child_exit[2] pid:14708 code:0 elapsed:0.110605\n+child_start[3] git gc --auto\n+... (Trace2 events from child process omitted)\n+child_exit[3] pid:14709 code:0 elapsed:0.006240\n+exit elapsed:5.198503 code:0\n+atexit elapsed:5.198541 code:0\n+----------------\n++\n+When a git process is a (direct or indirect) child of another\n+git process, it inherits Trace2 context information.  This\n+allows the child to print a verb hierarchy.  This example\n+shows gc as child[3] of fetch.  When the gc process reports\n+its verb as \"gc\", it also reports the hierarchy as \"fetch/gc\".\n+(In this example, trace2 messages from the child process is\n+indented for clarity.)\n++\n+----------------\n+$ export GIT_TR2_BRIEF=1\n+$ export GIT_TR2=~/log.normal\n+$ git fetch origin\n+...\n+\n+$ cat ~/log.normal\n+version 2.20.1.160.g5676107ecd.dirty\n+start git fetch official\n+worktree /Users/jeffhost/work/gfw\n+cmd_verb fetch (fetch)\n+...\n+child_start[3] git gc --auto\n+    version 2.20.1.160.g5676107ecd.dirty\n+    start /Users/jeffhost/work/gfw/git gc --auto\n+    worktree /Users/jeffhost/work/gfw\n+    cmd_verb gc (fetch/gc)\n+    exit elapsed:0.001959 code:0\n+    atexit elapsed:0.001997 code:0\n+child_exit[3] pid:20303 code:0 elapsed:0.007564\n+exit elapsed:3.868938 code:0\n+atexit elapsed:3.868970 code:0\n+----------------\n+\n+Region Events::\n+\n+\tTrace2 defines a region as a timed span of code.\n++\n+----------------\n+void wt_status_collect(struct wt_status *s)\n+{\n+\ttrace2_region_enter(\"status\", \"worktrees\", s->repo);\n+\twt_status_collect_changes_worktree(s);\n+\ttrace2_region_leave(\"status\", \"worktrees\", s->repo);\n+\n+\ttrace2_region_enter(\"status\", \"index\", s->repo);\n+\twt_status_collect_changes_index(s);\n+\ttrace2_region_leave(\"status\", \"index\", s->repo);\n+\n+\ttrace2_region_enter(\"status\", \"untracked\", s->repo);\n+\twt_status_collect_untracked(s);\n+\ttrace2_region_leave(\"status\", \"untracked\", s->repo);\n+}\n+\n+void wt_status_print(struct wt_status *s)\n+{\n+\ttrace2_region_enter(\"status\", \"print\", s->repo);\n+\tswitch (s->status_format) {\n+\t    ...\n+\t}\n+\ttrace2_region_leave(\"status\", \"print\", s->repo);\n+}\n+----------------\n++\n+In this example, scanning for untracked files ran from +0.012568 to\n++0.027149 (since the process started) and took 0.014581 seconds.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+\n+$ cat ~/log.perf\n+d0 | main                     | version      |     |           |           |            | 2.20.1.160.g5676107ecd.dirty\n+d0 | main                     | start        |     |           |           |            | git status\n+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n+d0 | main                     | cmd_verb     |     |           |           |            | status (status)\n+...\n+d0 | main                     | region_enter | r1  |  0.010988 |           | status     | label:worktrees\n+d0 | main                     | region_leave | r1  |  0.011236 |  0.000248 | status     | label:worktrees\n+d0 | main                     | region_enter | r1  |  0.011260 |           | status     | label:index\n+d0 | main                     | region_leave | r1  |  0.012542 |  0.001282 | status     | label:index\n+d0 | main                     | region_enter | r1  |  0.012568 |           | status     | label:untracked\n+d0 | main                     | region_leave | r1  |  0.027149 |  0.014581 | status     | label:untracked\n+d0 | main                     | region_enter | r1  |  0.027411 |           | status     | label:print\n+d0 | main                     | region_leave | r1  |  0.028741 |  0.001330 | status     | label:print\n+d0 | main                     | exit         |     |  0.028778 |           |            | code:0\n+d0 | main                     | atexit       |     |  0.028809 |           |            | code:0\n+----------------\n++\n+Regions may be nested.  This causes event messages to be indented.\n+Elapsed times are relative to the start of the correpsonding nesting\n+level as expected.  For example, if we add region events to:\n++\n+----------------\n+static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n+\tstruct index_state *istate, const char *base, int baselen,\n+\tstruct untracked_cache_dir *untracked, int check_only,\n+\tint stop_at_first_file, const struct pathspec *pathspec)\n+{\n+\tenum path_treatment state, subdir_state, dir_state = path_none;\n+\n+\ttrace2_region_enter_printf(\"dir\", \"read_recursive\", NULL, \"%.*s\", baselen, base);\n+\t...\n+\ttrace2_region_leave_printf(\"dir\", \"read_recursive\", NULL, \"%.*s\", baselen, base);\n+\treturn dir_state;\n+}\n+----------------\n++\n+We can further investigate the time spent scanning for untracked files.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+$ cat ~/log.perf\n+d0 | main                     | version      |     |           |           |            | 2.20.1.162.gb4ccea44db.dirty\n+d0 | main                     | start        |     |           |           |            | git status\n+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n+d0 | main                     | cmd_verb     |     |           |           |            | status (status)\n+...\n+d0 | main                     | region_enter | r1  |  0.015047 |           | status     | label:untracked\n+d0 | main                     | region_enter |     |  0.015132 |           | dir        | ..label:read_recursive\n+d0 | main                     | region_enter |     |  0.016341 |           | dir        | ....label:read_recursive vcs-svn/\n+d0 | main                     | region_leave |     |  0.016422 |  0.000081 | dir        | ....label:read_recursive vcs-svn/\n+d0 | main                     | region_enter |     |  0.016446 |           | dir        | ....label:read_recursive xdiff/\n+d0 | main                     | region_leave |     |  0.016522 |  0.000076 | dir        | ....label:read_recursive xdiff/\n+d0 | main                     | region_enter |     |  0.016612 |           | dir        | ....label:read_recursive git-gui/\n+d0 | main                     | region_enter |     |  0.016698 |           | dir        | ......label:read_recursive git-gui/po/\n+d0 | main                     | region_enter |     |  0.016810 |           | dir        | ........label:read_recursive git-gui/po/glossary/\n+d0 | main                     | region_leave |     |  0.016863 |  0.000053 | dir        | ........label:read_recursive git-gui/po/glossary/\n+...\n+d0 | main                     | region_enter |     |  0.031876 |           | dir        | ....label:read_recursive builtin/\n+d0 | main                     | region_leave |     |  0.032270 |  0.000394 | dir        | ....label:read_recursive builtin/\n+d0 | main                     | region_leave |     |  0.032414 |  0.017282 | dir        | ..label:read_recursive\n+d0 | main                     | region_leave | r1  |  0.032454 |  0.017407 | status     | label:untracked\n+...\n+d0 | main                     | exit         |     |  0.034279 |           |            | code:0\n+d0 | main                     | atexit       |     |  0.034322 |           |            | code:0\n+----------------\n++\n+Trace2 regions are similar to the existing trace_performance_enter()\n+and trace_performance_leave() routines, but are thread safe and\n+maintain per-thread stacks of timers.\n+\n+Data Events::\n+\n+\tTrace2 defines a data event to report important variables.\n+\tThese events contain the active thread and are indented\n+\trelative to the current thread's open region stack.\n++\n+----------------\n+int read_index_from(struct index_state *istate, const char *path,\n+\tconst char *gitdir)\n+{\n+\ttrace2_region_enter_printf(\"index\", \"do_read_index\", the_repository, \"%s\", path);\n+\n+\t...\n+\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/version\", istate->version);\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/cache_nr\", istate->cache_nr);\n+\n+\ttrace2_region_leave_printf(\"index\", \"do_read_index\", the_repository, \"%s\", path);\n+}\n+----------------\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+$ cat ~/log.perf\n+d0 | main                     | version      |     |           |           |            | 2.20.1.156.gf9916ae094.dirty\n+d0 | main                     | start        |     |           |           |            | git status\n+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n+d0 | main                     | cmd_verb     |     |           |           |            | status (status)\n+d0 | main                     | region_enter | r1  |  0.001791 |           | index      | label:do_read_index .git/index\n+d0 | main                     | data         | r1  |  0.002494 |  0.000703 | index      | ..read/version:2\n+d0 | main                     | data         | r1  |  0.002520 |  0.000729 | index      | ..read/cache_nr:3552\n+d0 | main                     | region_leave | r1  |  0.002539 |  0.000748 | index      | label:do_read_index .git/index\n+...\n+----------------\n+\n+Thread Events::\n+\n+\tTrace2 defines events to mark the start and end of a thread.\n+\tA thread name constructed during the start event. This name is\n+\tadded to subsequent thread-specific events.  The thread exit\n+\tevent reports the elapsed time in the thread.\n++\n+For example, the multithreaded preload-index code can be\n+instrumented with a region around the thread pool and then\n+per-thread start and exit events within the threadproc.\n++\n+----------------\n+static void *preload_thread(void *_data)\n+{\n+\ttrace2_thread_start(\"preload_thread\");\n+\n+\ttrace2_data_intmax(\"index\", the_repository, \"offset\", p->offset);\n+\ttrace2_data_intmax(\"index\", the_repository, \"count\", nr);\n+\n+\tdo {\n+\t    ...\n+\t} while (--nr > 0);\n+\t...\n+\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+void preload_index(struct index_state *index,\n+\tconst struct pathspec *pathspec,\n+\tunsigned int refresh_flags)\n+{\n+\ttrace2_region_enter(\"index\", \"preload\", the_repository);\n+\n+\tfor (i = 0; i < threads; i++) {\n+\t    ... /* create thread */\n+\t}\n+\n+\tfor (i = 0; i < threads; i++) {\n+\t    ... /* join thread */\n+\t}\n+\n+\ttrace2_region_leave(\"index\", \"preload\", the_repository);\n+}\n+----------------\n++\n+In this example preload_index() was executed by the `main` thread\n+and started the `preload` region.  Seven threads, named\n+`th01:preload_thread` through `th07:preload_thread`, were started.\n+Events from each thread are atomically appended to the shared target\n+stream as they occur so they may appear in random order with respect\n+other threads. Finally, the main thread waits for the threads to\n+finish and leaves the region.\n++\n+Data events are tagged with the active thread name.  They are used\n+to report the per-thread parameters.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+$ cat ~/log.perf\n+d0 | main                     | version      |     |           |           |            | 2.20.1.156.gf9916ae094.dirty\n+d0 | main                     | start        |     |           |           |            | git status\n+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n+d0 | main                     | cmd_verb     |     |           |           |            | status (status)\n+...\n+d0 | main                     | region_enter | r1  |  0.002595 |           | index      | label:preload\n+d0 | th01:preload_thread      | thread_start |     |  0.002699 |           |            |\n+d0 | th02:preload_thread      | thread_start |     |  0.002721 |           |            |\n+d0 | th01:preload_thread      | data         | r1  |  0.002736 |  0.000037 | index      | offset:0\n+d0 | th02:preload_thread      | data         | r1  |  0.002751 |  0.000030 | index      | offset:2032\n+d0 | th03:preload_thread      | thread_start |     |  0.002711 |           |            |\n+d0 | th06:preload_thread      | thread_start |     |  0.002739 |           |            |\n+d0 | th01:preload_thread      | data         | r1  |  0.002766 |  0.000067 | index      | count:508\n+d0 | th06:preload_thread      | data         | r1  |  0.002856 |  0.000117 | index      | offset:2540\n+d0 | th03:preload_thread      | data         | r1  |  0.002824 |  0.000113 | index      | offset:1016\n+d0 | th04:preload_thread      | thread_start |     |  0.002710 |           |            |\n+d0 | th02:preload_thread      | data         | r1  |  0.002779 |  0.000058 | index      | count:508\n+d0 | th06:preload_thread      | data         | r1  |  0.002966 |  0.000227 | index      | count:508\n+d0 | th07:preload_thread      | thread_start |     |  0.002741 |           |            |\n+d0 | th07:preload_thread      | data         | r1  |  0.003017 |  0.000276 | index      | offset:3048\n+d0 | th05:preload_thread      | thread_start |     |  0.002712 |           |            |\n+d0 | th05:preload_thread      | data         | r1  |  0.003067 |  0.000355 | index      | offset:1524\n+d0 | th05:preload_thread      | data         | r1  |  0.003090 |  0.000378 | index      | count:508\n+d0 | th07:preload_thread      | data         | r1  |  0.003037 |  0.000296 | index      | count:504\n+d0 | th03:preload_thread      | data         | r1  |  0.002971 |  0.000260 | index      | count:508\n+d0 | th04:preload_thread      | data         | r1  |  0.002983 |  0.000273 | index      | offset:508\n+d0 | th04:preload_thread      | data         | r1  |  0.007311 |  0.004601 | index      | count:508\n+d0 | th05:preload_thread      | thread_exit  |     |  0.008781 |  0.006069 |            |\n+d0 | th01:preload_thread      | thread_exit  |     |  0.009561 |  0.006862 |            |\n+d0 | th03:preload_thread      | thread_exit  |     |  0.009742 |  0.007031 |            |\n+d0 | th06:preload_thread      | thread_exit  |     |  0.009820 |  0.007081 |            |\n+d0 | th02:preload_thread      | thread_exit  |     |  0.010274 |  0.007553 |            |\n+d0 | th07:preload_thread      | thread_exit  |     |  0.010477 |  0.007736 |            |\n+d0 | th04:preload_thread      | thread_exit  |     |  0.011657 |  0.008947 |            |\n+d0 | main                     | region_leave | r1  |  0.011717 |  0.009122 | index      | label:preload\n+...\n+d0 | main                     | exit         |     |  0.029996 |           |            | code:0\n+d0 | main                     | atexit       |     |  0.030027 |           |            | code:0\n+----------------\n++\n+In this example, the preload region took 0.009122 seconds.  The 7 threads\n+took between 0.006069 and 0.008947 seconds to work on their portion of\n+the index.  Thread \"th01\" worked on 508 items at offset 0.  Thread \"th02\"\n+worked on 508 items at offset 2032.  Thread \"th04\" worked on 508 itemts\n+at offset 508.\n++\n+This example also shows that thread names are assigned in a racy manner\n+as each thread starts and allocates TLS storage.\n+\n+Printf Events::\n+\n+\tTrace2 defines a printf interface to write arbitrary messages\n+\tto the targets.  These are convenient for development, but\n+\tmay more difficult to post-process.\n+\n+Trace2 Target Formats\n+---------------------\n+\n+NORMAL Target Format\n+~~~~~~~~~~~~~~~~~~~~\n+\n+NORMAL format is enabled when the `GIT_TR2` environment variable\n+is set to \"1\" or an absolute pathname.\n+\n+Events are written as lines of the form:\n+\n+------------\n+[<time> SP <filename>:<line> SP+] <event-name> [[SP] <event-message>] LF\n+------------\n+\n+`<event-name>`::\n+\n+\tis the event name.\n+`<event-message>`::\n+\tis a free-form printf message intended for human consumption.\n++\n+Note that this may contain embedded LF or CRLF characters that are\n+not escaped, so the event may spill across multiple lines.\n+\n+If `GIT_TR2_BRIEF` is true, the time, filename, and line number fields\n+are omitted.\n+\n+This target is intended to be more of a summary (like GIT_TRACE) and\n+less detailed than the other targets, and therefore does not emit\n+messages for some of the Trace2 API verbs.  For example, thread,\n+region, and data events are omitted.\n+\n+PERF Target Format\n+~~~~~~~~~~~~~~~~~~\n+\n+PERF format is enabled when the `GIT_TR2_PERF` environment variable\n+is set to \"1\" or an absolute pathname.\n+\n+Events are written as lines of the form:\n+\n+------------\n+[<time> SP <filename>:<line> SP+\n+    BAR SP] d<depth> SP\n+    BAR SP <thread-name> SP+\n+    BAR SP <event-name> SP+\n+    BAR SP [r<repo-id>] SP+\n+    BAR SP [<t_abs>] SP+\n+    BAR SP [<t_rel>] SP+\n+    BAR SP [<category>] SP+\n+    BAR SP DOTS* <perf-event-message>\n+    LF\n+------------\n+\n+`<depth>`::\n+\tis the git process depth.  This is the number of parent\n+\tgit processes.  A top-level git command has depth value 0.\n+\tA child of it has depth value 1.\n+\n+`<thread-name>`::\n+\tis a unique name for the thread.  The primary thread\n+\tis called \"main\".  Other thread names are of the form \"th%d:%s\"\n+\tand include a unique number and the name of the thread-proc.\n+\n+`<event-name>`::\n+\tis the event name.\n+\n+`<repo-id>`::\n+\twhen present, is a number indicating the repository\n+\tin use.  A `def_repo` event is emitted when a repository is\n+\topened.  This defines the repo-id and associated worktree.\n+\tSubsequent repo-specific events will reference this repo-id.\n++\n+Currently, this is always \"r1\" for the main repository.\n+This field is in anticipation of in-proc submodules in the future.\n+\n+`<t_abs>`::\n+\twhen present, is the absolute time in seconds since the\n+\tprogram started.\n+\n+`<t_rel>`::\n+\twhen present, is time in seconds relative to the start of\n+\tthe current region.  For a thread-exit event, it is the elapsed\n+\ttime of the thread.\n+\n+`<category>`::\n+\tis present on region and data events and is used to\n+\tindicate a broad category, such as \"index\" or \"status\".\n++\n+Currently, this is advisory.  This field is in anticipation\n+of category-based filtering, similar to the `GIT_TRACE_<key>`\n+facility in Trace1.\n+\n+`<perf-event-message>`::\n+\tis a free-form printf message intended for human consumption.\n+\n+If `GIT_TR2_PERF_BRIEF` is true, the time, filename, and line number\n+fields are omitted.\n+\n+This target is intended for interactive performance analysis\n+during development and is quite noisy.\n+\n+EVENT Target Format\n+~~~~~~~~~~~~~~~~~~~\n+\n+EVENT format is enabled when the `GIT_TR2_EVENT` environment\n+variable is set to \"1\" or an absolute pathname.\n+\n+Each event is a JSON-object containing multiple key/value pairs\n+written as a single line and followed by a LF.\n+\n+------------\n+'{' <key> ':' <value> [',' <key> ':' <value>]* '}' LF\n+------------\n+\n+Some key/value pairs are common to all events and some are\n+event-specific.\n+\n+Common Key/Value Pairs\n+^^^^^^^^^^^^^^^^^^^^^^\n+\n+The following key/value pairs are common to all events:\n+\n+------------\n+{\n+\t\"event\":\"version\",\n+\t\"sid\":\"1547659722619736-11614\",\n+\t\"thread\":\"main\",\n+\t\"time\":\"2019-01-16 17:28:42.620713\",\n+\t\"file\":\"common-main.c\",\n+\t\"line\":38,\n+\t...\n+}\n+------------\n+\n+`\"event\":<event>`::\n+\tis the event name.\n+\n+`\"sid\":<sid>`::\n+\tis the session-id.  This is a unique string to identify the\n+\tprocess instance to allow all events emitted by a process to\n+\tbe identified.  A session-id is used instead of a PID because\n+\tPIDs are recycled by the OS.  For child git processes, the\n+\tsession-id is prepended with the session-id of the parent git\n+\tprocess to allow parent-child relationships to be identified\n+\tduring post-processing.\n+\n+`\"thread\":<thread>`::\n+\tis the thread name.\n+\n+`\"time\":<time>`::\n+\tis the UTC time of the event.\n+\n+`\"file\":<filename>`::\n+\tis source file generating the event.\n+\n+`\"line\":<line-number>`::\n+\tis the integer source line number generating the event.\n+\n+`\"repo\":<repo-id>`::\n+\twhen present, is the integer repo-id as described previously.\n+\n+If `GIT_TR2_EVENT_BRIEF` is true, the file and line fields are omitted\n+from all events and the time field is only present on the `start` and\n+`atexit` events.\n+\n+Event-Specific Key/Value Pairs\n+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n+\n+`version`::\n+\tThis event gives the version of the executable and the EVENT format.\n++\n+------------\n+{\n+\t\"event\":\"version\",\n+\t...\n+\t\"evt\":\"1\",\t\t       # EVENT format version\n+\t\"exe\":\"2.20.1.155.g426c96fcdb\" # git version\n+}\n+------------\n+\n+`start`::\n+\tThis event contains the complete argv received by main().\n++\n+------------\n+{\n+\t\"event\":\"start\",\n+\t...\n+\t\"argv\":[\"git\",\"version\"]\n+}\n+------------\n+\n+`exit`::\n+\tThis event is emitted when git calls `exit()`.\n++\n+------------\n+{\n+\t\"event\":\"exit\",\n+\t...\n+\t\"t_abs\":0.001227, # elapsed time in seconds\n+\t\"code\":0\t  # exit code\n+}\n+------------\n+\n+`atexit`::\n+\tThis event is emitted by the Trace2 `atexit` routine during\n+\tfinal shutdown.  It should be the last event emitted by the\n+\tprocess.  (The elapsed time reported here is greater than the\n+\ttime reported in the `exit` event because it runs after all\n+\tother atexit tasks have completed.)\n++\n+------------\n+{\n+\t\"event\":\"atexit\",\n+\t...\n+\t\"t_abs\":0.001227,\n+\t\"code\":0\n+}\n+------------\n+\n+`signal`::\n+\tThis event is emitted when the program is terminated by a user\n+\tsignal.  Depending on the platform, the signal event may\n+\tprevent the `atexit` event from being generated.\n++\n+------------\n+{\n+\t\"event\":\"signal\",\n+\t...\n+\t\"t_abs\":0.001227,\n+\t\"signal\":13\n+}\n+------------\n+\n+`error`::\n+\tThis event is emitted when one of the `error()`, `die()`,\n+\tor `usage()` functions are called.\n++\n+------------\n+{\n+\t\"event\":\"error\",\n+\t...\n+\t\"msg\":\"invalid option: --cahced\", # formatted error message\n+\t\"fmt\":\"invalid option: %s\"\t  # error format string\n+}\n+------------\n++\n+The error event may be emitted more than once.  The format string\n+allows post-processors to group errors by type without worrying\n+about specific error arguments.\n+\n+`cmd_path`::\n+\tThis event contains the discovered full path of the git\n+\texecutable (on platforms that are configured to resolve it).\n++\n+------------\n+{\n+\t\"event\":\"cmd_path\",\n+\t...\n+\t\"path\":\"C:/work/gfw/git.exe\"\n+}\n+------------\n+\n+`cmd_verb`::\n+\tThis event contains the primary verb for this command\n+\tand the hierarchy of command verbs.\n++\n+------------\n+{\n+\t\"event\":\"cmd_verb\",\n+\t...\n+\t\"name\":\"pack-objects\",\n+\t\"hierarchy\":\"push/pack-objects\"\n+}\n+------------\n++\n+Normally, the `name` field contains the canonical name of the\n+command.  When a canonical command verb is not available, one of\n+these special values are used:\n++\n+------------\n+\"_query_\"            # \"git --html-path\"\n+\"_run_dashed_\"       # when \"git foo\" tries to run \"git-foo\"\n+\"_run_shell_alias_\"  # alias expansion to a shell command\n+\"_run_git_alias_\"    # alias expansion to a git command\n+\"_usage_\"            # usage error\n+------------\n+\n+`cmd_subverb`::\n+\tThis event, when present, describes the variant of the main\n+\tcommand verb.  This event may be emitted more than once.\n++\n+------------\n+{\n+\t\"event\":\"cmd_subverb\",\n+\t...\n+\t\"name\":\"branch\"\n+}\n+------------\n++\n+The `name` field is an arbitrary string that makes sense in the\n+context of the primary verb. For example, checkout can checkout a\n+branch or an individual file.  And these variations typically have\n+different performance characteristics that are not comparable.\n+\n+`alias`::\n+\tThis event is present when an alias is expanded.\n++\n+------------\n+{\n+\t\"event\":\"alias\",\n+\t...\n+\t\"alias\":\"l\",\t\t # registered alias\n+\t\"argv\":[\"log\",\"--graph\"] # alias expansion\n+}\n+------------\n+\n+`child_start`::\n+\tThis event describes a child process that is about to be\n+\tspawned.\n++\n+------------\n+{\n+\t\"event\":\"child_start\",\n+\t...\n+\t\"child_id\":2,\n+\t\"child_class\":\"?\",\n+\t\"use_shell\":false,\n+\t\"argv\":[\"git\",\"rev-list\",\"--objects\",\"--stdin\",\"--not\",\"--all\",\"--quiet\"]\n+\n+\t\"hook_name\":\"<hook_name>\"  # present when child_class is \"hook\"\n+\t\"cd\":\"<path>\"\t\t   # present when cd is required\n+}\n+------------\n++\n+The `child_id` field can be used to match this child_start with the\n+corresponding child_exit event.\n++\n+The `child_class` field is a rough classification, such as \"editor\",\n+\"pager\", \"transport/*\", and \"hook\".  Unclassified children are classified\n+with \"?\".\n+\n+`child_exit`::\n+\tThis event is generated after the git process has returned\n+\tfrom the waitpid() and collected the exit information from the\n+\tchild.\n++\n+------------\n+{\n+\t\"event\":\"child_exit\",\n+\t...\n+\t\"child_id\":2,\n+\t\"pid\":14708,\t # child PID\n+\t\"code\":0,\t # child exit-code\n+\t\"t_rel\":0.110605 # observed run-time of child process\n+}\n+------------\n++\n+Note that the session-id of the child process is not available to\n+the current/spawning process, so the child's PID is reported here as\n+a hint for post-processing.  (But it is only a hint because the child\n+proces may be a shell script which doesn't have a session-id.)\n++\n+Note that the `t_rel` field contains the observed run time in seconds\n+for the child process (starting before the fork/exec/spawn and\n+stopping after the waitpid() and includes OS process creation overhead).\n+So this time will be slightly larger than the atexit time reported by\n+the child process itself.\n+\n+`exec`::\n+\tThis event is generated before git attempts to `exec()`\n+\tanother command rather than starting a child process.\n++\n+------------\n+{\n+\t\"event\":\"exec\",\n+\t...\n+\t\"exec_id\":0,\n+\t\"exe\":\"git\",\n+\t\"argv\":[\"foo\", \"bar\"]\n+}\n+------------\n++\n+Since `exec()` does not return if successful, this will be the last\n+event generated for the current git command before the new git commit\n+starts.\n++\n+The `exec_id` field is a command-unique id and is only useful if the\n+`exec()` fails and a corresponding exec_result event is generated.\n+\n+`exec_result`::\n+\tThis event is generated if the `exec()` fails and control\n+\treturns to the current git command.\n++\n+------------\n+{\n+\t\"event\":\"exec_result\",\n+\t...\n+\t\"exec_id\":0,\n+\t\"code\":1      # error code (errno) from exec()\n+}\n+------------\n+\n+`thread_start`::\n+\tThis event is generated when a thread is started.  It is\n+\tgenerated from *within* the new thread's thread-proc (for TLS\n+\treasons).\n++\n+------------\n+{\n+\t\"event\":\"thread_start\",\n+\t...\n+\t\"thread\":\"th02:preload_thread\"\n+}\n+------------\n+\n+`thread_exit`::\n+\tThis event is generated when a thread exits.  It is generated\n+\tfrom *within* the thread's thread-proc (for TLS reasons).\n++\n+------------\n+{\n+\t\"event\":\"thread_exit\",\n+\t...\n+\t\"thread\":\"th02:preload_thread\",\n+\t\"t_rel\":0.007328                # thread elapsed time\n+}\n+------------\n+\n+`def_param`::\n+\tThis event is generated to log a global key/value pair for the\n+\tcurrent command.\n++\n+------------\n+{\n+\t\"event\":\"def_param\",\n+\t...\n+\t\"param\":\"core.editor\",\n+\t\"value\":\"emacs\"\n+}\n+------------\n++\n+This is intended to log \"values of interest\" for post-processing.  For\n+example, this might be configuration settings known to affect\n+performance or the remote URL so that different repos can be grouped\n+differently.  For thread- or region-specific key/values pairs, see the\n+`data` and `data_json` events.\n+\n+`def_repo`::\n+\tThis event defines a repo-id and associates it with the root\n+\tof the worktree.  Subsequent repo-relative events will refer\n+\tto this repo-id.  Repo-id number 1 refers to the main\n+\trepository.\n++\n+------------\n+{\n+\t\"event\":\"def_repo\",\n+\t...\n+\t\"repo\":1,\n+\t\"worktree\":\"/Users/jeffhost/work/gfw\"\n+}\n+------------\n++\n+As stated earlier, the repo-id is currently always 1, so there will\n+only be one def_repo event.  Later, if in-proc submodules are\n+supported, a def_repo event should be emitted for each submodule\n+visited.\n+\n+`region_enter`::\n+\tThis event is generated when entering a region.\n+\tIt serves as a marker in the event stream.  It starts an activity timer\n+\tfor the region.  And it pushes a frame on the region stack for the\n+\tcurrent thread.\n++\n+------------\n+{\n+\t\"event\":\"region_enter\",\n+\t...\n+\t\"repo\":1,\n+\t\"nesting\":1,\t\t # current region stack depth\n+\t\"category\":\"index\",\t # optional\n+\t\"label\":\"do_read_index\", # optional\n+\t\"msg\":\".git/index\"\t # optional\n+}\n+------------\n++\n+The `category` field may be used in a future enhancement to\n+do category-based filtering.\n++\n+The `GIT_TR2_EVENT_NESTING` environment variable can be used to\n+filter deeply nested regions and data events.  It defaults to \"2\".\n+\n+`region_leave`::\n+\tThis event is generated when leaving a region.  It serves as a\n+\tmarker in the event stream and reports the elapsed time spent\n+\tin the region.  It pops a frame off of the region stack for\n+\tthe current thread.\n++\n+------------\n+{\n+\t\"event\":\"region_leave\",\n+\t...\n+\t\"repo\":1,\n+\t\"t_rel\":0.002876,\t # time spent in region in seconds\n+\t\"nesting\":1,\t\t # region stack depth\n+\t\"category\":\"index\",\t # optional\n+\t\"label\":\"do_read_index\", # optional\n+\t\"msg\":\".git/index\"\t # optional\n+}\n+------------\n++\n+The examples in this document have generally shown the region_enter\n+and region_leave events with the same category and label fields.  This\n+is not a requirement, but makes it easier to understand the logs.\n+\n+`data`::\n+\tThis event is generated to log a thread- and region-local\n+\tkey/value pair.  For example, during a recursive treewalk,\n+\tlogging the relative path and the number of entries in the\n+\ttree.\n++\n+------------\n+{\n+\t\"event\":\"data\",\n+\t...\n+\t\"repo\":1,              # optional\n+\t\"t_abs\":0.024107,      # absolute elapsed time\n+\t\"t_rel\":0.001031,      # elapsed time in region/thread\n+\t\"nesting\":2,\n+\t\"category\":\"index\",\n+\t\"key\":\"read/cache_nr\",\n+\t\"value\":\"3552\"}\n+}\n+------------\n++\n+The `value` field may be an integer or a string.\n+\n+`data-json`::\n+\tThis event is generated to log a pre-formatted JSON string\n+\tcontaining structured data.\n++\n+------------\n+{\n+\t\"event\":\"data_json\",\n+\t...\n+\t\"repo\":1,              # optional\n+\t\"t_abs\":0.015905,\n+\t\"t_rel\":0.015905,\n+\t\"nesting\":1,\n+\t\"category\":\"process\",\n+\t\"key\":\"windows/ancestry\",\n+\t\"value\":[\"bash.exe\",\"bash.exe\"]\n+}\n+------------\n+\n+Appendix: Relationship to the Existing Trace Api (api-trace.txt)\n+----------------------------------------------------------------\n+\n+Eventually, I would like Trace2 to replace the existing Trace1 API.\n+\n+Trace2 is a superset of Trace1 and the existing Trace1 API calls could\n+be converted to Trace2, but that work has not yet been attempted.\n+\n+There are a few issues here:\n+\n+* Updating existing tests that assume GIT_TRACE format messages.\n+\n+* How to best handle custom GIT_TRACE_<key> messages?\n+\n+** The GIT_TRACE_<key> mechanism allows each <key> to write to a\n+different file (in addition to just stderr).\n+\n+** Do we want to maintain that ability or simply write to the existing\n+Trace2 targets (and convert <key> to a \"category\").\n+\n-- \ngitgitgadget\n\n"},{"id":"367871","messageId":"6be101d520ac7cc8169e94b9b8f865ec599935b4.1548712060.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 05/14] trace2:data: add editor/pager child classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-28T21:47:45Z","receivedAt":"2019-01-28T21:47:50Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 process classification for editor and pager\nchild processes.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n editor.c | 1 +\n pager.c  | 1 +\n 2 files changed, 2 insertions(+)\n\ndiff --git a/editor.c b/editor.c\nindex c985eee1f9..71547674ab 100644\n--- a/editor.c\n+++ b/editor.c\n@@ -78,6 +78,7 @@ static int launch_specified_editor(const char *editor, const char *path,\n \t\tp.argv = args;\n \t\tp.env = env;\n \t\tp.use_shell = 1;\n+\t\tp.trace2_child_class = \"editor\";\n \t\tif (start_command(&p) < 0)\n \t\t\treturn error(\"unable to start editor '%s'\", editor);\n \ndiff --git a/pager.c b/pager.c\nindex a768797fcf..4168460ae9 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -100,6 +100,7 @@ void prepare_pager_args(struct child_process *pager_process, const char *pager)\n \targv_array_push(&pager_process->args, pager);\n \tpager_process->use_shell = 1;\n \tsetup_pager_env(&pager_process->env_array);\n+\tpager_process->trace2_child_class = \"pager\";\n }\n \n void setup_pager(void)\n-- \ngitgitgadget\n\n"},{"id":"367872","messageId":"68192b8dfb42c6991fb5133e4ae6442411fbd71b.1548712060.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 07/14] trace2:data: add trace2 transport child classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-28T21:47:46Z","receivedAt":"2019-01-28T21:47:51Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 child classification for transport processes.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n connect.c          | 3 +++\n transport-helper.c | 2 ++\n 2 files changed, 5 insertions(+)\n\ndiff --git a/connect.c b/connect.c\nindex 24281b6082..3c6f829a05 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -1251,6 +1251,7 @@ struct child_process *git_connect(int fd[2], const char *url,\n \t\tconn = NULL;\n \t} else if (protocol == PROTO_GIT) {\n \t\tconn = git_connect_git(fd, hostandport, path, prog, version, flags);\n+\t\tconn->trace2_child_class = \"transport/git\";\n \t} else {\n \t\tstruct strbuf cmd = STRBUF_INIT;\n \t\tconst char *const *var;\n@@ -1293,9 +1294,11 @@ struct child_process *git_connect(int fd[2], const char *url,\n \t\t\t\tstrbuf_release(&cmd);\n \t\t\t\treturn NULL;\n \t\t\t}\n+\t\t\tconn->trace2_child_class = \"transport/ssh\";\n \t\t\tfill_ssh_args(conn, ssh_host, port, version, flags);\n \t\t} else {\n \t\t\ttransport_check_allowed(\"file\");\n+\t\t\tconn->trace2_child_class = \"transport/file\";\n \t\t\tif (version > 0) {\n \t\t\t\targv_array_pushf(&conn->env_array, GIT_PROTOCOL_ENVIRONMENT \"=version=%d\",\n \t\t\t\t\t\t version);\ndiff --git a/transport-helper.c b/transport-helper.c\nindex bf225c698f..1eb656b687 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -127,6 +127,8 @@ static struct child_process *get_helper(struct transport *transport)\n \t\targv_array_pushf(&helper->env_array, \"%s=%s\",\n \t\t\t\t GIT_DIR_ENVIRONMENT, get_git_dir());\n \n+\thelper->trace2_child_class = helper->args.argv[0]; /* \"remote-<name>\" */\n+\n \tcode = start_command(helper);\n \tif (code < 0 && errno == ENOENT)\n \t\tdie(_(\"unable to find remote helper for '%s'\"), data->name);\n-- \ngitgitgadget\n\n"},{"id":"367873","messageId":"b373ab640b2303493ab7163d1f549788a6b5d461.1548712060.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 08/14] trace2:data: add trace2 hook classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-28T21:47:47Z","receivedAt":"2019-01-28T21:47:52Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nClassify certain child processes as hooks.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/am.c           | 1 +\n builtin/receive-pack.c | 4 ++++\n builtin/worktree.c     | 1 +\n sequencer.c            | 2 ++\n transport.c            | 1 +\n 5 files changed, 9 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 95370313b6..a81e61c1bc 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -468,6 +468,7 @@ static int run_post_rewrite_hook(const struct am_state *state)\n \n \tcp.in = xopen(am_path(state, \"rewritten\"), O_RDONLY);\n \tcp.stdout_to_stderr = 1;\n+\tcp.trace2_hook_name = \"post-rewrite\";\n \n \tret = run_command(&cp);\n \ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 33187bd8e9..6eef8575b9 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -694,6 +694,8 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,\n \tproc.argv = argv;\n \tproc.in = -1;\n \tproc.stdout_to_stderr = 1;\n+\tproc.trace2_hook_name = hook_name;\n+\n \tif (feed_state->push_options) {\n \t\tint i;\n \t\tfor (i = 0; i < feed_state->push_options->nr; i++)\n@@ -807,6 +809,7 @@ static int run_update_hook(struct command *cmd)\n \tproc.stdout_to_stderr = 1;\n \tproc.err = use_sideband ? -1 : 0;\n \tproc.argv = argv;\n+\tproc.trace2_hook_name = \"update\";\n \n \tcode = start_command(&proc);\n \tif (code)\n@@ -1190,6 +1193,7 @@ static void run_update_post_hook(struct command *commands)\n \tproc.no_stdin = 1;\n \tproc.stdout_to_stderr = 1;\n \tproc.err = use_sideband ? -1 : 0;\n+\tproc.trace2_hook_name = \"post-update\";\n \n \tif (!start_command(&proc)) {\n \t\tif (use_sideband)\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 5e84026177..32e6f1b341 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -401,6 +401,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\t\tcp.dir = path;\n \t\t\tcp.env = env;\n \t\t\tcp.argv = NULL;\n+\t\t\tcp.trace2_hook_name = \"post-checkout\";\n \t\t\targv_array_pushl(&cp.args, absolute_path(hook),\n \t\t\t\t\t oid_to_hex(&null_oid),\n \t\t\t\t\t oid_to_hex(&commit->object.oid),\ndiff --git a/sequencer.c b/sequencer.c\nindex b68bca0bef..9a6ec7fcd4 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1102,6 +1102,7 @@ static int run_rewrite_hook(const struct object_id *oldoid,\n \tproc.argv = argv;\n \tproc.in = -1;\n \tproc.stdout_to_stderr = 1;\n+\tproc.trace2_hook_name = \"post-rewrite\";\n \n \tcode = start_command(&proc);\n \tif (code)\n@@ -3771,6 +3772,7 @@ static int pick_commits(struct repository *r,\n \t\t\t\thook.in = open(rebase_path_rewritten_list(),\n \t\t\t\t\tO_RDONLY);\n \t\t\t\thook.stdout_to_stderr = 1;\n+\t\t\t\thook.trace2_hook_name = \"post-rewrite\";\n \t\t\t\targv_array_push(&hook.args, post_rewrite_hook);\n \t\t\t\targv_array_push(&hook.args, \"rebase\");\n \t\t\t\t/* we don't care if this hook failed */\ndiff --git a/transport.c b/transport.c\nindex 99678153c1..ed5a733c4a 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1061,6 +1061,7 @@ static int run_pre_push_hook(struct transport *transport,\n \n \tproc.argv = argv;\n \tproc.in = -1;\n+\tproc.trace2_hook_name = \"pre-push\";\n \n \tif (start_command(&proc)) {\n \t\tfinish_command(&proc);\n-- \ngitgitgadget\n\n"},{"id":"367874","messageId":"548ea5274217e67b2bb9ba59b185a521b9e36160.1548712060.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 09/14] trace2:data: add trace2 instrumentation to index read/write","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-28T21:47:48Z","receivedAt":"2019-01-28T21:47:54Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 events to measure reading and writing the index.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n read-cache.c | 47 ++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 46 insertions(+), 1 deletion(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex bfff271a3d..f6d84bd7ae 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -2232,6 +2232,14 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)\n \t\tload_index_extensions(&p);\n \t}\n \tmunmap((void *)mmap, mmap_size);\n+\n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/version\", istate->version);\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/cache_nr\", istate->cache_nr);\n+\n \treturn istate->cache_nr;\n \n unmap:\n@@ -2263,9 +2271,17 @@ int read_index_from(struct index_state *istate, const char *path,\n \tif (istate->initialized)\n \t\treturn istate->cache_nr;\n \n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_region_enter_printf(\"index\", \"do_read_index\", the_repository,\n+\t\t\t\t   \"%s\", path);\n \ttrace_performance_enter();\n \tret = do_read_index(istate, path, 0);\n \ttrace_performance_leave(\"read cache %s\", path);\n+\ttrace2_region_leave_printf(\"index\", \"do_read_index\", the_repository,\n+\t\t\t\t   \"%s\", path);\n \n \tsplit_index = istate->split_index;\n \tif (!split_index || is_null_oid(&split_index->base_oid)) {\n@@ -2281,7 +2297,11 @@ int read_index_from(struct index_state *istate, const char *path,\n \n \tbase_oid_hex = oid_to_hex(&split_index->base_oid);\n \tbase_path = xstrfmt(\"%s/sharedindex.%s\", gitdir, base_oid_hex);\n+\ttrace2_region_enter_printf(\"index\", \"shared/do_read_index\", the_repository,\n+\t\t\t\t   \"%s\", base_path);\n \tret = do_read_index(split_index->base, base_path, 1);\n+\ttrace2_region_leave_printf(\"index\", \"shared/do_read_index\", the_repository,\n+\t\t\t\t   \"%s\", base_path);\n \tif (!oideq(&split_index->base_oid, &split_index->base->oid))\n \t\tdie(_(\"broken index, expect %s in %s, got %s\"),\n \t\t    base_oid_hex, base_path,\n@@ -2988,6 +3008,14 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \tistate->timestamp.sec = (unsigned int)st.st_mtime;\n \tistate->timestamp.nsec = ST_MTIME_NSEC(st);\n \ttrace_performance_since(start, \"write index, changed mask = %x\", istate->cache_changed);\n+\n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_data_intmax(\"index\", the_repository, \"write/version\", istate->version);\n+\ttrace2_data_intmax(\"index\", the_repository, \"write/cache_nr\", istate->cache_nr);\n+\n \treturn 0;\n }\n \n@@ -3007,7 +3035,18 @@ static int commit_locked_index(struct lock_file *lk)\n static int do_write_locked_index(struct index_state *istate, struct lock_file *lock,\n \t\t\t\t unsigned flags)\n {\n-\tint ret = do_write_index(istate, lock->tempfile, 0);\n+\tint ret;\n+\n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_region_enter_printf(\"index\", \"do_write_index\", the_repository,\n+\t\t\t\t   \"%s\", lock->tempfile->filename.buf);\n+\tret = do_write_index(istate, lock->tempfile, 0);\n+\ttrace2_region_leave_printf(\"index\", \"do_write_index\", the_repository,\n+\t\t\t\t   \"%s\", lock->tempfile->filename.buf);\n+\n \tif (ret)\n \t\treturn ret;\n \tif (flags & COMMIT_LOCK)\n@@ -3092,7 +3131,13 @@ static int write_shared_index(struct index_state *istate,\n \tint ret;\n \n \tmove_cache_to_base_index(istate);\n+\n+\ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n+\t\t\t\t   the_repository, \"%s\", (*temp)->filename.buf);\n \tret = do_write_index(si->base, *temp, 1);\n+\ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n+\t\t\t\t   the_repository, \"%s\", (*temp)->filename.buf);\n+\n \tif (ret)\n \t\treturn ret;\n \tret = adjust_shared_perm(get_tempfile_path(*temp));\n-- \ngitgitgadget\n\n"},{"id":"367875","messageId":"9abbdf9ccda4526672523e1a4a2f6dcaa01758b8.1548712060.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 12/14] trace2:data: add subverb to reset command","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-28T21:47:50Z","receivedAt":"2019-01-28T21:47:55Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/reset.c | 6 ++++++\n 1 file changed, 6 insertions(+)\n\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex 59898c972e..b65b4a66db 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -340,6 +340,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \tif (patch_mode) {\n \t\tif (reset_type != NONE)\n \t\t\tdie(_(\"--patch is incompatible with --{hard,mixed,soft}\"));\n+\t\ttrace2_cmd_subverb(\"patch-interactive\");\n \t\treturn run_add_interactive(rev, \"--patch=reset\", &pathspec);\n \t}\n \n@@ -356,6 +357,11 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \tif (reset_type == NONE)\n \t\treset_type = MIXED; /* by default */\n \n+\tif (pathspec.nr)\n+\t\ttrace2_cmd_subverb(\"path\");\n+\telse\n+\t\ttrace2_cmd_subverb(reset_type_names[reset_type]);\n+\n \tif (reset_type != SOFT && (reset_type != MIXED || get_git_work_tree()))\n \t\tsetup_work_tree();\n \n-- \ngitgitgadget\n\n"},{"id":"367876","messageId":"86feec03e2e222657c832870f2ff34290b4d870e.1548712060.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 11/14] trace2:data: add subverb to checkout command","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-28T21:47:49Z","receivedAt":"2019-01-28T21:47:55Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/checkout.c | 7 +++++++\n 1 file changed, 7 insertions(+)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 6fadf412e8..8939ae99ed 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -262,6 +262,8 @@ static int checkout_paths(const struct checkout_opts *opts,\n \tstruct lock_file lock_file = LOCK_INIT;\n \tint nr_checkouts = 0;\n \n+\ttrace2_cmd_subverb(opts->patch_mode ? \"patch\" : \"path\");\n+\n \tif (opts->track != BRANCH_TRACK_UNSPECIFIED)\n \t\tdie(_(\"'%s' cannot be used with updating paths\"), \"--track\");\n \n@@ -952,6 +954,9 @@ static int switch_branches(const struct checkout_opts *opts,\n \tvoid *path_to_free;\n \tstruct object_id rev;\n \tint flag, writeout_error = 0;\n+\n+\ttrace2_cmd_subverb(\"branch\");\n+\n \tmemset(&old_branch_info, 0, sizeof(old_branch_info));\n \told_branch_info.path = path_to_free = resolve_refdup(\"HEAD\", 0, &rev, &flag);\n \tif (old_branch_info.path)\n@@ -1189,6 +1194,8 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)\n \tint status;\n \tstruct strbuf branch_ref = STRBUF_INIT;\n \n+\ttrace2_cmd_subverb(\"unborn\");\n+\n \tif (!opts->new_branch)\n \t\tdie(_(\"You are on a branch yet to be born\"));\n \tstrbuf_addf(&branch_ref, \"refs/heads/%s\", opts->new_branch);\n-- \ngitgitgadget\n\n"},{"id":"367877","messageId":"06ccce9632398558e4f58607adce3876a8b4fac5.1548712060.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 13/14] trace2:data: add subverb for rebase","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-28T21:47:51Z","receivedAt":"2019-01-28T21:47:57Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/rebase.c | 19 +++++++++++++++++++\n 1 file changed, 19 insertions(+)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 00de70365e..aac0d52ade 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -819,6 +819,16 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tACTION_EDIT_TODO,\n \t\tACTION_SHOW_CURRENT_PATCH,\n \t} action = NO_ACTION;\n+\tstatic const char *action_names[] = {\n+\t\tN_(\"undefined\"),\n+\t\tN_(\"continue\"),\n+\t\tN_(\"skip\"),\n+\t\tN_(\"abort\"),\n+\t\tN_(\"quit\"),\n+\t\tN_(\"edit_todo\"),\n+\t\tN_(\"show_current_patch\"),\n+\t\tNULL\n+\t};\n \tconst char *gpg_sign = NULL;\n \tstruct string_list exec = STRING_LIST_INIT_NODUP;\n \tconst char *rebase_merges = NULL;\n@@ -1001,6 +1011,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tdie(_(\"The --edit-todo action can only be used during \"\n \t\t      \"interactive rebase.\"));\n \n+\tif (trace2_is_enabled()) {\n+\t\tif (is_interactive(&options))\n+\t\t\ttrace2_cmd_subverb(\"interactive\");\n+\t\telse if (exec.nr)\n+\t\t\ttrace2_cmd_subverb(\"interactive-exec\");\n+\t\telse\n+\t\t\ttrace2_cmd_subverb(action_names[action]);\n+\t}\n+\n \tswitch (action) {\n \tcase ACTION_CONTINUE: {\n \t\tstruct object_id head;\n-- \ngitgitgadget\n\n"},{"id":"367878","messageId":"851aa8f34d7223a3d872033abf2f2a95517b3b23.1548712060.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 14/14] trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-28T21:47:52Z","receivedAt":"2019-01-28T21:47:59Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate unit tests for Trace2.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                  |   1 +\n t/helper/test-tool.c      |   1 +\n t/helper/test-tool.h      |   1 +\n t/helper/test-trace2.c    | 273 ++++++++++++++++++++++++++++++++++++++\n t/t0210-trace2-normal.sh  | 147 ++++++++++++++++++++\n t/t0210/scrub_normal.perl |  48 +++++++\n t/t0211-trace2-perf.sh    | 163 +++++++++++++++++++++++\n t/t0211/scrub_perf.perl   |  76 +++++++++++\n t/t0212-trace2-event.sh   | 246 ++++++++++++++++++++++++++++++++++\n t/t0212/parse_events.perl | 251 +++++++++++++++++++++++++++++++++++\n 10 files changed, 1207 insertions(+)\n create mode 100644 t/helper/test-trace2.c\n create mode 100755 t/t0210-trace2-normal.sh\n create mode 100644 t/t0210/scrub_normal.perl\n create mode 100755 t/t0211-trace2-perf.sh\n create mode 100644 t/t0211/scrub_perf.perl\n create mode 100755 t/t0212-trace2-event.sh\n create mode 100644 t/t0212/parse_events.perl\n\ndiff --git a/Makefile b/Makefile\nindex 5d4e993cdc..8100364023 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -753,6 +753,7 @@ TEST_BUILTINS_OBJS += test-string-list.o\n TEST_BUILTINS_OBJS += test-submodule-config.o\n TEST_BUILTINS_OBJS += test-submodule-nested-repo-config.o\n TEST_BUILTINS_OBJS += test-subprocess.o\n+TEST_BUILTINS_OBJS += test-trace2.o\n TEST_BUILTINS_OBJS += test-urlmatch-normalization.o\n TEST_BUILTINS_OBJS += test-wildmatch.o\n TEST_BUILTINS_OBJS += test-windows-named-pipe.o\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 85d1f812fe..3d591c00ac 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -49,6 +49,7 @@ static struct test_cmd cmds[] = {\n \t{ \"submodule-config\", cmd__submodule_config },\n \t{ \"submodule-nested-repo-config\", cmd__submodule_nested_repo_config },\n \t{ \"subprocess\", cmd__subprocess },\n+\t{ \"trace2\", cmd__trace2 },\n \t{ \"urlmatch-normalization\", cmd__urlmatch_normalization },\n \t{ \"wildmatch\", cmd__wildmatch },\n #ifdef GIT_WINDOWS_NATIVE\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 042f12464b..226b86aca9 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -44,6 +44,7 @@ int cmd__string_list(int argc, const char **argv);\n int cmd__submodule_config(int argc, const char **argv);\n int cmd__submodule_nested_repo_config(int argc, const char **argv);\n int cmd__subprocess(int argc, const char **argv);\n+int cmd__trace2(int argc, const char **argv);\n int cmd__urlmatch_normalization(int argc, const char **argv);\n int cmd__wildmatch(int argc, const char **argv);\n #ifdef GIT_WINDOWS_NATIVE\ndiff --git a/t/helper/test-trace2.c b/t/helper/test-trace2.c\nnew file mode 100644\nindex 0000000000..b867f58d32\n--- /dev/null\n+++ b/t/helper/test-trace2.c\n@@ -0,0 +1,273 @@\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"argv-array.h\"\n+#include \"run-command.h\"\n+#include \"exec-cmd.h\"\n+#include \"config.h\"\n+\n+typedef int (fn_unit_test)(int argc, const char **argv);\n+\n+struct unit_test \n+{\n+\tfn_unit_test *ut_fn;\n+\tconst char   *ut_name;\n+\tconst char   *ut_usage;\n+};\n+\n+#define MyOk    0\n+#define MyError 1\n+\n+static int get_i(int *p_value, const char *data)\n+{\n+\tchar *endptr;\n+\n+\tif (!data || !*data)\n+\t\treturn MyError;\n+\n+\t*p_value = strtol(data, &endptr, 10);\n+\tif (*endptr || errno == ERANGE)\n+\t\treturn MyError;\n+\n+\treturn MyOk;\n+}\n+\n+/*\n+ * Cause process to exit with the requested value via \"return\".\n+ *\n+ * Rely on test-tool.c:cmd_main() to call trace2_cmd_exit()\n+ * with our result.\n+ *\n+ * Test harness can confirm:\n+ * [] the process-exit value.\n+ * [] the \"code\" field in the \"exit\" trace2 event.\n+ * [] the \"code\" field in the \"atexit\" trace2 event.\n+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n+ * [] \"def_param\" events for all of the \"interesting\" pre-defined config settings.\n+ */\n+static int ut_001return(int argc, const char **argv)\n+{\n+\tint rc;\n+\n+\tif (get_i(&rc, argv[0]))\n+\t\tdie(\"expect <exit_code>\");\n+\n+\treturn rc;\n+}\n+\n+/*\n+ * Cause the process to exit with the requested value via \"exit()\".\n+ *\n+ * Test harness can confirm:\n+ * [] the \"code\" field in the \"exit\" trace2 event.\n+ * [] the \"code\" field in the \"atexit\" trace2 event.\n+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n+ * [] \"def_param\" events for all of the \"interesting\" pre-defined config settings.\n+ */\n+static int ut_002exit(int argc, const char **argv)\n+{\n+\tint rc;\n+\n+\tif (get_i(&rc, argv[0]))\n+\t\tdie(\"expect <exit_code>\");\n+\n+\texit(rc);\n+}\n+\n+/*\n+ * Send an \"error\" event with each value in argv.  Normally, git only issues\n+ * a single \"error\" event immediately before issuing an \"exit\" event (such\n+ * as in die() or BUG()), but multiple \"error\" events are allowed.\n+ *\n+ * Test harness can confirm:\n+ * [] a trace2 \"error\" event for each value in argv.\n+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n+ * [] (optional) the file:line in the \"exit\" event refers to this function.\n+ */\n+static int ut_003error(int argc, const char **argv)\n+{\n+\tint k;\n+\n+\tif (!argv[0] || !*argv[0])\n+\t\tdie(\"expect <error_message>\");\n+\n+\tfor (k = 0; k < argc; k++)\n+\t\terror(\"%s\", argv[k]);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Run a child process and wait for it to finish and exit with its return code.\n+ * test-tool trace2 004child [<child-command-line>]\n+ *\n+ * For example:\n+ * test-tool trace2 004child git version\n+ * test-tool trace2 004child test-tool trace2 001return 0\n+ * test-tool trace2 004child test-tool trace2 004child test-tool trace2 004child\n+ * test-tool trace2 004child git -c alias.xyz=version xyz\n+ *\n+ * Test harness can confirm:\n+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n+ * [] that the outer process has a single component SID (or depth \"d0\" in\n+ *    the PERF stream).\n+ * [] that \"child_start\" and \"child_exit\" events are generated for the child.\n+ * [] if the child process is an instrumented executable:\n+ *    [] that \"version\", \"start\", ..., \"exit\", and \"atexit\" events are\n+ *       generated by the child process.\n+ *    [] that the child process events have a multiple component SID (or\n+ *       depth \"dN+1\" in the PERF stream).\n+ * [] that the child exit code is propagated to the parent process \"exit\"\n+ *    and \"atexit\" events..\n+ * [] (optional) that the \"t_abs\" field in the child process \"atexit\" event\n+ *    is less than the \"t_rel\" field in the \"child_exit\" event of the parent\n+ *    process.\n+ * [] if the child process is like the alias example above,\n+ *    [] (optional) the child process attempts to run \"git-xyx\" as a dashed\n+ *       command.\n+ *    [] the child process emits an \"alias\" event with \"xyz\" => \"version\"\n+ *    [] the child process runs \"git version\" as a child process.\n+ *    [] the child process has a 3 component SID (or depth \"d2\" in the PERF\n+ *       stream).\n+ */\n+static int ut_004child(int argc, const char **argv)\n+{\n+\tint result;\n+\n+\t/*\n+\t * Allow empty <child_command_line> so we can do arbitrarily deep\n+\t * command nesting and let the last one be null.\n+\t */\n+\tif (!argc)\n+\t\treturn 0;\n+\n+\tresult = run_command_v_opt(argv, 0);\n+\texit(result);\n+}\n+\n+/*\n+ * Exec a git command.  This may either create a child process (Windows)\n+ * or replace the existing process.\n+ * test-tool trace2 005exec <git_command_args>\n+ *\n+ * For example:\n+ * test-tool trace2 005exec version\n+ *\n+ * Test harness can confirm (on Windows):\n+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n+ * [] that the outer process has a single component SID (or depth \"d0\" in\n+ *    the PERF stream).\n+ * [] that \"exec\" and \"exec_result\" events are generated for the child\n+ *    process (since the Windows compatibility layer fakes an exec() with\n+ *    a CreateProcess(), WaitForSingleObject(), and exit()).\n+ * [] that the child process has multiple component SID (or depth \"dN+1\"\n+ *    in the PERF stream).\n+ *\n+ * Test harness can confirm (on platforms with a real exec() function):\n+ * [] TODO talk about process replacement and how it affects SID.\n+ */ \n+static int ut_005exec(int argc, const char **argv)\n+{\n+\tint result;\n+\n+\tif (!argc)\n+\t\treturn 0;\n+\n+\tresult = execv_git_cmd(argv);\n+\treturn result;\n+}\n+\n+static int ut_006data(int argc, const char **argv)\n+{\n+\tconst char *usage_error =\n+\t\t\"expect <cat0> <k0> <v0> [<cat1> <k1> <v1> [...]]\";\n+\n+\tif (argc % 3 != 0)\n+\t\tdie(\"%s\", usage_error);\n+\n+\twhile (argc) {\n+\t\tif (!argv[0] || !*argv[0] ||\n+\t\t    !argv[1] || !*argv[1] ||\n+\t\t    !argv[2] || !*argv[2])\n+\t\t\tdie(\"%s\", usage_error);\n+\n+\t\ttrace2_data_string(argv[0], the_repository, argv[1], argv[2]);\n+\t\targv += 3;\n+\t\targc -= 3;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Usage:\n+ *     test-tool trace2 <ut_name_1> <ut_usage_1>\n+ *     test-tool trace2 <ut_name_2> <ut_usage_2>\n+ *     ...\n+ */ \n+#define USAGE_PREFIX \"test-tool trace2\"\n+\n+static struct unit_test ut_table[] = {\n+\t{ ut_001return,   \"001return\", \"<exit_code>\" },\n+\t{ ut_002exit,     \"002exit\",   \"<exit_code>\" },\n+\t{ ut_003error,    \"003error\",  \"<error_message>+\" },\n+\t{ ut_004child,    \"004child\",  \"[<child_command_line>]\" },\n+\t{ ut_005exec,     \"005exec\",   \"<git_command_args>\" },\n+\t{ ut_006data,     \"006data\",   \"[<category> <key> <value>]+\" },\n+};\n+\n+#define for_each_ut(k, ut_k)\t\t\t\\\n+\tfor (k = 0, ut_k = &ut_table[k];\t\\\n+\t     k < ARRAY_SIZE(ut_table);\t\t\\\n+\t     k++, ut_k = &ut_table[k])\n+\n+static int print_usage(void)\n+{\n+\tint k;\n+\tstruct unit_test *ut_k;\n+\n+\tfprintf(stderr, \"usage:\\n\");\n+\tfor_each_ut(k, ut_k) {\n+\t\tfprintf(stderr, \"\\t%s %s %s\\n\",\n+\t\t\tUSAGE_PREFIX, ut_k->ut_name, ut_k->ut_usage);\n+\t}\n+\n+\treturn 129;\n+}\n+\n+/*\n+ * Issue various trace2 events for testing.\n+ *\n+ * We assume that these trace2 routines has already been called:\n+ *    [] trace2_initialize()      [common-main.c:main()]\n+ *    [] trace2_cmd_start()       [common-main.c:main()]\n+ *    [] trace2_cmd_verb()        [test-tool.c:cmd_main()]\n+ *    [] tracd2_cmd_list_config() [test-tool.c:cmd_main()]\n+ * So that:\n+ *    [] the various trace2 streams are open.\n+ *    [] the process SID has been created.\n+ *    [] the \"version\" event has been generated.\n+ *    [] the \"start\" event has been generated.\n+ *    [] the \"verb\" event has been generated.\n+ *    [] this writes various \"def_param\" events for interesting config values.\n+ *\n+ * We further assume that if we return (rather than exit()), trace2_cmd_exit()\n+ * will be called by test-tool.c:cmd_main().\n+ */\n+int cmd__trace2(int argc, const char **argv)\n+{\n+\tint k;\n+\tstruct unit_test *ut_k;\n+\n+\targc--;\t/* skip over \"trace2\" arg */\n+\targv++;\n+\n+\tif (argc) {\n+\t\tfor_each_ut(k, ut_k) {\n+\t\t\tif (!strcmp(argv[0], ut_k->ut_name))\n+\t\t\t\treturn ut_k->ut_fn(argc - 1, argv + 1);\n+\t\t}\n+\t}\n+\n+\treturn print_usage();\n+}\n+\ndiff --git a/t/t0210-trace2-normal.sh b/t/t0210-trace2-normal.sh\nnew file mode 100755\nindex 0000000000..f5790d565e\n--- /dev/null\n+++ b/t/t0210-trace2-normal.sh\n@@ -0,0 +1,147 @@\n+#!/bin/sh\n+\n+test_description='test trace2 facility (normal target)'\n+. ./test-lib.sh\n+\n+# Add t/helper directory to PATH so that we can use a relative\n+# path to run nested instances of test-tool.exe (see 004child).\n+# This helps with HEREDOC comparisons later.\n+TTDIR=\"$GIT_BUILD_DIR/t/helper/\" && export TTDIR\n+PATH=\"$TTDIR:$PATH\" && export PATH\n+\n+TT=\"test-tool\" && export TT\n+\n+# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe\n+# Warning: to do the actual diff/comparison, so the HEREDOCs here\n+# Warning: only cover our actual calls to test-tool and/or git.\n+# Warning: So you may see extra lines in artifact files when\n+# Warning: interactively debugging.\n+\n+# Turn off any inherited trace2 settings for this test.\n+unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT\n+unset GIT_TR2_BRIEF\n+unset GIT_TR2_CONFIG_PARAMS\n+\n+V=$(git version | sed -e 's/^git version //') && export V\n+\n+# There are multiple trace2 targets: normal, perf, and event.\n+# Trace2 events will/can be written to each active target (subject\n+# to whatever filtering that target decides to do).\n+# This script tests the normal target in isolation.\n+#\n+# Defer setting GIT_TR2 until the actual command line we want to test\n+# because hidden git and test-tool commands run by the test harness\n+# can contaminate our output.\n+\n+# Enable \"brief\" feature which turns off \"<clock> <file>:<line> \" prefix.\n+GIT_TR2_BRIEF=1 && export GIT_TR2_BRIEF\n+\n+# Basic tests of the trace2 normal stream.  Since this stream is used\n+# primarily with printf-style debugging/tracing, we do limited testing\n+# here.\n+#\n+# We do confirm the following API features:\n+# [] the 'version <v>' event\n+# [] the 'start <argv>' event\n+# [] the 'cmd_verb <verb>' event\n+# [] the 'exit <time> code:<code>' event\n+# [] the 'atexit <time> code:<code>' event\n+#\n+# Fields of the form _FIELD_ are tokens that have been replaced (such\n+# as the elapsed time).\n+\n+# Verb 001return\n+#\n+# Implicit return from cmd_<verb> function propagates <code>.\n+\n+test_expect_success 'normal stream, return code 0' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\tGIT_TR2=\"$(pwd)/trace.normal\" && export GIT_TR2 &&\n+\t$TT trace2 001return 0 &&\n+\tunset GIT_TR2 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 001return 0\n+\t\tcmd_verb trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:0\n+\t\tatexit elapsed:_TIME_ code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'normal stream, return code 1' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\tGIT_TR2=\"$(pwd)/trace.normal\" && export GIT_TR2 &&\n+\ttest_must_fail $TT trace2 001return 1 &&\n+\tunset GIT_TR2 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 001return 1\n+\t\tcmd_verb trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:1\n+\t\tatexit elapsed:_TIME_ code:1\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 002exit\n+#\n+# Explicit exit(code) from within cmd_<verb> propagates <code>.\n+\n+test_expect_success 'normal stream, exit code 0' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\tGIT_TR2=\"$(pwd)/trace.normal\" && export GIT_TR2 &&\n+\t$TT trace2 002exit 0 &&\n+\tunset GIT_TR2 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 002exit 0\n+\t\tcmd_verb trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:0\n+\t\tatexit elapsed:_TIME_ code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'normal stream, exit code 1' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\tGIT_TR2=\"$(pwd)/trace.normal\" && export GIT_TR2 &&\n+\ttest_must_fail $TT trace2 002exit 1 &&\n+\tunset GIT_TR2 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 002exit 1\n+\t\tcmd_verb trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:1\n+\t\tatexit elapsed:_TIME_ code:1\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 003error\n+#\n+# To the above, add multiple 'error <msg>' events\n+\n+test_expect_success 'normal stream, error event' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\tGIT_TR2=\"$(pwd)/trace.normal\" && export GIT_TR2 &&\n+\t$TT trace2 003error \"hello world\" \"this is a test\" &&\n+\tunset GIT_TR2 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 003error '\\''hello world'\\'' '\\''this is a test'\\''\n+\t\tcmd_verb trace2 (trace2)\n+\t\terror hello world\n+\t\terror this is a test\n+\t\texit elapsed:_TIME_ code:0\n+\t\tatexit elapsed:_TIME_ code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t0210/scrub_normal.perl b/t/t0210/scrub_normal.perl\nnew file mode 100644\nindex 0000000000..c65d1a815e\n--- /dev/null\n+++ b/t/t0210/scrub_normal.perl\n@@ -0,0 +1,48 @@\n+#!/usr/bin/perl\n+#\n+# Scrub the variable fields from the normal trace2 output to\n+# make testing easier.\n+\n+use strict;\n+use warnings;\n+\n+my $float = '[0-9]*\\.[0-9]+([eE][-+]?[0-9]+)?';\n+\n+# This code assumes that the trace2 data was written with bare\n+# turned on (which omits the \"<clock> <file>:<line>\" prefix.\n+\n+while (<>) {\n+    # Various messages include an elapsed time in the middle\n+    # of the message.  Replace the time with a placeholder to\n+    # simplify our HEREDOC in the test script.\n+    s/elapsed:$float/elapsed:_TIME_/g;\n+\n+    my $line = $_;\n+\n+    # we expect:\n+    #    start <argv0> [<argv1> [<argv2> [...]]]\n+    #\n+    # where argv0 might be a relative or absolute path, with\n+    # or without quotes, and platform dependent.  Replace argv0\n+    # with a token for HEREDOC matching in the test script.\n+\n+    if ($line =~ m/^start/) {\n+\t$line =~ /^start\\s+(.*)/;\n+\tmy $argv = $1;\n+\t$argv =~ m/(\\'[^\\']*\\'|[^ ]+)\\s+(.*)/;\n+\tmy $argv_0 = $1;\n+\tmy $argv_rest = $2;\n+\n+\tprint \"start _EXE_ $argv_rest\\n\";\n+    }\n+    elsif ($line =~ m/^cmd_path/) {\n+\t# Likewise, the 'cmd_path' message breaks out argv[0].\n+\t#\n+\t# This line is only emitted when RUNTIME_PREFIX is defined,\n+\t# so just omit it for testing purposes.\n+\t# print \"cmd_path _EXE_\\n\";\n+    }\n+    else {\n+\tprint \"$line\";\n+    }\n+}\ndiff --git a/t/t0211-trace2-perf.sh b/t/t0211-trace2-perf.sh\nnew file mode 100755\nindex 0000000000..b24104557c\n--- /dev/null\n+++ b/t/t0211-trace2-perf.sh\n@@ -0,0 +1,163 @@\n+#!/bin/sh\n+\n+test_description='test trace2 facility (perf target)'\n+. ./test-lib.sh\n+\n+# Add t/helper directory to PATH so that we can use a relative\n+# path to run nested instances of test-tool.exe (see 004child).\n+# This helps with HEREDOC comparisons later.\n+TTDIR=\"$GIT_BUILD_DIR/t/helper/\" && export TTDIR\n+PATH=\"$TTDIR:$PATH\" && export PATH\n+\n+TT=\"test-tool\" && export TT\n+\n+# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe\n+# Warning: to do the actual diff/comparison, so the HEREDOCs here\n+# Warning: only cover our actual calls to test-tool and/or git.\n+# Warning: So you may see extra lines in artifact files when\n+# Warning: interactively debugging.\n+\n+# Turn off any inherited trace2 settings for this test.\n+unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT\n+unset GIT_TR2_PERF_BRIEF\n+unset GIT_TR2_CONFIG_PARAMS\n+\n+V=$(git version | sed -e 's/^git version //') && export V\n+\n+# There are multiple trace2 targets: normal, perf, and event.\n+# Trace2 events will/can be written to each active target (subject\n+# to whatever filtering that target decides to do).\n+# Test each target independently.\n+#\n+# Defer setting GIT_TR2_PERF until the actual command we want to\n+# test because hidden git and test-tool commands in the test\n+# harness can contaminate our output.\n+\n+# Enable \"brief\" feature which turns off the prefix:\n+#     \"<clock> <file>:<line> | <nr_parents> | \"\n+GIT_TR2_PERF_BRIEF=1 && export GIT_TR2_PERF_BRIEF\n+\n+# Repeat some of the t0210 tests using the perf target stream instead of\n+# the normal stream.\n+#\n+# Tokens here of the form _FIELD_ have been replaced in the observed output.\n+\n+# Verb 001return\n+#\n+# Implicit return from cmd_<verb> function propagates <code>.\n+\n+test_expect_success 'perf stream, return code 0' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" && export GIT_TR2_PERF &&\n+\t$TT trace2 001return 0 &&\n+\tunset GIT_TR2_PERF &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 001return 0\n+\t\td0|main|cmd_verb|||||trace2 (trace2)\n+\t\td0|main|exit||_T_ABS_|||code:0\n+\t\td0|main|atexit||_T_ABS_|||code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'perf stream, return code 1' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" && export GIT_TR2_PERF &&\n+\ttest_must_fail $TT trace2 001return 1 &&\n+\tunset GIT_TR2_PERF &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 001return 1\n+\t\td0|main|cmd_verb|||||trace2 (trace2)\n+\t\td0|main|exit||_T_ABS_|||code:1\n+\t\td0|main|atexit||_T_ABS_|||code:1\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 003error\n+#\n+# To the above, add multiple 'error <msg>' events\n+\n+test_expect_success 'perf stream, error event' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" && export GIT_TR2_PERF &&\n+\t$TT trace2 003error \"hello world\" \"this is a test\" &&\n+\tunset GIT_TR2_PERF &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 003error '\\''hello world'\\'' '\\''this is a test'\\''\n+\t\td0|main|cmd_verb|||||trace2 (trace2)\n+\t\td0|main|error|||||hello world\n+\t\td0|main|error|||||this is a test\n+\t\td0|main|exit||_T_ABS_|||code:0\n+\t\td0|main|atexit||_T_ABS_|||code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 004child\n+#\n+# Test nested spawning of child processes.\n+#\n+# Conceptually, this looks like:\n+#    P1: TT trace2 004child\n+#    P2: |--- TT trace2 004child\n+#    P3:      |--- TT trace2 001return 0\n+#\n+# Which should generate events:\n+#    P1: version\n+#    P1: start\n+#    P1: cmd_verb\n+#    P1: child_start\n+#        P2: version\n+#        P2: start\n+#        P2: cmd_verb\n+#        P2: child_start\n+#            P3: version\n+#            P3: start\n+#            P3: cmd_verb\n+#            P3: exit\n+#            P3: atexit\n+#        P2: child_exit\n+#        P2: exit\n+#        P2: atexit\n+#    P1: child_exit\n+#    P1: exit\n+#    P1: atexit\n+\n+test_expect_success 'perf stream, child processes' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" && export GIT_TR2_PERF &&\n+\t$TT trace2 004child $TT trace2 004child $TT trace2 001return 0 &&\n+\tunset GIT_TR2_PERF &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 004child $TT trace2 004child $TT trace2 001return 0\n+\t\td0|main|cmd_verb|||||trace2 (trace2)\n+\t\td0|main|child_start||_T_ABS_|||[ch0] class:? argv: $TT trace2 004child $TT trace2 001return 0\n+\t\td1|main|version|||||$V\n+\t\td1|main|start|||||_EXE_ trace2 004child $TT trace2 001return 0\n+\t\td1|main|cmd_verb|||||trace2 (trace2/trace2)\n+\t\td1|main|child_start||_T_ABS_|||[ch0] class:? argv: $TT trace2 001return 0\n+\t\td2|main|version|||||$V\n+\t\td2|main|start|||||_EXE_ trace2 001return 0\n+\t\td2|main|cmd_verb|||||trace2 (trace2/trace2/trace2)\n+\t\td2|main|exit||_T_ABS_|||code:0\n+\t\td2|main|atexit||_T_ABS_|||code:0\n+\t\td1|main|child_exit||_T_ABS_|_T_REL_||[ch0] pid:_PID_ code:0\n+\t\td1|main|exit||_T_ABS_|||code:0\n+\t\td1|main|atexit||_T_ABS_|||code:0\n+\t\td0|main|child_exit||_T_ABS_|_T_REL_||[ch0] pid:_PID_ code:0\n+\t\td0|main|exit||_T_ABS_|||code:0\n+\t\td0|main|atexit||_T_ABS_|||code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t0211/scrub_perf.perl b/t/t0211/scrub_perf.perl\nnew file mode 100644\nindex 0000000000..351af7844e\n--- /dev/null\n+++ b/t/t0211/scrub_perf.perl\n@@ -0,0 +1,76 @@\n+#!/usr/bin/perl\n+#\n+# Scrub the variable fields from the perf trace2 output to\n+# make testing easier.\n+\n+use strict;\n+use warnings;\n+\n+my $qpath = '\\'[^\\']*\\'|[^ ]*';\n+\n+my $col_depth=0;\n+my $col_thread=1;\n+my $col_event=2;\n+my $col_repo=3;\n+my $col_t_abs=4;\n+my $col_t_rel=5;\n+my $col_category=6;\n+my $col_rest=7;\n+\n+# This code assumes that the trace2 data was written with bare\n+# turned on (which omits the \"<clock> <file>:<line> | <parents>\"\n+# prefix.\n+\n+while (<>) {\n+    my @tokens = split /\\|/;\n+\n+    foreach my $col (@tokens) { $col =~ s/^\\s+|\\s+$//g; }\n+\n+    if ($tokens[$col_event] =~ m/^start/) {\n+\t# The 'start' message lists the contents of argv in $col_rest.\n+\t# On some platforms (Windows), argv[0] is *sometimes* a canonical\n+\t# absolute path to the EXE rather than the value passed in the\n+\t# shell script.  Replace it with a placeholder to simplify our\n+\t# HEREDOC in the test script.\n+\tmy $argv0;\n+\tmy $argvRest;\n+\t$tokens[$col_rest] =~ s/^($qpath)\\W*(.*)/_EXE_ $2/;\n+    }\n+    elsif ($tokens[$col_event] =~ m/cmd_path/) {\n+\t# Likewise, the 'cmd_path' message breaks out argv[0].\n+\t#\n+\t# This line is only emitted when RUNTIME_PREFIX is defined,\n+\t# so just omit it for testing purposes.\n+\t# $tokens[$col_rest] = \"_EXE_\";\n+\tgoto SKIP_LINE;\n+    }\n+    elsif ($tokens[$col_event] =~ m/child_exit/) {\n+\t$tokens[$col_rest] =~ s/ pid:\\d* / pid:_PID_ /;\n+    }\n+    elsif ($tokens[$col_event] =~ m/data/) {\n+\tif ($tokens[$col_category] =~ m/process/) {\n+\t    # 'data' and 'data_json' events containing 'process'\n+\t    # category data are assumed to be platform-specific\n+\t    # and highly variable.  Just omit them.\n+\t    goto SKIP_LINE;\n+\t}\n+    }\n+\n+    # t_abs and t_rel are either blank or a float.  Replace the float\n+    # with a constant for matching the HEREDOC in the test script.\n+    if ($tokens[$col_t_abs] =~ m/\\d/) {\n+\t$tokens[$col_t_abs] = \"_T_ABS_\";\n+    }\n+    if ($tokens[$col_t_rel] =~ m/\\d/) {\n+\t$tokens[$col_t_rel] = \"_T_REL_\";\n+    }\n+\n+    my $out;\n+\n+    $out = join('|', @tokens);\n+    print \"$out\\n\";\n+\n+  SKIP_LINE:\n+}\n+\n+\ndiff --git a/t/t0212-trace2-event.sh b/t/t0212-trace2-event.sh\nnew file mode 100755\nindex 0000000000..995bc5e1b3\n--- /dev/null\n+++ b/t/t0212-trace2-event.sh\n@@ -0,0 +1,246 @@\n+#!/bin/sh\n+\n+test_description='test trace2 facility'\n+. ./test-lib.sh\n+\n+# Add t/helper directory to PATH so that we can use a relative\n+# path to run nested instances of test-tool.exe (see 004child).\n+# This helps with HEREDOC comparisons later.\n+TTDIR=\"$GIT_BUILD_DIR/t/helper/\" && export TTDIR\n+PATH=\"$TTDIR:$PATH\" && export PATH\n+\n+TT=\"test-tool\" && export TT\n+\n+# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe\n+# Warning: to do the actual diff/comparison, so the HEREDOCs here\n+# Warning: only cover our actual calls to test-tool and/or git.\n+# Warning: So you may see extra lines in artifact files when\n+# Warning: interactively debugging.\n+\n+# Turn off any inherited trace2 settings for this test.\n+unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT\n+unset GIT_TR2_BARE\n+unset GIT_TR2_CONFIG_PARAMS\n+\n+V=$(git version | sed -e 's/^git version //') && export V\n+\n+# There are multiple trace2 targets: normal, perf, and event.\n+# Trace2 events will/can be written to each active target (subject\n+# to whatever filtering that target decides to do).\n+# Test each target independently.\n+#\n+# Defer setting GIT_TR2_PERF until the actual command we want to\n+# test because hidden git and test-tool commands in the test\n+# harness can contaminate our output.\n+\n+# We don't bother repeating the 001return and 002exit tests, since they\n+# have coverage in the normal and perf targets.\n+\n+# Verb 003error\n+#\n+# To the above, add multiple 'error <msg>' events\n+\n+test_expect_success 'event stream, error event' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" && export GIT_TR2_EVENT &&\n+\t$TT trace2 003error \"hello world\" \"this is a test\" &&\n+\tunset GIT_TR2_EVENT &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"003error\",\n+\t|      \"hello world\",\n+\t|      \"this is a test\"\n+\t|    ],\n+\t|    \"errors\":[\n+\t|      \"%s\",\n+\t|      \"%s\"\n+\t|    ],\n+\t|    \"exit_code\":0,\n+\t|    \"verb\":\"trace2\",\n+\t|    \"verb_hierarchy\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 004child\n+#\n+# Test nested spawning of child processes.\n+#\n+# Conceptually, this looks like:\n+#    P1: TT trace2 004child\n+#    P2: |--- TT trace2 004child\n+#    P3:      |--- TT trace2 001return 0\n+\n+test_expect_success 'event stream, return code 0' '\n+#\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" && export GIT_TR2_EVENT &&\n+\t$TT trace2 004child $TT trace2 004child $TT trace2 001return 0 &&\n+\tunset GIT_TR2_EVENT &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"004child\",\n+\t|      \"$TT\",\n+\t|      \"trace2\",\n+\t|      \"004child\",\n+\t|      \"$TT\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"child\":{\n+\t|      \"0\":{\n+\t|        \"child_argv\":[\n+\t|          \"_EXE_\",\n+\t|          \"trace2\",\n+\t|          \"004child\",\n+\t|          \"$TT\",\n+\t|          \"trace2\",\n+\t|          \"001return\",\n+\t|          \"0\"\n+\t|        ],\n+\t|        \"child_class\":\"?\",\n+\t|        \"child_code\":0,\n+\t|        \"use_shell\":0\n+\t|      }\n+\t|    },\n+\t|    \"exit_code\":0,\n+\t|    \"verb\":\"trace2\",\n+\t|    \"verb_hierarchy\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  },\n+\t|  \"_SID0_/_SID1_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"004child\",\n+\t|      \"$TT\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"child\":{\n+\t|      \"0\":{\n+\t|        \"child_argv\":[\n+\t|          \"_EXE_\",\n+\t|          \"trace2\",\n+\t|          \"001return\",\n+\t|          \"0\"\n+\t|        ],\n+\t|        \"child_class\":\"?\",\n+\t|        \"child_code\":0,\n+\t|        \"use_shell\":0\n+\t|      }\n+\t|    },\n+\t|    \"exit_code\":0,\n+\t|    \"verb\":\"trace2\",\n+\t|    \"verb_hierarchy\":\"trace2/trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  },\n+\t|  \"_SID0_/_SID1_/_SID2_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"exit_code\":0,\n+\t|    \"verb\":\"trace2\",\n+\t|    \"verb_hierarchy\":\"trace2/trace2/trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Test listing of all \"interesting\" config settings.\n+\n+test_expect_success 'event stream, list config' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tgit config --local t0212.abc 1 &&\n+\tgit config --local t0212.def \"hello world\" &&\n+\t# delete events generated by the above config commands\n+\trm trace.event &&\n+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" && export GIT_TR2_EVENT &&\n+\tGIT_TR2_CONFIG_PARAMS=\"t0212.*\" $TT trace2 001return 0 &&\n+\tunset GIT_TR2_EVENT &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"exit_code\":0,\n+\t|    \"params\":[\n+\t|      {\n+\t|        \"param\":\"t0212.abc\",\n+\t|        \"value\":\"1\"\n+\t|      },\n+\t|      {\n+\t|        \"param\":\"t0212.def\",\n+\t|        \"value\":\"hello world\"\n+\t|      }\n+\t|    ],\n+\t|    \"verb\":\"trace2\",\n+\t|    \"verb_hierarchy\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'basic trace2_data' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" && export GIT_TR2_EVENT &&\n+\t$TT trace2 006data test_category k1 v1 test_category k2 v2 &&\n+\tunset GIT_TR2_EVENT &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"006data\",\n+\t|      \"test_category\",\n+\t|      \"k1\",\n+\t|      \"v1\",\n+\t|      \"test_category\",\n+\t|      \"k2\",\n+\t|      \"v2\"\n+\t|    ],\n+\t|    \"data\":{\n+\t|      \"test_category\":{\n+\t|        \"k1\":\"v1\",\n+\t|        \"k2\":\"v2\"\n+\t|      }\n+\t|    },\n+\t|    \"exit_code\":0,\n+\t|    \"verb\":\"trace2\",\n+\t|    \"verb_hierarchy\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t0212/parse_events.perl b/t/t0212/parse_events.perl\nnew file mode 100644\nindex 0000000000..0e38fa6db6\n--- /dev/null\n+++ b/t/t0212/parse_events.perl\n@@ -0,0 +1,251 @@\n+#!/usr/bin/perl\n+#\n+# Parse event stream and convert individual events into a summary\n+# record for the process.\n+#\n+# Git.exe generates one or more \"event\" records for each API method,\n+# such as \"start <argv>\" and \"exit <code>\", during the life of the git\n+# process.  Additionally, the input may contain interleaved events\n+# from multiple concurrent git processes and/or multiple threads from\n+# within a git process.\n+#\n+# Accumulate events for each process (based on its unique SID) in a\n+# dictionary and emit process summary records.\n+#\n+# Convert some of the variable fields (such as elapsed time) into\n+# placeholders (or omit them) to make HEREDOC comparisons easier in\n+# the test scripts.\n+#\n+# We may also omit fields not (currently) useful for testing purposes.\n+\n+use strict;\n+use warnings;\n+use JSON::PP;\n+use Data::Dumper;\n+use Getopt::Long;\n+\n+# The version of the trace2 event target format that we understand.\n+# This is reported in the 'version' event in the 'evt' field.\n+# It comes from the GIT_TR2_EVENT_VERSION macro in trace2/tr2_tgt_event.c\n+my $evt_version = '1';\n+\n+my $show_children = 1;\n+my $show_exec     = 1;\n+my $show_threads  = 1;\n+\n+# A hack to generate test HEREDOC data for pasting into the test script.\n+# Usage:\n+#    cd \"t/trash directory.t0212-trace2-event\"\n+#    $TT trace ... >trace.event\n+#    VV=$(../../git.exe version | sed -e 's/^git version //')\n+#    perl ../t0212/parse_events.perl --HEREDOC --VERSION=$VV <trace.event >heredoc\n+# Then paste heredoc into your new test.\n+\n+my $gen_heredoc = 0;\n+my $gen_version = '';\n+\n+GetOptions(\"children!\" => \\$show_children,\n+\t   \"exec!\"     => \\$show_exec,\n+\t   \"threads!\"  => \\$show_threads,\n+\t   \"HEREDOC!\"  => \\$gen_heredoc,\n+\t   \"VERSION=s\" => \\$gen_version    )\n+    or die(\"Error in command line arguments\\n\");\n+\n+\n+# SIDs contains timestamps and PIDs of the process and its parents.\n+# This makes it difficult to match up in a HEREDOC in the test script.\n+# Build a map from actual SIDs to predictable constant values and yet\n+# keep the parent/child relationships.  For example:\n+# {..., \"sid\":\"1539706952458276-8652\", ...}\n+# {..., \"sid\":\"1539706952458276-8652/1539706952649493-15452\", ...}\n+# becomes:\n+# {..., \"sid\":\"_SID1_\", ...}\n+# {..., \"sid\":\"_SID1_/_SID2_\", ...}\n+my $sid_map;\n+my $sid_count = 0;\n+\n+my $processes;\n+\n+while (<>) {\n+    my $line = decode_json( $_ );\n+\n+    my $sid = \"\";\n+    my $sid_sep = \"\";\n+\n+    my $raw_sid = $line->{'sid'};\n+    my @raw_sid_parts = split /\\//, $raw_sid;\n+    foreach my $raw_sid_k (@raw_sid_parts) {\n+\tif (!exists $sid_map->{$raw_sid_k}) {\n+\t    $sid_map->{$raw_sid_k} = '_SID' . $sid_count . '_';\n+\t    $sid_count++;\n+\t}\n+\t$sid = $sid . $sid_sep . $sid_map->{$raw_sid_k};\n+\t$sid_sep = '/';\n+    }\n+    \n+    my $event = $line->{'event'};\n+\n+    if ($event eq 'version') {\n+\t$processes->{$sid}->{'version'} = $line->{'exe'};\n+\tif ($gen_heredoc == 1 && $gen_version eq $line->{'exe'}) {\n+\t    # If we are generating data FOR the test script, replace\n+\t    # the reported git.exe version with a reference to an\n+\t    # environment variable.  When our output is pasted into\n+\t    # the test script, it will then be expanded in future\n+\t    # test runs to the THEN current version of git.exe.\n+\t    # We assume that the test script uses env var $V.\n+\t    $processes->{$sid}->{'version'} = \"\\$V\";\n+\t}\n+    }\n+\n+    elsif ($event eq 'start') {\n+\t$processes->{$sid}->{'argv'} = $line->{'argv'};\n+\t$processes->{$sid}->{'argv'}[0] = \"_EXE_\";\n+    }\n+\n+    elsif ($event eq 'exit') {\n+\t$processes->{$sid}->{'exit_code'} = $line->{'code'};\n+    }\n+\n+    elsif ($event eq 'atexit') {\n+\t$processes->{$sid}->{'exit_code'} = $line->{'code'};\n+    }\n+\n+    elsif ($event eq 'error') {\n+\t# For HEREDOC purposes, use the error message format string if\n+\t# available, rather than the formatted message (which probably\n+\t# has an absolute pathname).\n+\tif (exists $line->{'fmt'}) {\n+\t    push( @{$processes->{$sid}->{'errors'}}, $line->{'fmt'} );\n+\t}\n+\telsif (exists $line->{'msg'}) {\n+\t    push( @{$processes->{$sid}->{'errors'}}, $line->{'msg'} );\n+\t}\n+    }\n+\n+    elsif ($event eq 'cmd_path') {\n+\t## $processes->{$sid}->{'path'} = $line->{'path'};\n+\t#\n+\t# Like in the 'start' event, we need to replace the value of\n+\t# argv[0] with a token for HEREDOC purposes.  However, the\n+\t# event is only emitted when RUNTIME_PREFIX is defined, so\n+\t# just omit it for testing purposes.\n+\t# $processes->{$sid}->{'path'} = \"_EXE_\";\n+    }\n+    \n+    elsif ($event eq 'cmd_verb') {\n+\t$processes->{$sid}->{'verb'} = $line->{'name'};\n+\t$processes->{$sid}->{'verb_hierarchy'} = $line->{'hierarchy'};\n+    }\n+\n+    elsif ($event eq 'alias') {\n+\t$processes->{$sid}->{'alias'}->{'key'} = $line->{'alias'};\n+\t$processes->{$sid}->{'alias'}->{'argv'} = $line->{'argv'};\n+    }\n+\n+    elsif ($event eq 'def_param') {\n+\tmy $kv;\n+\t$kv->{'param'} = $line->{'param'};\n+\t$kv->{'value'} = $line->{'value'};\n+\tpush( @{$processes->{$sid}->{'params'}}, $kv );\n+    }\n+\n+    elsif ($event eq 'child_start') {\n+\tif ($show_children == 1) {\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_class'} = $line->{'child_class'};\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_argv'} = $line->{'argv'};\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_argv'}[0] = \"_EXE_\";\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'use_shell'} = $line->{'use_shell'} ? 1 : 0;\n+\t}\n+    }\n+\n+    elsif ($event eq 'child_exit') {\n+\tif ($show_children == 1) {\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_code'} = $line->{'code'};\n+\t}\n+    }\n+\n+    # TODO decide what information we want to test from thread events.\n+\n+    elsif ($event eq 'thread_start') {\n+\tif ($show_threads == 1) {\n+\t}\n+    }\n+\n+    elsif ($event eq 'thread_exit') {\n+\tif ($show_threads == 1) {\n+\t}\n+    }\n+\n+    # TODO decide what information we want to test from exec events.\n+\n+    elsif ($event eq 'exec') {\n+\tif ($show_exec == 1) {\n+\t}\n+    }\n+\n+    elsif ($event eq 'exec_result') {\n+\tif ($show_exec == 1) {\n+\t}\n+    }\n+\n+    elsif ($event eq 'def_param') {\n+\t# Accumulate parameter key/value pairs by key rather than in an array\n+\t# so that we get overwrite (last one wins) effects.\n+\t$processes->{$sid}->{'params'}->{$line->{'param'}} = $line->{'value'};\n+    }\n+\n+    elsif ($event eq 'def_repo') {\n+\t# $processes->{$sid}->{'repos'}->{$line->{'repo'}} = $line->{'worktree'};\n+\t$processes->{$sid}->{'repos'}->{$line->{'repo'}} = \"_WORKTREE_\";\n+    }\n+\n+    # A series of potentially nested and threaded region and data events\n+    # is fundamentally incompatibile with the type of summary record we\n+    # are building in this script.  Since they are intended for\n+    # perf-trace-like analysis rather than a result summary, we ignore\n+    # most of them here.\n+\n+    # elsif ($event eq 'region_enter') {\n+    # }\n+    # elsif ($event eq 'region_leave') {\n+    # }\n+\n+    elsif ($event eq 'data') {\n+\tmy $cat = $line->{'category'};\n+\tif ($cat eq 'test_category') {\n+\t    \n+\t    my $key = $line->{'key'};\n+\t    my $value = $line->{'value'};\n+\t    $processes->{$sid}->{'data'}->{$cat}->{$key} = $value;\n+\t}\n+    }\n+\n+    # This trace2 target does not emit 'printf' events.\n+    #\n+    # elsif ($event eq 'printf') {\n+    # }\n+}\n+\n+# Dump the resulting hash into something that we can compare against\n+# in the test script.  These options make Dumper output look a little\n+# bit like JSON.  Also convert variable references of the form \"$VAR*\"\n+# so that the matching HEREDOC doesn't need to escape it.\n+\n+$Data::Dumper::Sortkeys = 1;\n+$Data::Dumper::Indent = 1;\n+$Data::Dumper::Purity = 1;\n+$Data::Dumper::Pair = ':';\n+\n+my $out = Dumper($processes);\n+$out =~ s/'/\"/g;\n+$out =~ s/\\$VAR/VAR/g;\n+\n+# Finally, if we're running this script to generate (manually confirmed)\n+# data to add to the test script, guard the indentation.\n+\n+if ($gen_heredoc == 1) {\n+    $out =~ s/^/\\t\\|/gms;\n+}\n+\n+print $out;\n-- \ngitgitgadget\n"},{"id":"367879","messageId":"34589178117c8ddf9cf80fda3cde80a38676d700.1548712060.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 10/14] pack-objects: add trace2 regions","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-28T21:47:49Z","receivedAt":"2019-01-28T21:48:00Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWhen studying the performance of 'git push' we would like to know\nhow much time is spent at various parts of the command. One area\nthat could cause performance trouble is 'git pack-objects'.\n\nAdd trace2 regions around the three main actions taken in this\ncommand:\n\n1. Enumerate objects.\n2. Prepare pack.\n3. Write pack-file.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/pack-objects.c | 12 +++++++++++-\n 1 file changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 889df2c755..6708529e3c 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -33,6 +33,7 @@\n #include \"object-store.h\"\n #include \"dir.h\"\n #include \"midx.h\"\n+#include \"trace2.h\"\n \n #define IN_PACK(obj) oe_in_pack(&to_pack, obj)\n #define SIZE(obj) oe_size(&to_pack, obj)\n@@ -3472,6 +3473,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n+\ttrace2_region_enter(\"pack-objects\", \"enumerate-objects\", the_repository);\n \tprepare_packing_data(the_repository, &to_pack);\n \n \tif (progress)\n@@ -3486,12 +3488,20 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)\n \tif (include_tag && nr_result)\n \t\tfor_each_ref(add_ref_tag, NULL);\n \tstop_progress(&progress_state);\n+\ttrace2_region_leave(\"pack-objects\", \"enumerate-objects\", the_repository);\n \n \tif (non_empty && !nr_result)\n \t\treturn 0;\n-\tif (nr_result)\n+\tif (nr_result) {\n+\t\ttrace2_region_enter(\"pack-objects\", \"prepare-pack\", the_repository);\n \t\tprepare_pack(window, depth);\n+\t\ttrace2_region_leave(\"pack-objects\", \"prepare-pack\", the_repository);\n+\t}\n+\n+\ttrace2_region_enter(\"pack-objects\", \"write-pack-file\", the_repository);\n \twrite_pack_file();\n+\ttrace2_region_leave(\"pack-objects\", \"write-pack-file\", the_repository);\n+\n \tif (progress)\n \t\tfprintf_ln(stderr,\n \t\t\t   _(\"Total %\"PRIu32\" (delta %\"PRIu32\"),\"\n-- \ngitgitgadget\n\n"},{"id":"367880","messageId":"ea39b76d316dd3743799391d1b89ae3823394553.1548712060.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 02/14] trace2: create new combined trace facility","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-28T21:47:42Z","receivedAt":"2019-01-28T21:48:01Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate a new unified tracing facility for git.  The eventual intent is to\nreplace the current trace_printf* and trace_performance* routines with a\nunified set of git_trace2* routines.\n\nIn addition to the usual printf-style API, trace2 provides higer-level\nevent verbs with fixed-fields allowing structured data to be written.\nThis makes post-processing and analysis easier for external tools.\n\nTrace2 defines 3 output targets.  These are set using the environment\nvariables \"GIT_TR2\", \"GIT_TR2_PERF\", and \"GIT_TR2_EVENT\".  These may be\nset to \"1\" or to an absolute pathname (just like the current GIT_TRACE).\n\n* GIT_TR2 is intended to be a replacement for GIT_TRACE and logs command\n  summary data.\n\n* GIT_TR2_PERF is intended as a replacement for GIT_TRACE_PERFORMANCE.\n  It extends the output with columns for the command process, thread,\n  repo, absolute and relative elapsed times.  It reports events for\n  child process start/stop, thread start/stop, and per-thread function\n  nesting.\n\n* GIT_TR2_EVENT is a new structured format. It writes event data as a\n  series of JSON records.\n\nCalls to trace2 functions log to any of the 3 output targets enabled\nwithout the need to call different trace_printf* or trace_performance*\nroutines.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                      |  10 +\n builtin/submodule--helper.c   |  11 +-\n cache.h                       |   1 +\n common-main.c                 |  12 +-\n compat/mingw.c                |  11 +-\n compat/mingw.h                |   3 +-\n config.c                      |   2 +\n exec-cmd.c                    |   2 +\n git-compat-util.h             |   7 +\n git.c                         |  65 +++\n remote-curl.c                 |   7 +\n repository.c                  |   2 +\n repository.h                  |   3 +\n run-command.c                 |  63 ++-\n run-command.h                 |  17 +-\n sh-i18n--envsubst.c           |   3 +\n submodule.c                   |  11 +-\n t/helper/test-parse-options.c |   3 +\n t/helper/test-tool.c          |   3 +\n t/t0001-init.sh               |   1 +\n trace2.c                      | 809 ++++++++++++++++++++++++++++++++++\n trace2.h                      | 390 ++++++++++++++++\n trace2/tr2_cfg.c              |  92 ++++\n trace2/tr2_cfg.h              |  19 +\n trace2/tr2_dst.c              |  90 ++++\n trace2/tr2_dst.h              |  34 ++\n trace2/tr2_sid.c              |  67 +++\n trace2/tr2_sid.h              |  18 +\n trace2/tr2_tbuf.c             |  32 ++\n trace2/tr2_tbuf.h             |  23 +\n trace2/tr2_tgt.h              | 126 ++++++\n trace2/tr2_tgt_event.c        | 606 +++++++++++++++++++++++++\n trace2/tr2_tgt_normal.c       | 331 ++++++++++++++\n trace2/tr2_tgt_perf.c         | 573 ++++++++++++++++++++++++\n trace2/tr2_tls.c              | 164 +++++++\n trace2/tr2_tls.h              |  95 ++++\n trace2/tr2_verb.c             |  30 ++\n trace2/tr2_verb.h             |  24 +\n usage.c                       |  31 ++\n 39 files changed, 3774 insertions(+), 17 deletions(-)\n create mode 100644 trace2.c\n create mode 100644 trace2.h\n create mode 100644 trace2/tr2_cfg.c\n create mode 100644 trace2/tr2_cfg.h\n create mode 100644 trace2/tr2_dst.c\n create mode 100644 trace2/tr2_dst.h\n create mode 100644 trace2/tr2_sid.c\n create mode 100644 trace2/tr2_sid.h\n create mode 100644 trace2/tr2_tbuf.c\n create mode 100644 trace2/tr2_tbuf.h\n create mode 100644 trace2/tr2_tgt.h\n create mode 100644 trace2/tr2_tgt_event.c\n create mode 100644 trace2/tr2_tgt_normal.c\n create mode 100644 trace2/tr2_tgt_perf.c\n create mode 100644 trace2/tr2_tls.c\n create mode 100644 trace2/tr2_tls.h\n create mode 100644 trace2/tr2_verb.c\n create mode 100644 trace2/tr2_verb.h\n\ndiff --git a/Makefile b/Makefile\nindex 1a44c811aa..5d4e993cdc 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -996,6 +996,16 @@ LIB_OBJS += tempfile.o\n LIB_OBJS += thread-utils.o\n LIB_OBJS += tmp-objdir.o\n LIB_OBJS += trace.o\n+LIB_OBJS += trace2.o\n+LIB_OBJS += trace2/tr2_cfg.o\n+LIB_OBJS += trace2/tr2_dst.o\n+LIB_OBJS += trace2/tr2_sid.o\n+LIB_OBJS += trace2/tr2_tbuf.o\n+LIB_OBJS += trace2/tr2_tgt_event.o\n+LIB_OBJS += trace2/tr2_tgt_normal.o\n+LIB_OBJS += trace2/tr2_tgt_perf.o\n+LIB_OBJS += trace2/tr2_tls.o\n+LIB_OBJS += trace2/tr2_verb.o\n LIB_OBJS += trailer.o\n LIB_OBJS += transport.o\n LIB_OBJS += transport-helper.o\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex b45514be31..82716b5060 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -1813,11 +1813,12 @@ static int update_submodules(struct submodule_update_clone *suc)\n {\n \tint i;\n \n-\trun_processes_parallel(suc->max_jobs,\n-\t\t\t       update_clone_get_next_task,\n-\t\t\t       update_clone_start_failure,\n-\t\t\t       update_clone_task_finished,\n-\t\t\t       suc);\n+\trun_processes_parallel_tr2(suc->max_jobs,\n+\t\t\t\t   update_clone_get_next_task,\n+\t\t\t\t   update_clone_start_failure,\n+\t\t\t\t   update_clone_task_finished,\n+\t\t\t\t   suc,\n+\t\t\t\t   \"submodule\", \"parallel/update\");\n \n \t/*\n \t * We saved the output and put it out all at once now.\ndiff --git a/cache.h b/cache.h\nindex 49713cc5a5..af996443ee 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -9,6 +9,7 @@\n #include \"gettext.h\"\n #include \"convert.h\"\n #include \"trace.h\"\n+#include \"trace2.h\"\n #include \"string-list.h\"\n #include \"pack-revindex.h\"\n #include \"hash.h\"\ndiff --git a/common-main.c b/common-main.c\nindex 3728f66b4c..6dbdc4adf2 100644\n--- a/common-main.c\n+++ b/common-main.c\n@@ -25,12 +25,18 @@ static void restore_sigpipe_to_default(void)\n \n int main(int argc, const char **argv)\n {\n+\tint result;\n+\n \t/*\n \t * Always open file descriptors 0/1/2 to avoid clobbering files\n \t * in die().  It also avoids messing up when the pipes are dup'ed\n \t * onto stdin/stdout/stderr in the child processes we spawn.\n \t */\n \tsanitize_stdfds();\n+\trestore_sigpipe_to_default();\n+\n+\ttrace2_initialize();\n+\ttrace2_cmd_start(argv);\n \n \tgit_resolve_executable_dir(argv[0]);\n \n@@ -40,7 +46,9 @@ int main(int argc, const char **argv)\n \n \tattr_start();\n \n-\trestore_sigpipe_to_default();\n+\tresult = cmd_main(argc, argv);\n+\n+\ttrace2_cmd_exit(result);\n \n-\treturn cmd_main(argc, argv);\n+\treturn result;\n }\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex b459e1a291..111fe68baf 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -1476,19 +1476,23 @@ static int try_shell_exec(const char *cmd, char *const *argv)\n \t\treturn 0;\n \tprog = path_lookup(interpr, 1);\n \tif (prog) {\n+\t\tint exec_id;\n \t\tint argc = 0;\n \t\tconst char **argv2;\n \t\twhile (argv[argc]) argc++;\n \t\tALLOC_ARRAY(argv2, argc + 1);\n \t\targv2[0] = (char *)cmd;\t/* full path to the script file */\n \t\tmemcpy(&argv2[1], &argv[1], sizeof(*argv) * argc);\n+\t\texec_id = trace2_exec(prog, argv2);\n \t\tpid = mingw_spawnv(prog, argv2, 1);\n \t\tif (pid >= 0) {\n \t\t\tint status;\n \t\t\tif (waitpid(pid, &status, 0) < 0)\n \t\t\t\tstatus = 255;\n+\t\t\ttrace2_exec_result(exec_id, status);\n \t\t\texit(status);\n \t\t}\n+\t\ttrace2_exec_result(exec_id, -1);\n \t\tpid = 1;\t/* indicate that we tried but failed */\n \t\tfree(prog);\n \t\tfree(argv2);\n@@ -1501,12 +1505,17 @@ int mingw_execv(const char *cmd, char *const *argv)\n \t/* check if git_command is a shell script */\n \tif (!try_shell_exec(cmd, argv)) {\n \t\tint pid, status;\n+\t\tint exec_id;\n \n+\t\texec_id = trace2_exec(cmd, (const char **)argv);\n \t\tpid = mingw_spawnv(cmd, (const char **)argv, 0);\n-\t\tif (pid < 0)\n+\t\tif (pid < 0) {\n+\t\t\ttrace2_exec_result(exec_id, -1);\n \t\t\treturn -1;\n+\t\t}\n \t\tif (waitpid(pid, &status, 0) < 0)\n \t\t\tstatus = 255;\n+\t\ttrace2_exec_result(exec_id, status);\n \t\texit(status);\n \t}\n \treturn -1;\ndiff --git a/compat/mingw.h b/compat/mingw.h\nindex 30d9fb3e36..4d73f8aa9d 100644\n--- a/compat/mingw.h\n+++ b/compat/mingw.h\n@@ -147,8 +147,7 @@ static inline int fcntl(int fd, int cmd, ...)\n \terrno = EINVAL;\n \treturn -1;\n }\n-/* bash cannot reliably detect negative return codes as failure */\n-#define exit(code) exit((code) & 0xff)\n+\n #define sigemptyset(x) (void)0\n static inline int sigaddset(sigset_t *set, int signum)\n { return 0; }\ndiff --git a/config.c b/config.c\nindex ff521eb27a..4beeb7795a 100644\n--- a/config.c\n+++ b/config.c\n@@ -2656,6 +2656,8 @@ int git_config_set_gently(const char *key, const char *value)\n void git_config_set(const char *key, const char *value)\n {\n \tgit_config_set_multivar(key, value, NULL, 0);\n+\n+\ttrace2_cmd_set_config(key, value);\n }\n \n /*\ndiff --git a/exec-cmd.c b/exec-cmd.c\nindex 4f81f44310..7deeab3039 100644\n--- a/exec-cmd.c\n+++ b/exec-cmd.c\n@@ -209,6 +209,8 @@ static int git_get_exec_path(struct strbuf *buf, const char *argv0)\n \t\treturn -1;\n \t}\n \n+\ttrace2_cmd_path(buf->buf);\n+\n \treturn 0;\n }\n \ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex b8ace77410..a41b277929 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -1238,6 +1238,13 @@ static inline int is_missing_file_error(int errno_)\n \n extern int cmd_main(int, const char **);\n \n+/*\n+ * Intercept all calls to exit() and route them to trace2 to\n+ * optionally emit a message before calling the real exit().\n+ */\n+int trace2_cmd_exit_fl(const char *file, int line, int code);\n+#define exit(code) exit(trace2_cmd_exit_fl(__FILE__, __LINE__, (code)))\n+\n /*\n  * You can mark a stack variable with UNLEAK(var) to avoid it being\n  * reported as a leak by tools like LSAN or valgrind. The argument\ndiff --git a/git.c b/git.c\nindex 4d53a3d50d..2db81c775a 100644\n--- a/git.c\n+++ b/git.c\n@@ -146,16 +146,20 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\t\tgit_set_exec_path(cmd + 1);\n \t\t\telse {\n \t\t\t\tputs(git_exec_path());\n+\t\t\t\ttrace2_cmd_verb(\"_query_\");\n \t\t\t\texit(0);\n \t\t\t}\n \t\t} else if (!strcmp(cmd, \"--html-path\")) {\n \t\t\tputs(system_path(GIT_HTML_PATH));\n+\t\t\ttrace2_cmd_verb(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"--man-path\")) {\n \t\t\tputs(system_path(GIT_MAN_PATH));\n+\t\t\ttrace2_cmd_verb(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"--info-path\")) {\n \t\t\tputs(system_path(GIT_INFO_PATH));\n+\t\t\ttrace2_cmd_verb(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"-p\") || !strcmp(cmd, \"--paginate\")) {\n \t\t\tuse_pager = 1;\n@@ -284,6 +288,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\t(*argv)++;\n \t\t\t(*argc)--;\n \t\t} else if (skip_prefix(cmd, \"--list-cmds=\", &cmd)) {\n+\t\t\ttrace2_cmd_verb(\"_query_\");\n \t\t\tif (!strcmp(cmd, \"parseopt\")) {\n \t\t\t\tstruct string_list list = STRING_LIST_INIT_DUP;\n \t\t\t\tint i;\n@@ -331,9 +336,14 @@ static int handle_alias(int *argcp, const char ***argv)\n \t\t\tcommit_pager_choice();\n \n \t\t\tchild.use_shell = 1;\n+\t\t\tchild.trace2_child_class = \"shell_alias\";\n \t\t\targv_array_push(&child.args, alias_string + 1);\n \t\t\targv_array_pushv(&child.args, (*argv) + 1);\n \n+\t\t\ttrace2_cmd_alias(alias_command, child.args.argv);\n+\t\t\ttrace2_cmd_list_config();\n+\t\t\ttrace2_cmd_verb(\"_run_shell_alias_\");\n+\n \t\t\tret = run_command(&child);\n \t\t\tif (ret >= 0)   /* normal exit */\n \t\t\t\texit(ret);\n@@ -368,6 +378,9 @@ static int handle_alias(int *argcp, const char ***argv)\n \t\t/* insert after command name */\n \t\tmemcpy(new_argv + count, *argv + 1, sizeof(char *) * *argcp);\n \n+\t\ttrace2_cmd_alias(alias_command, new_argv);\n+\t\ttrace2_cmd_list_config();\n+\n \t\t*argv = new_argv;\n \t\t*argcp += count - 1;\n \n@@ -416,6 +429,8 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)\n \t\tsetup_work_tree();\n \n \ttrace_argv_printf(argv, \"trace: built-in: git\");\n+\ttrace2_cmd_verb(p->cmd);\n+\ttrace2_cmd_list_config();\n \n \tvalidate_cache_entries(&the_index);\n \tstatus = p->fn(argc, argv, prefix);\n@@ -665,7 +680,14 @@ static void execv_dashed_external(const char **argv)\n \tcmd.clean_on_exit = 1;\n \tcmd.wait_after_clean = 1;\n \tcmd.silent_exec_failure = 1;\n+\tcmd.trace2_child_class = \"dashed\";\n \n+\ttrace2_cmd_verb(\"_run_dashed_\");\n+\n+\t/*\n+\t * The code in run_command() logs trace2 child_start/child_exit\n+\t * events, so we do not need to report exec/exec_result events here.\n+\t */\n \ttrace_argv_printf(cmd.args.argv, \"trace: exec:\");\n \n \t/*\n@@ -675,6 +697,12 @@ static void execv_dashed_external(const char **argv)\n \t * the program.\n \t */\n \tstatus = run_command(&cmd);\n+\n+\t/*\n+\t * If the child process ran and we are now going to exit, emit a\n+\t * generic string as our trace2 command verb to indicate that we\n+\t * launched a dashed command.\n+\t */\n \tif (status >= 0)\n \t\texit(status);\n \telse if (errno != ENOENT)\n@@ -700,6 +728,43 @@ static int run_argv(int *argcp, const char ***argv)\n \t\tif (!done_alias)\n \t\t\thandle_builtin(*argcp, *argv);\n \n+#if 0 // TODO In GFW, need to amend a7924b655e940b06cb547c235d6bed9767929673 to include trace2_ and _tr2 lines.\n+\t\telse if (get_builtin(**argv)) {\n+\t\t\tstruct argv_array args = ARGV_ARRAY_INIT;\n+\t\t\tint i;\n+\n+\t\t\t/*\n+\t\t\t * The current process is committed to launching a\n+\t\t\t * child process to run the command named in (**argv)\n+\t\t\t * and exiting.  Log a generic string as the trace2\n+\t\t\t * command verb to indicate this.  Note that the child\n+\t\t\t * process will log the actual verb when it runs.\n+\t\t\t */\n+\t\t\ttrace2_cmd_verb(\"_run_git_alias_\");\n+\n+\t\t\tif (get_super_prefix())\n+\t\t\t\tdie(\"%s doesn't support --super-prefix\", **argv);\n+\n+\t\t\tcommit_pager_choice();\n+\n+\t\t\targv_array_push(&args, \"git\");\n+\t\t\tfor (i = 0; i < *argcp; i++)\n+\t\t\t\targv_array_push(&args, (*argv)[i]);\n+\n+\t\t\ttrace_argv_printf(args.argv, \"trace: exec:\");\n+\n+\t\t\t/*\n+\t\t\t * if we fail because the command is not found, it is\n+\t\t\t * OK to return. Otherwise, we just pass along the status code.\n+\t\t\t */\n+\t\t\ti = run_command_v_opt_tr2(args.argv, RUN_SILENT_EXEC_FAILURE |\n+\t\t\t\t\t\t  RUN_CLEAN_ON_EXIT, \"git_alias\");\n+\t\t\tif (i >= 0 || errno != ENOENT)\n+\t\t\t\texit(i);\n+\t\t\tdie(\"could not execute builtin %s\", **argv);\n+\t\t}\n+#endif // a7924b655e940b06cb547c235d6bed9767929673\n+\n \t\t/* .. then try the external ones */\n \t\texecv_dashed_external(*argv);\n \ndiff --git a/remote-curl.c b/remote-curl.c\nindex 1220dffcdc..8950b5c383 100644\n--- a/remote-curl.c\n+++ b/remote-curl.c\n@@ -1340,6 +1340,13 @@ int cmd_main(int argc, const char **argv)\n \tstring_list_init(&options.deepen_not, 1);\n \tstring_list_init(&options.push_options, 1);\n \n+\t/*\n+\t * Just report \"remote-curl\" here (folding all the various aliases\n+\t * (\"git-remote-http\", \"git-remote-https\", and etc.) here since they\n+\t * are all just copies of the same actual executable.\n+\t */\n+\ttrace2_cmd_verb(\"remote-curl\");\n+\n \tremote = remote_get(argv[1]);\n \n \tif (argc > 2) {\ndiff --git a/repository.c b/repository.c\nindex 7b02e1dffa..b741089dbb 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -119,6 +119,8 @@ static int repo_init_gitdir(struct repository *repo, const char *gitdir)\n void repo_set_worktree(struct repository *repo, const char *path)\n {\n \trepo->worktree = real_pathdup(path, 1);\n+\n+\ttrace2_def_repo(repo);\n }\n \n static int read_and_verify_repository_format(struct repository_format *format,\ndiff --git a/repository.h b/repository.h\nindex 9f16c42c1e..97b03846ba 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -90,6 +90,9 @@ struct repository {\n \t/* Repository's current hash algorithm, as serialized on disk. */\n \tconst struct git_hash_algo *hash_algo;\n \n+\t/* A unique-id for tracing purposes. */\n+\tint trace2_repo_id;\n+\n \t/* Configurations */\n \n \t/* Indicate if a repository has a different 'commondir' from 'gitdir' */\ndiff --git a/run-command.c b/run-command.c\nindex 3db26b7b0e..8948f6fc6f 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -219,9 +219,29 @@ static int exists_in_PATH(const char *file)\n \n int sane_execvp(const char *file, char * const argv[])\n {\n+#ifndef GIT_WINDOWS_NATIVE\n+\t/*\n+\t * execvp() doesn't return, so we all we can do is tell trace2\n+\t * what we are about to do and let it leave a hint in the log\n+\t * (unless of course the execvp() fails).\n+\t *\n+\t * we skip this for Windows because the compat layer already\n+\t * has to emulate the execvp() call anyway.\n+\t */\n+\tint exec_id = trace2_exec(file, (const char**)argv);\n+#endif\n+\n \tif (!execvp(file, argv))\n \t\treturn 0; /* cannot happen ;-) */\n \n+#ifndef GIT_WINDOWS_NATIVE\n+\t{\n+\t\tint ec = errno;\n+\t\ttrace2_exec_result(exec_id, ec);\n+\t\terrno = ec;\n+\t}\n+#endif\n+\n \t/*\n \t * When a command can't be found because one of the directories\n \t * listed in $PATH is unsearchable, execvp reports EACCES, but\n@@ -712,6 +732,7 @@ int start_command(struct child_process *cmd)\n \t\tcmd->err = fderr[0];\n \t}\n \n+\ttrace2_child_start(cmd);\n \ttrace_run_command(cmd);\n \n \tfflush(NULL);\n@@ -926,6 +947,8 @@ int start_command(struct child_process *cmd)\n #endif\n \n \tif (cmd->pid < 0) {\n+\t\ttrace2_child_exit(cmd, -1);\n+\n \t\tif (need_in)\n \t\t\tclose_pair(fdin);\n \t\telse if (cmd->in)\n@@ -964,13 +987,16 @@ int start_command(struct child_process *cmd)\n int finish_command(struct child_process *cmd)\n {\n \tint ret = wait_or_whine(cmd->pid, cmd->argv[0], 0);\n+\ttrace2_child_exit(cmd, ret);\n \tchild_process_clear(cmd);\n \treturn ret;\n }\n \n int finish_command_in_signal(struct child_process *cmd)\n {\n-\treturn wait_or_whine(cmd->pid, cmd->argv[0], 1);\n+\tint ret = wait_or_whine(cmd->pid, cmd->argv[0], 1);\n+\ttrace2_child_exit(cmd, ret);\n+\treturn ret;\n }\n \n \n@@ -992,7 +1018,18 @@ int run_command_v_opt(const char **argv, int opt)\n \treturn run_command_v_opt_cd_env(argv, opt, NULL, NULL);\n }\n \n+int run_command_v_opt_tr2(const char **argv, int opt, const char *tr2_class)\n+{\n+\treturn run_command_v_opt_cd_env_tr2(argv, opt, NULL, NULL, tr2_class);\n+}\n+\n int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env)\n+{\n+\treturn run_command_v_opt_cd_env_tr2(argv, opt, dir, env, NULL);\n+}\n+\n+int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir, const char *const *env,\n+\t\t\t\t const char *tr2_class)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \tcmd.argv = argv;\n@@ -1004,6 +1041,7 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const\n \tcmd.clean_on_exit = opt & RUN_CLEAN_ON_EXIT ? 1 : 0;\n \tcmd.dir = dir;\n \tcmd.env = env;\n+\tcmd.trace2_child_class = tr2_class;\n \treturn run_command(&cmd);\n }\n \n@@ -1319,6 +1357,7 @@ int run_hook_ve(const char *const *env, const char *name, va_list args)\n \thook.env = env;\n \thook.no_stdin = 1;\n \thook.stdout_to_stderr = 1;\n+\thook.trace2_hook_name = name;\n \n \treturn run_command(&hook);\n }\n@@ -1807,3 +1846,25 @@ int run_processes_parallel(int n,\n \tpp_cleanup(&pp);\n \treturn 0;\n }\n+\n+int run_processes_parallel_tr2(int n,\n+\t\t\t       get_next_task_fn get_next_task,\n+\t\t\t       start_failure_fn start_failure,\n+\t\t\t       task_finished_fn task_finished,\n+\t\t\t       void *pp_cb,\n+\t\t\t       const char *tr2_category,\n+\t\t\t       const char *tr2_label)\n+{\n+\tint result;\n+\n+\ttrace2_region_enter_printf(tr2_category, tr2_label, NULL,\n+\t\t\t\t   \"max:%d\", ((n < 1) ? online_cpus() : n));\n+\n+\tresult = run_processes_parallel(n, get_next_task, start_failure,\n+\t\t\t\t\ttask_finished, pp_cb);\n+\n+\ttrace2_region_leave(tr2_category, tr2_label, NULL);\n+\n+\treturn result;\n+}\n+\ndiff --git a/run-command.h b/run-command.h\nindex 68f5369fc2..91ac29102d 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -10,6 +10,12 @@ struct child_process {\n \tstruct argv_array args;\n \tstruct argv_array env_array;\n \tpid_t pid;\n+\n+\tint trace2_child_id;\n+\tuint64_t trace2_child_us_start;\n+\tconst char *trace2_child_class;\n+\tconst char *trace2_hook_name;\n+\n \t/*\n \t * Using .in, .out, .err:\n \t * - Specify 0 for no redirections (child inherits stdin, stdout,\n@@ -73,12 +79,14 @@ extern int run_hook_ve(const char *const *env, const char *name, va_list args);\n #define RUN_USING_SHELL 16\n #define RUN_CLEAN_ON_EXIT 32\n int run_command_v_opt(const char **argv, int opt);\n-\n+int run_command_v_opt_tr2(const char **argv, int opt, const char *tr2_class);\n /*\n  * env (the environment) is to be formatted like environ: \"VAR=VALUE\".\n  * To unset an environment variable use just \"VAR\".\n  */\n int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env);\n+int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir, const char *const *env,\n+\t\t\t\t const char *tr2_class);\n \n /**\n  * Execute the given command, sending \"in\" to its stdin, and capturing its\n@@ -220,5 +228,12 @@ int run_processes_parallel(int n,\n \t\t\t   start_failure_fn,\n \t\t\t   task_finished_fn,\n \t\t\t   void *pp_cb);\n+int run_processes_parallel_tr2(int n,\n+\t\t\t       get_next_task_fn,\n+\t\t\t       start_failure_fn,\n+\t\t\t       task_finished_fn,\n+\t\t\t       void *pp_cb,\n+\t\t\t       const char *tr2_category,\n+\t\t\t       const char *tr2_label);\n \n #endif\ndiff --git a/sh-i18n--envsubst.c b/sh-i18n--envsubst.c\nindex 09c6b445b9..2296ba1a2d 100644\n--- a/sh-i18n--envsubst.c\n+++ b/sh-i18n--envsubst.c\n@@ -14,6 +14,7 @@\n  */\n \n #include \"git-compat-util.h\"\n+#include \"trace2.h\"\n \n /* Substitution of environment variables in shell format strings.\n    Copyright (C) 2003-2007 Free Software Foundation, Inc.\n@@ -67,6 +68,8 @@ cmd_main (int argc, const char *argv[])\n   /* Default values for command line options.  */\n   /* unsigned short int show_variables = 0; */\n \n+  trace2_cmd_verb(\"sh-i18n--envsubst\");\n+\n   switch (argc)\n \t{\n \tcase 1:\ndiff --git a/submodule.c b/submodule.c\nindex 6415cc5580..acc96e133b 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1374,11 +1374,12 @@ int fetch_populated_submodules(struct repository *r,\n \t/* default value, \"--submodule-prefix\" and its value are added later */\n \n \tcalculate_changed_submodule_paths(r);\n-\trun_processes_parallel(max_parallel_jobs,\n-\t\t\t       get_next_submodule,\n-\t\t\t       fetch_start_failure,\n-\t\t\t       fetch_finish,\n-\t\t\t       &spf);\n+\trun_processes_parallel_tr2(max_parallel_jobs,\n+\t\t\t\t   get_next_submodule,\n+\t\t\t\t   fetch_start_failure,\n+\t\t\t\t   fetch_finish,\n+\t\t\t\t   &spf,\n+\t\t\t\t   \"submodule\", \"parallel/fetch\");\n \n \targv_array_clear(&spf.args);\n out:\ndiff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c\nindex 47fee660b8..5318681739 100644\n--- a/t/helper/test-parse-options.c\n+++ b/t/helper/test-parse-options.c\n@@ -2,6 +2,7 @@\n #include \"cache.h\"\n #include \"parse-options.h\"\n #include \"string-list.h\"\n+#include \"trace2.h\"\n \n static int boolean = 0;\n static int integer = 0;\n@@ -153,6 +154,8 @@ int cmd__parse_options(int argc, const char **argv)\n \tint i;\n \tint ret = 0;\n \n+\ttrace2_cmd_verb(\"_parse_\");\n+\n \targc = parse_options(argc, (const char **)argv, prefix, options, usage, 0);\n \n \tif (length_cb.called) {\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex bfb195b1a8..85d1f812fe 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -1,5 +1,6 @@\n #include \"git-compat-util.h\"\n #include \"test-tool.h\"\n+#include \"trace2.h\"\n \n struct test_cmd {\n \tconst char *name;\n@@ -78,6 +79,8 @@ int cmd_main(int argc, const char **argv)\n \t\tif (!strcmp(cmds[i].name, argv[1])) {\n \t\t\targv++;\n \t\t\targc--;\n+\t\t\ttrace2_cmd_verb(cmds[i].name);\n+\t\t\ttrace2_cmd_list_config();\n \t\t\treturn cmds[i].fn(argc, argv);\n \t\t}\n \t}\ndiff --git a/t/t0001-init.sh b/t/t0001-init.sh\nindex 42a263cada..5e27604b24 100755\n--- a/t/t0001-init.sh\n+++ b/t/t0001-init.sh\n@@ -93,6 +93,7 @@ test_expect_success 'No extra GIT_* on alias scripts' '\n \t\tsed -n \\\n \t\t\t-e \"/^GIT_PREFIX=/d\" \\\n \t\t\t-e \"/^GIT_TEXTDOMAINDIR=/d\" \\\n+\t\t\t-e \"/^GIT_TR2_PARENT/d\" \\\n \t\t\t-e \"/^GIT_/s/=.*//p\" |\n \t\tsort\n \tEOF\ndiff --git a/trace2.c b/trace2.c\nnew file mode 100644\nindex 0000000000..72af89ba73\n--- /dev/null\n+++ b/trace2.c\n@@ -0,0 +1,809 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"json-writer.h\"\n+#include \"quote.h\"\n+#include \"run-command.h\"\n+#include \"sigchain.h\"\n+#include \"thread-utils.h\"\n+#include \"version.h\"\n+#include \"trace2/tr2_cfg.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_sid.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+#include \"trace2/tr2_verb.h\"\n+\n+static int trace2_enabled;\n+\n+static int tr2_next_child_id; /* modify under lock */\n+static int tr2_next_exec_id; /* modify under lock */\n+static int tr2_next_repo_id = 1; /* modify under lock. zero is reserved */\n+\n+/*\n+ * A table of the builtin TRACE2 targets.  Each of these may be independently\n+ * enabled or disabled.  Each TRACE2 API method will try to write an event to\n+ * *each* of the enabled targets.\n+ */\n+static struct tr2_tgt *tr2_tgt_builtins[] =\n+{\n+\t&tr2_tgt_normal,\n+\t&tr2_tgt_perf,\n+\t&tr2_tgt_event,\n+\tNULL\n+};\n+\n+#define for_each_builtin(j, tgt_j)\t\t\t\\\n+\tfor (j = 0, tgt_j = tr2_tgt_builtins[j];\t\\\n+\t     tgt_j;\t\t\t\t\t\\\n+\t     j++, tgt_j = tr2_tgt_builtins[j])\n+\n+#define for_each_wanted_builtin(j, tgt_j)\t\\\n+\tfor_each_builtin(j, tgt_j)\t\t\\\n+\tif (tr2_dst_trace_want(tgt_j->pdst))\n+\n+/*\n+ * Force (rather than lazily) initialize any of the requested\n+ * builtin TRACE2 targets at startup (and before we've seen an\n+ * actual TRACE2 event call) so we can see if we need to setup\n+ * the TR2 and TLS machinery.\n+ *\n+ * Return the number of builtin targets enabled.\n+ */\n+static int tr2_tgt_want_builtins(void)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tint sum = 0;\n+\n+\tfor_each_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_init())\n+\t\t\tsum++;\n+\t}\n+\n+\treturn sum;\n+}\n+\n+/*\n+ * Properly terminate each builtin target.  Give each target\n+ * a chance to write a summary event and/or flush if necessary\n+ * and then close the fd.\n+ */\n+static void tr2_tgt_disable_builtins(void)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tfor_each_builtin(j, tgt_j) {\n+\t\ttgt_j->pfn_term();\n+\t}\n+}\n+\n+static int tr2main_exit_code;\n+\n+/*\n+ * Our atexit routine should run after everything has finished.\n+ *\n+ * Note that events generated here might not actually appear if\n+ * we are writing to fd 1 or 2 and our atexit routine runs after\n+ * the pager's atexit routine (since it closes them to shutdown\n+ * the pipes).\n+ */\n+static void tr2main_atexit_handler(void)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Clear any unbalanced regions so that our atexit message\n+\t * does not appear nested.  This improves the appearance of\n+\t * the trace output if someone calls die(), for example.\n+\t */\n+\ttr2tls_pop_unwind_self();\n+\t\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_atexit)\n+\t\t\ttgt_j->pfn_atexit(us_elapsed_absolute,\n+\t\t\t\t\t  tr2main_exit_code);\n+\t}\n+\n+\ttr2_tgt_disable_builtins();\n+\n+\ttr2tls_release();\n+\ttr2_sid_release();\n+\ttr2_verb_release();\n+\ttr2_cfg_free_patterns();\n+\n+\ttrace2_enabled = 0;\n+}\n+\n+static void tr2main_signal_handler(int signo)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_signal)\n+\t\t\ttgt_j->pfn_signal(us_elapsed_absolute, signo);\n+\t}\n+\n+\tsigchain_pop(signo);\n+\traise(signo);\n+}\n+\n+void trace2_initialize_fl(const char *file, int line)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (trace2_enabled)\n+\t\treturn;\n+\n+\tif (!tr2_tgt_want_builtins())\n+\t\treturn;\n+\ttrace2_enabled = 1;\n+\n+\ttr2_sid_get();\n+\n+\tatexit(tr2main_atexit_handler);\n+\tsigchain_push(SIGPIPE, tr2main_signal_handler);\n+\ttr2tls_init();\n+\n+\t/*\n+\t * Emit 'version' message on each active builtin target.\n+\t */\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_version_fl)\n+\t\t\ttgt_j->pfn_version_fl(file, line);\n+\t}\n+}\n+\n+int trace2_is_enabled(void)\n+{\n+\treturn trace2_enabled;\n+}\n+\n+void trace2_cmd_start_fl(const char *file, int line, const char **argv)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_start_fl)\n+\t\t\ttgt_j->pfn_start_fl(file, line, argv);\n+\t}\n+}\n+\n+int trace2_cmd_exit_fl(const char *file, int line, int code)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tcode &= 0xff;\n+\n+\tif (!trace2_enabled)\n+\t\treturn code;\n+\n+\ttr2main_exit_code = code;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_exit_fl)\n+\t\t\ttgt_j->pfn_exit_fl(file, line, us_elapsed_absolute, code);\n+\t}\n+\n+\treturn code;\n+}\n+\n+void trace2_cmd_error_va_fl(const char *file, int line,\n+\t\t\t    const char *fmt, va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\t/*\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy (because an 'ap' can only be walked once).\n+\t */\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_error_va_fl)\n+\t\t\ttgt_j->pfn_error_va_fl(file, line, fmt, ap);\n+\t}\n+}\n+\n+void trace2_cmd_path_fl(const char *file, int line, const char *pathname)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_command_path_fl)\n+\t\t\ttgt_j->pfn_command_path_fl(file, line, pathname);\n+\t}\n+}\n+\n+void trace2_cmd_verb_fl(const char *file, int line, const char *command_verb)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tconst char *hierarchy;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\ttr2_verb_append_hierarchy(command_verb);\n+\thierarchy = tr2_verb_get_hierarchy();\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_command_verb_fl)\n+\t\t\ttgt_j->pfn_command_verb_fl(file, line, command_verb,\n+\t\t\t\t\t\t   hierarchy);\n+\t}\n+}\n+\n+void trace2_cmd_subverb_fl(const char *file, int line,\n+\t\t\t   const char *command_subverb)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_command_subverb_fl)\n+\t\t\ttgt_j->pfn_command_subverb_fl(file, line,\n+\t\t\t\t\t\t      command_subverb);\n+\t}\n+}\n+\n+void trace2_cmd_alias_fl(const char *file, int line,\n+\t\t\t const char *alias, const char **argv)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_alias_fl)\n+\t\t\ttgt_j->pfn_alias_fl(file, line, alias, argv);\n+\t}\n+}\n+\n+void trace2_cmd_list_config_fl(const char *file, int line)\n+{\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\ttr2_cfg_list_config_fl(file, line);\n+}\n+\n+void trace2_cmd_set_config_fl(const char *file, int line,\n+\t\t\t      const char *key, const char *value)\n+{\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\ttr2_cfg_set_fl(file, line, key, value);\n+}\n+\n+void trace2_child_start_fl(const char *file, int line,\n+\t\t\t   struct child_process *cmd)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tcmd->trace2_child_id = tr2tls_locked_increment(&tr2_next_child_id);\n+\tcmd->trace2_child_us_start = us_now;\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_child_start_fl)\n+\t\t\ttgt_j->pfn_child_start_fl(file, line,\n+\t\t\t\t\t\t  us_elapsed_absolute, cmd);\n+\t}\n+}\n+\n+void trace2_child_exit_fl(const char *file, int line,\n+\t\t\t  struct child_process *cmd,\n+\t\t\t  int child_exit_code)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_child;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\t\n+\tif (cmd->trace2_child_us_start)\n+\t\tus_elapsed_child = us_now - cmd->trace2_child_us_start;\n+\telse\n+\t\tus_elapsed_child = 0;\n+\t\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_child_exit_fl)\n+\t\t\ttgt_j->pfn_child_exit_fl(\n+\t\t\t\tfile, line, us_elapsed_absolute,\n+\t\t\t\tcmd->trace2_child_id,\n+\t\t\t\tcmd->pid,\n+\t\t\t\tchild_exit_code, us_elapsed_child);\n+\t}\n+}\n+\n+int trace2_exec_fl(const char *file, int line,\n+\t\t   const char *exe, const char **argv)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tint exec_id;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn -1;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\texec_id = tr2tls_locked_increment(&tr2_next_exec_id);\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_exec_fl)\n+\t\t\ttgt_j->pfn_exec_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t   exec_id, exe, argv);\n+\t}\n+\n+\treturn exec_id;\n+}\n+\n+void trace2_exec_result_fl(const char *file, int line, int exec_id, int code)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_exec_result_fl)\n+\t\t\ttgt_j->pfn_exec_result_fl(file, line,\n+\t\t\t\t\t\t  us_elapsed_absolute,\n+\t\t\t\t\t\t  exec_id, code);\n+\t}\n+}\n+\n+void trace2_thread_start_fl(const char *file, int line,\n+\t\t\t    const char *thread_name)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tif (tr2tls_is_main_thread())\n+\t{\n+\t\t/*\n+\t\t * We should only be called from the new thread's thread-proc,\n+\t\t * so this is technically a bug.  But in those cases where the\n+\t\t * main thread also runs the thread-proc function (or when we\n+\t\t * are built with threading disabled), we need to allow it.\n+\t\t *\n+\t\t * Convert this call to a region-enter so the nesting looks\n+\t\t * looks correct.  \n+\t\t */\n+\t\ttrace2_region_enter_printf_fl(file, line, NULL, NULL, NULL,\n+\t\t\t\t\t      \"thread-proc on main: %s\",\n+\t\t\t\t\t      thread_name);\n+\t\treturn;\n+\t}\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\ttr2tls_create_self(thread_name);\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_thread_start_fl)\n+\t\t\ttgt_j->pfn_thread_start_fl(file, line,\n+\t\t\t\t\t\t   us_elapsed_absolute);\n+\t}\n+}\n+\n+void trace2_thread_exit_fl(const char *file, int line)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_thread;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tif (tr2tls_is_main_thread())\n+\t{\n+\t\t/*\n+\t\t * We should only be called from the exiting thread's thread-proc,\n+\t\t * so this is technically a bug.  But in those cases where the\n+\t\t * main thread also runs the thread-proc function (or when we\n+\t\t * are built with threading disabled), we need to allow it.\n+\t\t *\n+\t\t * Convert this call to a region-leave so the nesting looks\n+\t\t * looks correct.  \n+\t\t */\n+\t\ttrace2_region_leave_printf_fl(file, line, NULL, NULL, NULL,\n+\t\t\t\t\t      \"thread-proc on main\");\n+\t\treturn;\n+\t}\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Clear any unbalanced regions and then get the relative time\n+\t * for the outer-most region (which we pushed when the thread\n+\t * started).  This gives us the run time of the thread.\n+\t */\n+\ttr2tls_pop_unwind_self();\n+\tus_elapsed_thread = tr2tls_region_elasped_self(us_now);\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_thread_exit_fl)\n+\t\t\ttgt_j->pfn_thread_exit_fl(file, line,\n+\t\t\t\t\t\t  us_elapsed_absolute,\n+\t\t\t\t\t\t  us_elapsed_thread);\n+\t}\n+\n+\ttr2tls_unset_self();\n+}\n+\n+void trace2_def_param_fl(const char *file, int line,\n+\t\t\t const char *param, const char *value)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_param_fl)\n+\t\t\ttgt_j->pfn_param_fl(file, line, param, value);\n+\t}\n+}\n+\n+void trace2_def_repo_fl(const char *file, int line,\n+\t\t\tstruct repository *repo)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tif (repo->trace2_repo_id)\n+\t\treturn;\n+\n+\trepo->trace2_repo_id = tr2tls_locked_increment(&tr2_next_repo_id);\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_repo_fl)\n+\t\t\ttgt_j->pfn_repo_fl(file, line, repo);\n+\t}\n+}\n+\n+void trace2_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category,\n+\t\t\t\t      const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Print the region-enter message at the current nesting\n+\t * (indentation) level and then push a new level.\n+\t *\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy.\n+\t */\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_region_enter_printf_va_fl)\n+\t\t\ttgt_j->pfn_region_enter_printf_va_fl(\n+\t\t\t\tfile, line, us_elapsed_absolute,\n+\t\t\t\tcategory, label, repo, fmt, ap);\n+\t}\n+\n+\ttr2tls_push_self(us_now);\n+}\n+\n+void trace2_region_enter_fl(const char *file, int line,\n+\t\t\t    const char *category,\n+\t\t\t    const char *label,\n+\t\t\t    const struct repository *repo)\n+{\n+\ttrace2_region_enter_printf_va_fl(file, line,\n+\t\t\t\t\t category, label, repo,\n+\t\t\t\t\t NULL, NULL);\n+}\n+\n+void trace2_region_enter_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category,\n+\t\t\t\t   const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_enter_printf_va_fl(file, line,\n+\t\t\t\t\t category, label, repo,\n+\t\t\t\t\t fmt, ap);\n+\tva_end(ap);\n+}\n+\n+#ifndef HAVE_VARIADIC_MACROS\n+void trace2_region_enter_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo,\n+\t\t\t\tconst char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_enter_printf_va_fl(NULL, 0,\n+\t\t\t\t\t category, label, repo,\n+\t\t\t\t\t fmt, ap);\n+\tva_end(ap);\n+}\n+#endif\n+\n+void trace2_region_leave_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category,\n+\t\t\t\t      const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_region;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Get the elapsed time in the current region before we\n+\t * pop it off the stack.  Pop the stack.  And then print\n+\t * the perf message at the new (shallower) level so that\n+\t * it lines up with the corresponding push/enter.\n+\t */\n+\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n+\t\n+\ttr2tls_pop_self();\n+\n+\t/*\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy.\n+\t */\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_region_leave_printf_va_fl)\n+\t\t\ttgt_j->pfn_region_leave_printf_va_fl(\n+\t\t\t\tfile, line, us_elapsed_absolute,\n+\t\t\t\tus_elapsed_region,\n+\t\t\t\tcategory, label, repo,\n+\t\t\t\tfmt, ap);\n+\t}\n+}\n+\n+void trace2_region_leave_fl(const char *file, int line,\n+\t\t\t    const char *category,\n+\t\t\t    const char *label,\n+\t\t\t    const struct repository *repo)\n+{\n+\ttrace2_region_leave_printf_va_fl(file, line,\n+\t\t\t\t\t category, label, repo,\n+\t\t\t\t\t NULL, NULL);\n+}\n+\n+void trace2_region_leave_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category,\n+\t\t\t\t   const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_leave_printf_va_fl(file, line,\n+\t\t\t\t\t category, label, repo,\n+\t\t\t\t\t fmt, ap);\n+\tva_end(ap);\n+}\n+\n+#ifndef HAVE_VARIADIC_MACROS\n+void trace2_region_leave_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo,\n+\t\t\t\tconst char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_leave_printf_va_fl(NULL, 0,\n+\t\t\t\t\t category, label, repo,\n+\t\t\t\t\t fmt, ap);\n+\tva_end(ap);\n+}\n+#endif\n+\n+void trace2_data_string_fl(const char *file, int line,\n+\t\t\t   const char *category,\n+\t\t\t   const struct repository *repo,\n+\t\t\t   const char *key,\n+\t\t\t   const char *value)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_region;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_data_fl)\n+\t\t\ttgt_j->pfn_data_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t   us_elapsed_region,\n+\t\t\t\t\t   category, repo, key, value);\n+\t}\n+}\n+\n+void trace2_data_intmax_fl(const char *file, int line,\n+\t\t\t   const char *category,\n+\t\t\t   const struct repository *repo,\n+\t\t\t   const char *key,\n+\t\t\t   intmax_t value)\n+{\n+\tstruct strbuf buf_string = STRBUF_INIT;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tstrbuf_addf(&buf_string, \"%\"PRIdMAX, value);\n+\ttrace2_data_string_fl(file, line, category, repo, key, buf_string.buf);\n+\tstrbuf_release(&buf_string);\n+}\n+\n+void trace2_data_json_fl(const char *file, int line,\n+\t\t\t const char *category,\n+\t\t\t const struct repository *repo,\n+\t\t\t const char *key,\n+\t\t\t const struct json_writer *value)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_region;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n+\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_data_fl)\n+\t\t\ttgt_j->pfn_data_json_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t\tus_elapsed_region,\n+\t\t\t\t\t\tcategory, repo, key, value);\n+\t}\n+}\n+\n+void trace2_printf_va_fl(const char *file, int line,\n+\t\t\t const char *fmt, va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy.\n+\t */\n+\tfor_each_wanted_builtin(j, tgt_j) {\n+\t\tif (tgt_j->pfn_printf_va_fl)\n+\t\t\ttgt_j->pfn_printf_va_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t\tfmt, ap);\n+\t}\n+}\n+\n+void trace2_printf_fl(const char *file, int line,\n+\t\t      const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_printf_va_fl(file, line, fmt, ap);\n+\tva_end(ap);\n+}\n+\n+#ifndef HAVE_VARIADIC_MACROS\n+void trace2_printf(const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_printf_va_fl(NULL, 0, fmt, ap);\n+\tva_end(ap);\n+}\n+#endif\ndiff --git a/trace2.h b/trace2.h\nnew file mode 100644\nindex 0000000000..7ab9f355f3\n--- /dev/null\n+++ b/trace2.h\n@@ -0,0 +1,390 @@\n+#ifndef TRACE2_H\n+#define TRACE2_H\n+\n+struct child_process;\n+struct repository;\n+struct json_writer;\n+\n+/*\n+ * The public TRACE2 routines are grouped into the following groups:\n+ *\n+ * [] trace2_initialize -- initialization.\n+ * [] trace2_cmd_*      -- emit command/control messages.\n+ * [] trace2_child*     -- emit child start/stop messages.\n+ * [] trace2_exec*      -- emit exec start/stop messages.\n+ * [] trace2_thread*    -- emit thread start/stop messages.\n+ * [] trace2_def*       -- emit definition/parameter mesasges.\n+ * [] trace2_region*    -- emit region nesting messages.\n+ * [] trace2_data*      -- emit region/thread/repo data messages.\n+ * [] trace2_printf*    -- legacy trace[1] messages.\n+ */\n+\n+/*\n+ * Initialize TRACE2 tracing facility if any of the builtin TRACE2\n+ * targets are enabled in the environment.  Emits a 'version' event.\n+ *\n+ * Cleanup/Termination is handled automatically by a registered\n+ * atexit() routine.\n+ */\n+void trace2_initialize_fl(const char *file, int line);\n+\n+#define trace2_initialize() trace2_initialize_fl(__FILE__, __LINE__)\n+\n+/*\n+ * Return true if trace2 is enabled.\n+ */\n+int trace2_is_enabled(void);\n+\n+/*\n+ * Emit a 'start' event with the original (unmodified) argv.\n+ */\n+void trace2_cmd_start_fl(const char *file, int line, const char **argv);\n+\n+#define trace2_cmd_start(argv) trace2_cmd_start_fl(__FILE__, __LINE__, (argv))\n+\n+/*\n+ * Emit an 'exit' event.\n+ *\n+ * Write the exit-code that will be passed to exit() or returned\n+ * from main().\n+ *\n+ * Use this prior to actually calling exit().\n+ * See \"#define exit()\" in git-compat-util.h\n+ */\n+int trace2_cmd_exit_fl(const char *file, int line, int code);\n+\n+#define trace2_cmd_exit(code) (trace2_cmd_exit_fl(__FILE__, __LINE__, (code)))\n+\n+/*\n+ * Emit an 'error' event.\n+ *\n+ * Write an error message to the TRACE2 targets.\n+ */\n+void trace2_cmd_error_va_fl(const char *file, int line,\n+\t\t\t    const char *fmt, va_list ap);\n+\n+#define trace2_cmd_error_va(fmt, ap) \\\n+\ttrace2_cmd_error_va_fl(__FILE__, __LINE__, (fmt), (ap))\n+\n+/*\n+ * Emit a 'pathname' event with the canonical pathname of the current process\n+ * This gives post-processors a simple field to identify the command without\n+ * having to parse the argv.  For example, to distinguish invocations from\n+ * installed versus debug executables.\n+ */\n+void trace2_cmd_path_fl(const char *file, int line, const char *pathname);\n+\n+#define trace2_cmd_path(p) \\\n+\ttrace2_cmd_path_fl(__FILE__, __LINE__, (p))\n+\n+/*\n+ * Emit a 'cmd_verb' event with the canonical name of the (usually)\n+ * builtin command.  This gives post-processors a simple field\n+ * to identify the command verb without having to parse the argv.\n+ */\n+void trace2_cmd_verb_fl(const char *file, int line, const char *command_verb);\n+\n+#define trace2_cmd_verb(v) \\\n+\ttrace2_cmd_verb_fl(__FILE__, __LINE__, (v))\n+\n+/*\n+ * Emit a 'cmd_subverb' event to further describe the command being run.\n+ * For example, \"checkout\" can checkout a single file or can checkout a\n+ * different branch.  This gives post-processors a simple field to compare\n+ * equivalent commands without having to parse the argv.\n+ */\n+void trace2_cmd_subverb_fl(const char *file, int line,\n+\t\t\t   const char *command_subverb);\n+\n+#define trace2_cmd_subverb(sv) \\\n+\ttrace2_cmd_subverb_fl(__FILE__, __LINE__, (sv))\n+\n+/*\n+ * Emit an 'alias' expansion event.\n+ */\n+void trace2_cmd_alias_fl(const char *file, int line,\n+\t\t\t const char *alias, const char **argv);\n+\n+#define trace2_cmd_alias(alias, argv) \\\n+\ttrace2_cmd_alias_fl(__FILE__, __LINE__, (alias), (argv))\n+\n+/*\n+ * Emit one or more 'def_param' events for \"interesting\" configuration\n+ * settings.\n+ *\n+ * The environment variable \"GIT_TR2_CONFIG_PARAMS\" can be set to a\n+ * list of patterns considered important.  For example:\n+ *\n+ *    GIT_TR2_CONFIG_PARAMS=\"core.*,remote.*.url\"\n+ *\n+ * Note: this routine does a read-only iteration on the config data\n+ * (using read_early_config()), so it must not be called until enough\n+ * of the process environment has been established.  This includes the\n+ * location of the git and worktree directories, expansion of any \"-c\"\n+ * and \"-C\" command line options, and etc.\n+ */\n+void trace2_cmd_list_config_fl(const char *file, int line);\n+\n+#define trace2_cmd_list_config() \\\n+\ttrace2_cmd_list_config_fl(__FILE__, __LINE__)\n+\n+/*\n+ * Emit a \"def_param\" event for the given config key/value pair IF\n+ * we consider the key to be \"interesting\".\n+ *\n+ * Use this for new/updated config settings created/updated after\n+ * trace2_cmd_list_config() is called.\n+ */\n+void trace2_cmd_set_config_fl(const char *file, int line,\n+\t\t\t      const char *key, const char *value);\n+\n+#define trace2_cmd_set_config(k, v) \\\n+\ttrace2_cmd_set_config_fl(__FILE__, __LINE__, (k), (v))\n+\n+/*\n+ * Emit a 'child_start' event prior to spawning a child process.\n+ *\n+ * Before calling optionally set \"cmd->trace2_child_class\" to a string\n+ * describing the type of the child process.  For example, \"editor\" or\n+ * \"pager\".\n+ */\n+void trace2_child_start_fl(const char *file, int line,\n+\t\t\t   struct child_process *cmd);\n+\n+#define trace2_child_start(cmd) \\\n+\ttrace2_child_start_fl(__FILE__, __LINE__, (cmd))\n+\n+/*\n+ * Emit a 'child_exit' event after the child process completes. \n+ */\n+void trace2_child_exit_fl(const char *file, int line,\n+\t\t\t  struct child_process *cmd,\n+\t\t\t  int child_exit_code);\n+\n+#define trace2_child_exit(cmd, code) \\\n+\ttrace2_child_exit_fl(__FILE__, __LINE__, (cmd), (code))\n+\n+/*\n+ * Emit an 'exec' event prior to calling one of exec(), execv(),\n+ * execvp(), and etc.  On Unix-derived systems, this will be the\n+ * last event emitted for the current process, unless the exec\n+ * fails.  On Windows, exec() behaves like 'child_start' and a\n+ * waitpid(), so additional events may be emitted.\n+ *\n+ * Returns the \"exec_id\".\n+ */\n+int trace2_exec_fl(const char *file, int line,\n+\t\t   const char *exe, const char **argv);\n+\n+#define trace2_exec(exe, argv) \\\n+\ttrace2_exec_fl(__FILE__, __LINE__, (exe), (argv))\n+\n+/*\n+ * Emit an 'exec_result' when possible.  On Unix-derived systems,\n+ * this should be called after exec() returns (which only happens\n+ * when there is an error starting the new process).  On Windows,\n+ * this should be called after the waitpid().\n+ *\n+ * The \"exec_id\" should be the value returned from trace2_exec().\n+ */\n+void trace2_exec_result_fl(const char *file, int line, int exec_id, int code);\n+\n+#define trace2_exec_result(id, code) \\\n+\ttrace2_exec_result_fl(__FILE__, __LINE__, (id), (code))\n+\n+/*\n+ * Emit a 'thread_start' event.  This must be called from inside the\n+ * thread-proc to set up the trace2 TLS data for the thread.\n+ *\n+ * Thread names should be descriptive, like \"preload_index\".\n+ * Thread names will be decorated with an instance number automatically.\n+ */\n+void trace2_thread_start_fl(const char *file, int line,\n+\t\t\t\t const char *thread_name);\n+\n+#define trace2_thread_start(thread_name) \\\n+\ttrace2_thread_start_fl(__FILE__, __LINE__, (thread_name))\n+\n+/*\n+ * Emit a 'thread_exit' event.  This must be called from inside the\n+ * thread-proc to report thread-specific data and cleanup TLS data\n+ * for the thread.\n+ */\n+void trace2_thread_exit_fl(const char *file, int line);\n+\n+#define trace2_thread_exit() trace2_thread_exit_fl(__FILE__, __LINE__)\n+\n+/*\n+ * Emit a 'param' event.\n+ *\n+ * Write a \"<param> = <value>\" pair describing some aspect of the\n+ * run such as an important configuration setting or command line\n+ * option that significantly changes command behavior.\n+ */ \n+void trace2_def_param_fl(const char *file, int line,\n+\t\t\t const char *param, const char *value);\n+\n+#define trace2_def_param(param, value) \\\n+\ttrace2_def_param_fl(__FILE__, __LINE__, (param), (value))\n+\n+/*\n+ * Tell trace2 about a newly instantiated repo object and assign\n+ * a trace2-repo-id to be used in subsequent activity events.\n+ *\n+ * Emits a 'worktree' event for this repo instance.\n+ */\n+void trace2_def_repo_fl(const char *file, int line,\n+\t\t\tstruct repository *repo);\n+\n+#define trace2_def_repo(repo) \\\n+\ttrace2_def_repo_fl(__FILE__, __LINE__, repo)\n+\n+/*\n+ * Emit a 'region_enter' event for <category>.<label> with optional\n+ * repo-id and printf message.\n+ *\n+ * Enter a new nesting level on the current thread and remember the\n+ * current time.  This controls the indenting of all subsequent events\n+ * on this thread.\n+ */\n+void trace2_region_enter_fl(const char *file, int line,\n+\t\t\t    const char *category,\n+\t\t\t    const char *label,\n+\t\t\t    const struct repository *repo);\n+\n+#define trace2_region_enter(category, label, repo) \\\n+\ttrace2_region_enter_fl( \\\n+\t\t__FILE__, __LINE__, (category), (label), (repo))\n+\n+void trace2_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category,\n+\t\t\t\t      const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap);\n+\n+#define trace2_region_enter_printf_va(category, label, repo, fmt, ap) \\\n+\ttrace2_region_enter_printf_va_fl( \\\n+\t\t__FILE__, __LINE__, (category), (label), (repo), (fmt), (ap))\n+\n+void trace2_region_enter_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category, const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...);\n+\n+#ifdef HAVE_VARIADIC_MACROS\n+#define trace2_region_enter_printf(category, label, repo, ...) \\\n+\ttrace2_region_enter_printf_fl( \\\n+\t\t__FILE__, __LINE__, (category), (label), (repo), __VA_ARGS__)\n+#else\n+__attribute__((format (region_enter_printf, 4, 5)))\n+void trace2_region_enter_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo,\n+\t\t\t\tconst char *fmt, ...);\n+#endif\n+\n+/*\n+ * Emit a 'region_leave' event for <category>.<label> with optional\n+ * repo-id and printf message.\n+ *\n+ * Leave current nesting level and report the elapsed time spent\n+ * in this nesting level.\n+ */\n+void trace2_region_leave_fl(const char *file, int line,\n+\t\t\t    const char *category,\n+\t\t\t    const char *label,\n+\t\t\t    const struct repository *repo);\n+\n+#define trace2_region_leave(category, label, repo) \\\n+\ttrace2_region_leave_fl( \\\n+\t\t__FILE__, __LINE__, (category), (label), (repo))\n+\n+void trace2_region_leave_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category,\n+\t\t\t\t      const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap);\n+\n+#define trace2_region_leave_printf_va(category, label, repo, fmt, ap) \\\n+\ttrace2_region_leave_printf_va_fl( \\\n+\t\t__FILE__, __LINE__, (category), (label), (repo), (fmt), (ap))\n+\n+void trace2_region_leave_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category, const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...);\n+\n+#ifdef HAVE_VARIADIC_MACROS\n+#define trace2_region_leave_printf(category, label, repo, ...) \\\n+\ttrace2_region_leave_printf_fl( \\\n+\t\t__FILE__, __LINE__, (category), (label), (repo), __VA_ARGS__)\n+#else\n+__attribute__((format (region_leave_printf, 4, 5)))\n+void trace2_region_leave_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo,\n+\t\t\t\tconst char *fmt, ...);\n+#endif\n+\n+/*\n+ * Emit a key-value pair 'data' event of the form <category>.<key> = <value>.\n+ * This event implicitly contains information about thread, nesting region,\n+ * and optional repo-id.\n+ *\n+ * On event-based TRACE2 targets, this generates a 'data' event suitable\n+ * for post-processing.  On printf-based TRACE2 targets, this is converted\n+ * into a fixed-format printf message.\n+ */\n+void trace2_data_string_fl(const char *file, int line,\n+\t\t\t   const char *category,\n+\t\t\t   const struct repository *repo,\n+\t\t\t   const char *key,\n+\t\t\t   const char *value);\n+\n+#define trace2_data_string(category, repo, key, value) \\\n+\ttrace2_data_string_fl( \\\n+\t\t__FILE__, __LINE__, (category), (repo), (key), (value))\n+\n+void trace2_data_intmax_fl(const char *file, int line,\n+\t\t\t   const char *category,\n+\t\t\t   const struct repository *repo,\n+\t\t\t   const char *key,\n+\t\t\t   intmax_t value);\n+\n+#define trace2_data_intmax(category, repo, key, value) \\\n+\ttrace2_data_intmax_fl( \\\n+\t\t__FILE__, __LINE__, (category), (repo), (key), (value))\n+\n+void trace2_data_json_fl(const char *file, int line,\n+\t\t\t const char *category,\n+\t\t\t const struct repository *repo,\n+\t\t\t const char *key,\n+\t\t\t const struct json_writer *jw);\n+\n+#define trace2_data_json(category, repo, key, value) \\\n+\ttrace2_data_json_fl( \\\n+\t\t__FILE__, __LINE__, (category), (repo), (key), (value))\n+\n+/*\n+ * Emit a 'printf' event.\n+ *\n+ * Write an arbitrary formatted message to the TRACE2 targets.  These\n+ * text messages should be considered as human-readable strings without\n+ * any formatting guidelines.  Post-processors may choose to ignore\n+ * them.\n+ */\n+void trace2_printf_va_fl(const char *file, int line,\n+\t\t\t const char *fmt, va_list ap);\n+\n+#define trace2_printf_va(fmt, ap) \\\n+\ttrace2_printf_va_fl(__FILE__, __LINE__, (fmt), (ap))\n+\n+void trace2_printf_fl(const char *file, int line, const char *fmt, ...);\n+\n+#ifdef HAVE_VARIADIC_MACROS\n+#define trace2_printf(...) \\\n+\ttrace2_printf_fl(__FILE__, __LINE__, __VA_ARGS__)\n+#else\n+__attribute__((format (printf, 1, 2)))\n+void trace2_printf(const char *fmt, ...);\n+#endif\n+\n+#endif /* TRACE2_H */\ndiff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c\nnew file mode 100644\nindex 0000000000..7b8291010f\n--- /dev/null\n+++ b/trace2/tr2_cfg.c\n@@ -0,0 +1,92 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"tr2_cfg.h\"\n+\n+#define TR2_ENVVAR_CFG_PARAM \"GIT_TR2_CONFIG_PARAMS\"\n+\n+static struct strbuf **tr2_cfg_patterns;\n+static int tr2_cfg_count_patterns;\n+static int tr2_cfg_loaded;\n+\n+/*\n+ * Parse a string containing a comma-delimited list of config keys\n+ * or wildcard patterns into a list of strbufs.\n+ */\n+static int tr2_cfg_load_patterns(void)\n+{\n+\tstruct strbuf **s;\n+\tconst char *envvar;\n+\n+\tif (tr2_cfg_loaded)\n+\t\treturn tr2_cfg_count_patterns;\n+\ttr2_cfg_loaded = 1;\n+\n+\tenvvar = getenv(TR2_ENVVAR_CFG_PARAM);\n+\tif (!envvar || !*envvar)\n+\t\treturn tr2_cfg_count_patterns;\n+\n+\ttr2_cfg_patterns = strbuf_split_buf(envvar, strlen(envvar), ',', -1);\n+\tfor (s = tr2_cfg_patterns; *s; s++) {\n+\t\tstruct strbuf *buf = *s;\n+\n+\t\tif (buf->len && buf->buf[buf->len - 1] == ',')\n+\t\t\tstrbuf_setlen(buf, buf->len - 1);\n+\t\tstrbuf_trim_trailing_newline(*s);\n+\t\tstrbuf_trim(*s);\n+\t}\n+\n+\ttr2_cfg_count_patterns = s - tr2_cfg_patterns;\n+\treturn tr2_cfg_count_patterns;\n+}\n+\n+void tr2_cfg_free_patterns(void)\n+{\n+\tif (tr2_cfg_patterns)\n+\t\tstrbuf_list_free(tr2_cfg_patterns);\n+\ttr2_cfg_count_patterns = 0;\n+\ttr2_cfg_loaded = 0;\n+}\n+\n+struct tr2_cfg_data\n+{\n+\tconst char *file;\n+\tint line;\n+};\n+\n+/*\n+ * See if the given config key matches any of our patterns of interest.\n+ */\n+static int tr2_cfg_cb(const char *key, const char *value, void *d)\n+{\n+\tstruct strbuf **s;\n+\tstruct tr2_cfg_data *data = (struct tr2_cfg_data *)d;\n+\n+\tfor (s = tr2_cfg_patterns; *s; s++) {\n+\t\tstruct strbuf *buf = *s;\n+\t\tint wm = wildmatch(buf->buf, key, WM_CASEFOLD);\n+\t\tif (wm == WM_MATCH) {\n+\t\t\ttrace2_def_param_fl(data->file, data->line, key, value);\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void tr2_cfg_list_config_fl(const char *file, int line)\n+{\n+\tstruct tr2_cfg_data data = { file, line };\n+\n+\tif (tr2_cfg_load_patterns() > 0)\n+\t\tread_early_config(tr2_cfg_cb, &data);\n+}\n+\n+void tr2_cfg_set_fl(const char *file, int line,\n+\t\t    const char *key, const char *value)\n+{\n+\tstruct tr2_cfg_data data = { file, line };\n+\n+\tif (tr2_cfg_load_patterns() > 0)\n+\t\ttr2_cfg_cb(key, value, &data);\n+}\n+\ndiff --git a/trace2/tr2_cfg.h b/trace2/tr2_cfg.h\nnew file mode 100644\nindex 0000000000..0e7ca4eace\n--- /dev/null\n+++ b/trace2/tr2_cfg.h\n@@ -0,0 +1,19 @@\n+#ifndef TR2_CFG_H\n+#define TR2_CFG_H\n+\n+/*\n+ * Iterate over all config settings and emit 'def_param' events for the\n+ * \"interesting\" ones to TRACE2.\n+ */\n+void tr2_cfg_list_config_fl(const char *file, int line);\n+\n+/*\n+ * Emit a \"def_param\" event for the given key/value pair IF we consider\n+ * the key to be \"interesting\".\n+ */\n+void tr2_cfg_set_fl(const char *file, int line,\n+\t\t    const char *key, const char *value);\n+\n+void tr2_cfg_free_patterns(void);\n+\n+#endif /* TR2_CFG_H */\ndiff --git a/trace2/tr2_dst.c b/trace2/tr2_dst.c\nnew file mode 100644\nindex 0000000000..485b2f8851\n--- /dev/null\n+++ b/trace2/tr2_dst.c\n@@ -0,0 +1,90 @@\n+#include \"cache.h\"\n+#include \"trace2/tr2_dst.h\"\n+\n+void tr2_dst_trace_disable(struct tr2_dst *dst)\n+{\n+\tif (dst->need_close)\n+\t\tclose(dst->fd);\n+\tdst->fd = 0;\n+\tdst->initialized = 1;\n+\tdst->need_close = 0;\n+}\n+\n+int tr2_dst_get_trace_fd(struct tr2_dst *dst)\n+{\n+\tconst char *trace;\n+\n+\t/* don't open twice */\n+\tif (dst->initialized)\n+\t\treturn dst->fd;\n+\n+\ttrace = getenv(dst->env_var_name);\n+\n+\tif (!trace || !strcmp(trace, \"\") ||\n+\t    !strcmp(trace, \"0\") || !strcasecmp(trace, \"false\"))\n+\t\tdst->fd = 0;\n+\telse if (!strcmp(trace, \"1\") || !strcasecmp(trace, \"true\"))\n+\t\tdst->fd = STDERR_FILENO;\n+\telse if (strlen(trace) == 1 && isdigit(*trace))\n+\t\tdst->fd = atoi(trace);\n+\telse if (is_absolute_path(trace)) {\n+\t\tint fd = open(trace, O_WRONLY | O_APPEND | O_CREAT, 0666);\n+\t\tif (fd == -1) {\n+\t\t\t/*\n+\t\t\t * Silently eat the error and disable tracing on this\n+\t\t\t * destination.  This will cause us to lose events,\n+\t\t\t * but that is better than causing a stream of warnings\n+\t\t\t * for each git command they run.\n+\t\t\t *\n+\t\t\t * warning(\"could not open '%s' for tracing: %s\",\n+\t\t\t *         trace, strerror(errno));\n+\t\t\t */\n+\t\t\ttr2_dst_trace_disable(dst);\n+\t\t} else {\n+\t\t\tdst->fd = fd;\n+\t\t\tdst->need_close = 1;\n+\t\t}\n+\t} else {\n+\t\twarning(\"unknown trace value for '%s': %s\\n\"\n+\t\t\t\"         If you want to trace into a file, then please set %s\\n\"\n+\t\t\t\"         to an absolute pathname (starting with /)\",\n+\t\t\tdst->env_var_name, trace, dst->env_var_name);\n+\t\ttr2_dst_trace_disable(dst);\n+\t}\n+\n+\tdst->initialized = 1;\n+\treturn dst->fd;\n+}\n+\n+int tr2_dst_trace_want(struct tr2_dst *dst)\n+{\n+\treturn !!tr2_dst_get_trace_fd(dst);\n+}\n+\n+void tr2_dst_write_line(struct tr2_dst *dst, struct strbuf *buf_line)\n+{\n+\tstrbuf_complete_line(buf_line); /* ensure final NL on buffer */\n+\n+\t/*\n+\t * We do not use write_in_full() because we do not want\n+\t * a short-write to try again.  We are using O_APPEND mode\n+\t * files and the kernel handles the atomic seek+write. If\n+\t * another thread or git process is concurrently writing to\n+\t * this fd or file, our remainder-write may not be contiguous\n+\t * with our initial write of this message.  And that will\n+\t * confuse readers.  So just don't bother.\n+\t *\n+\t * It is assumed that TRACE2 messages are short enough that\n+\t * the system can write them in 1 attempt and we won't see\n+\t * a short-write.\n+\t *\n+\t * If we get an IO error, just close the trace dst.\n+\t */\n+\n+\tif (write(tr2_dst_get_trace_fd(dst),\n+\t\t  buf_line->buf, buf_line->len) < 0) {\n+\t\twarning(\"unable to write trace for '%s': %s\",\n+\t\t\tdst->env_var_name, strerror(errno));\n+\t\ttr2_dst_trace_disable(dst);\n+\t}\n+}\ndiff --git a/trace2/tr2_dst.h b/trace2/tr2_dst.h\nnew file mode 100644\nindex 0000000000..3eea3f2d1a\n--- /dev/null\n+++ b/trace2/tr2_dst.h\n@@ -0,0 +1,34 @@\n+#ifndef TR2_DST_H\n+#define TR2_DST_H\n+\n+struct tr2_dst {\n+\tconst char * const env_var_name;\n+\tint fd;\n+\tunsigned int initialized : 1;\n+\tunsigned int  need_close : 1;\n+};\n+\n+/*\n+ * Disable TRACE2 on the destination.  In TRACE2 a destination (DST)\n+ * wraps a file descriptor; it is associated with a TARGET which\n+ * defines the formatting.\n+ */\n+void tr2_dst_trace_disable(struct tr2_dst *dst);\n+\n+/*\n+ * Return the file descriptor for the DST.\n+ * If 0, the dst is closed or disabled.\n+ */\n+int tr2_dst_get_trace_fd(struct tr2_dst *dst);\n+\n+/*\n+ * Return true if the DST is opened for writing.\n+ */\n+int tr2_dst_trace_want(struct tr2_dst *dst);\n+\n+/*\n+ * Write a single line/message to the trace file.\n+ */\n+void tr2_dst_write_line(struct tr2_dst *dst, struct strbuf *buf_line);\n+\n+#endif /* TR2_DST_H */\ndiff --git a/trace2/tr2_sid.c b/trace2/tr2_sid.c\nnew file mode 100644\nindex 0000000000..43e8a3e184\n--- /dev/null\n+++ b/trace2/tr2_sid.c\n@@ -0,0 +1,67 @@\n+#include \"cache.h\"\n+#include \"trace2/tr2_sid.h\"\n+\n+#define TR2_ENVVAR_PARENT_SID \"GIT_TR2_PARENT_SID\"\n+\n+static struct strbuf tr2sid_buf = STRBUF_INIT;\n+static int tr2sid_nr_git_parents;\n+\n+/*\n+ * Compute a \"unique\" session id (SID) for the current process.  This allows\n+ * all events from this process to have a single label (much like a PID).\n+ *\n+ * Export this into our environment so that all child processes inherit it.\n+ *\n+ * If we were started by another git instance, use our parent's SID as a\n+ * prefix.  (This lets us track parent/child relationships even if there\n+ * is an intermediate shell process.)\n+ *\n+ * Additionally, count the number of nested git processes.\n+ */\n+static void tr2_sid_compute(void)\n+{\n+\tuint64_t us_now;\n+\tconst char *parent_sid;\n+\n+\tif (tr2sid_buf.len)\n+\t\treturn;\n+\n+\tparent_sid = getenv(TR2_ENVVAR_PARENT_SID);\n+\tif (parent_sid && *parent_sid) {\n+\t\tconst char *p;\n+\t\tfor (p = parent_sid; *p; p++)\n+\t\t\tif (*p == '/')\n+\t\t\t\ttr2sid_nr_git_parents++;\n+\n+\t\tstrbuf_addstr(&tr2sid_buf, parent_sid);\n+\t\tstrbuf_addch(&tr2sid_buf, '/');\n+\t\ttr2sid_nr_git_parents++;\n+\t}\n+\n+\tus_now = getnanotime() / 1000;\n+\tstrbuf_addf(&tr2sid_buf, \"%\"PRIuMAX\"-%\"PRIdMAX,\n+\t\t    (uintmax_t)us_now, (intmax_t)getpid());\n+\n+\tsetenv(TR2_ENVVAR_PARENT_SID, tr2sid_buf.buf, 1);\n+}\n+\n+const char *tr2_sid_get(void)\n+{\n+\tif (!tr2sid_buf.len)\n+\t\ttr2_sid_compute();\n+\n+\treturn tr2sid_buf.buf;\n+}\n+\n+int tr2_sid_depth(void)\n+{\n+\tif (!tr2sid_buf.len)\n+\t\ttr2_sid_compute();\n+\n+\treturn tr2sid_nr_git_parents;\n+}\n+\n+void tr2_sid_release(void)\n+{\n+\tstrbuf_release(&tr2sid_buf);\n+}\ndiff --git a/trace2/tr2_sid.h b/trace2/tr2_sid.h\nnew file mode 100644\nindex 0000000000..9bef321708\n--- /dev/null\n+++ b/trace2/tr2_sid.h\n@@ -0,0 +1,18 @@\n+#ifndef TR2_SID_H\n+#define TR2_SID_H\n+\n+/*\n+ * Get our session id. Compute if necessary.\n+ */\n+const char *tr2_sid_get(void);\n+\n+/*\n+ * Get our process depth.  A top-level git process invoked from the\n+ * command line will have depth=0.  A child git process will have\n+ * depth=1 and so on.\n+ */\n+int tr2_sid_depth(void);\n+\n+void tr2_sid_release(void);\n+\n+#endif /* TR2_SID_H */\ndiff --git a/trace2/tr2_tbuf.c b/trace2/tr2_tbuf.c\nnew file mode 100644\nindex 0000000000..452b4d0520\n--- /dev/null\n+++ b/trace2/tr2_tbuf.c\n@@ -0,0 +1,32 @@\n+#include \"cache.h\"\n+#include \"tr2_tbuf.h\"\n+\n+void tr2_tbuf_local_time(struct tr2_tbuf *tb)\n+{\n+\tstruct timeval tv;\n+\tstruct tm tm;\n+\ttime_t secs;\n+\n+\tgettimeofday(&tv, NULL);\n+\tsecs = tv.tv_sec;\n+\tlocaltime_r(&secs, &tm);\n+\n+\txsnprintf(tb->buf, sizeof(tb->buf), \"%02d:%02d:%02d.%06ld\",\n+\t\t  tm.tm_hour, tm.tm_min, tm.tm_sec, (long)tv.tv_usec);\n+}\n+\n+void tr2_tbuf_utc_time(struct tr2_tbuf *tb)\n+{\n+\tstruct timeval tv;\n+\tstruct tm tm;\n+\ttime_t secs;\n+\n+\tgettimeofday(&tv, NULL);\n+\tsecs = tv.tv_sec;\n+\tgmtime_r(&secs, &tm);\n+\n+\txsnprintf(tb->buf, sizeof(tb->buf), \"%4d-%02d-%02d %02d:%02d:%02d.%06ld\",\n+\t\t  tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,\n+\t\t  tm.tm_hour, tm.tm_min, tm.tm_sec, (long)tv.tv_usec);\n+}\n+\ndiff --git a/trace2/tr2_tbuf.h b/trace2/tr2_tbuf.h\nnew file mode 100644\nindex 0000000000..9cdefa3957\n--- /dev/null\n+++ b/trace2/tr2_tbuf.h\n@@ -0,0 +1,23 @@\n+#ifndef TR2_TBUF_H\n+#define TR2_TBUF_H\n+\n+/*\n+ * A simple wrapper around a fixed buffer to avoid C syntax\n+ * quirks and the need to pass around an additional size_t\n+ * argument.\n+ */\n+struct tr2_tbuf {\n+\tchar buf[32];\n+};\n+\n+/*\n+ * Fill buffer with formatted local time string.\n+ */\n+void tr2_tbuf_local_time(struct tr2_tbuf *tb);\n+\n+/*\n+ * Fill buffer with formatted UTC time string.\n+ */\n+void tr2_tbuf_utc_time(struct tr2_tbuf *tb);\n+\n+#endif /* TR2_TBUF_H */\ndiff --git a/trace2/tr2_tgt.h b/trace2/tr2_tgt.h\nnew file mode 100644\nindex 0000000000..4fdf253b57\n--- /dev/null\n+++ b/trace2/tr2_tgt.h\n@@ -0,0 +1,126 @@\n+#ifndef TR2_TGT_H\n+#define TR2_TGT_H\n+\n+/*\n+ * Function prototypes for a TRACE2 \"target\" vtable.\n+ */\n+\n+typedef int (tr2_tgt_init_t)(void);\n+typedef void (tr2_tgt_term_t)(void);\n+\n+typedef void (tr2_tgt_evt_version_fl_t)\n+\t(const char *file, int line);\n+\n+typedef void (tr2_tgt_evt_start_fl_t)\n+\t(const char *file, int line, const char **argv);\n+typedef void (tr2_tgt_evt_exit_fl_t)\n+\t(const char *file, int line, uint64_t us_elapsed_absolute, int code);\n+typedef void (tr2_tgt_evt_signal_t)\n+\t(uint64_t us_elapsed_absolute, int signo);\n+typedef void (tr2_tgt_evt_atexit_t)\n+\t(uint64_t us_elapsed_absolute, int code);\n+\n+typedef void (tr2_tgt_evt_error_va_fl_t)\n+\t(const char *file, int line, const char *fmt, va_list ap);\n+\n+typedef void (tr2_tgt_evt_command_path_fl_t)\n+\t(const char *file, int line, const char *command_path);\n+typedef void (tr2_tgt_evt_command_verb_fl_t)\n+\t(const char *file, int line, const char *command_verb,\n+\t const char *hierarchy);\n+typedef void (tr2_tgt_evt_command_subverb_fl_t)\n+\t(const char *file, int line, const char *command_subverb);\n+\n+typedef void (tr2_tgt_evt_alias_fl_t)\n+\t(const char *file, int line, const char *alias, const char **argv);\n+\n+typedef void (tr2_tgt_evt_child_start_fl_t)\n+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t const struct child_process *cmd);\n+typedef void (tr2_tgt_evt_child_exit_fl_t)\n+\t(const char *file, int line, uint64_t us_elapsed_absolute, int cid,\n+\t int pid, int code, uint64_t us_elapsed_child);\n+\n+typedef void (tr2_tgt_evt_thread_start_fl_t)\n+\t(const char *file, int line, uint64_t us_elapsed_absolute);\n+typedef void (tr2_tgt_evt_thread_exit_fl_t)\n+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t uint64_t us_elapsed_thread);\n+\n+typedef void (tr2_tgt_evt_exec_fl_t)\n+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t int exec_id, const char *exe, const char **argv);\n+typedef void (tr2_tgt_evt_exec_result_fl_t)\n+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t int exec_id, int code);\n+\n+typedef void (tr2_tgt_evt_param_fl_t)\n+\t(const char *file, int line, const char *param, const char *value);\n+\n+typedef void (tr2_tgt_evt_repo_fl_t)\n+\t(const char *file, int line, const struct repository *repo);\n+\n+typedef void (tr2_tgt_evt_region_enter_printf_va_fl_t)\n+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t const char *category, const char *label, const struct repository *repo,\n+\t const char *fmt, va_list ap);\n+typedef void (tr2_tgt_evt_region_leave_printf_va_fl_t)\n+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t uint64_t us_elapsed_region, const char *category, const char *label,\n+\t const struct repository *repo, const char *fmt, va_list ap);\n+\n+typedef void (tr2_tgt_evt_data_fl_t)\n+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t uint64_t us_elapsed_region, const char *category,\n+\t const struct repository *repo, const char *key, const char *value);\n+typedef void (tr2_tgt_evt_data_json_fl_t)\n+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t uint64_t us_elapsed_region, const char *category,\n+\t const struct repository *repo,\n+\t const char *key, const struct json_writer *value);\n+\n+typedef void (tr2_tgt_evt_printf_va_fl_t)\n+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t const char *fmt, va_list ap);\n+\n+/*\n+ * \"vtable\" for a TRACE2 target.  Use NULL if a target does not want\n+ * to emit that message.\n+ */\n+struct tr2_tgt\n+{\n+\tstruct tr2_dst                          *pdst;\n+\n+\ttr2_tgt_init_t                          *pfn_init;\n+\ttr2_tgt_term_t                          *pfn_term;\n+\n+\ttr2_tgt_evt_version_fl_t                *pfn_version_fl;\n+\ttr2_tgt_evt_start_fl_t                  *pfn_start_fl;\n+\ttr2_tgt_evt_exit_fl_t                   *pfn_exit_fl;\n+\ttr2_tgt_evt_signal_t                    *pfn_signal;\n+\ttr2_tgt_evt_atexit_t                    *pfn_atexit;\n+\ttr2_tgt_evt_error_va_fl_t               *pfn_error_va_fl;\n+\ttr2_tgt_evt_command_path_fl_t           *pfn_command_path_fl;\n+\ttr2_tgt_evt_command_verb_fl_t           *pfn_command_verb_fl;\n+\ttr2_tgt_evt_command_subverb_fl_t        *pfn_command_subverb_fl;\n+\ttr2_tgt_evt_alias_fl_t                  *pfn_alias_fl;\n+\ttr2_tgt_evt_child_start_fl_t            *pfn_child_start_fl;\n+\ttr2_tgt_evt_child_exit_fl_t             *pfn_child_exit_fl;\n+\ttr2_tgt_evt_thread_start_fl_t           *pfn_thread_start_fl;\n+\ttr2_tgt_evt_thread_exit_fl_t            *pfn_thread_exit_fl;\n+\ttr2_tgt_evt_exec_fl_t                   *pfn_exec_fl;\n+\ttr2_tgt_evt_exec_result_fl_t            *pfn_exec_result_fl;\n+\ttr2_tgt_evt_param_fl_t                  *pfn_param_fl;\n+\ttr2_tgt_evt_repo_fl_t                   *pfn_repo_fl;\n+\ttr2_tgt_evt_region_enter_printf_va_fl_t *pfn_region_enter_printf_va_fl;\n+\ttr2_tgt_evt_region_leave_printf_va_fl_t *pfn_region_leave_printf_va_fl;\n+\ttr2_tgt_evt_data_fl_t                   *pfn_data_fl;\n+\ttr2_tgt_evt_data_json_fl_t              *pfn_data_json_fl;\n+\ttr2_tgt_evt_printf_va_fl_t              *pfn_printf_va_fl;\n+};\n+\n+extern struct tr2_tgt tr2_tgt_event;\n+extern struct tr2_tgt tr2_tgt_normal;\n+extern struct tr2_tgt tr2_tgt_perf;\n+\n+#endif /* TR2_TGT_H */\ndiff --git a/trace2/tr2_tgt_event.c b/trace2/tr2_tgt_event.c\nnew file mode 100644\nindex 0000000000..495171ae6d\n--- /dev/null\n+++ b/trace2/tr2_tgt_event.c\n@@ -0,0 +1,606 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"json-writer.h\"\n+#include \"run-command.h\"\n+#include \"version.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_tbuf.h\"\n+#include \"trace2/tr2_sid.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+static struct tr2_dst tr2dst_event  = { \"GIT_TR2_EVENT\", 0, 0, 0 };\n+\n+/*\n+ * The version number of the JSON data generated by the EVENT target\n+ * in this source file.  Update this if you make a significant change\n+ * to the JSON fields or message structure.  You probably do not need\n+ * to update this if you just add another call to one of the existing\n+ * TRACE2 API methods.\n+ */\n+#define TR2_EVENT_VERSION \"1\"\n+\n+/*\n+ * Region nesting limit for messages written to the event target.\n+ *\n+ * The \"region_enter\" and \"region_leave\" messages (especially recursive\n+ * messages such as those produced while diving the worktree or index)\n+ * are primarily intended for the performance target during debugging.\n+ *\n+ * Some of the outer-most messages, however, may be of interest to the\n+ * event target.  Set this environment variable to a larger integer for\n+ * more detail in the event target.\n+ */\n+#define TR2_ENVVAR_EVENT_NESTING \"GIT_TR2_EVENT_NESTING\"\n+static int tr2env_event_nesting_wanted = 2;\n+\n+/*\n+ * Set this environment variable to true to omit the <time>, <file>, and\n+ * <line> fields from most events.\n+ */\n+#define TR2_ENVVAR_EVENT_BRIEF \"GIT_TR2_EVENT_BRIEF\"\n+static int tr2env_event_brief;\n+\n+static int fn_init(void)\n+{\n+\tint want = tr2_dst_trace_want(&tr2dst_event);\n+\tint want_nesting;\n+\tint want_brief;\n+\tchar *nesting;\n+\tchar *brief;\n+\n+\tif (!want)\n+\t\treturn want;\n+\n+\tnesting = getenv(TR2_ENVVAR_EVENT_NESTING);\n+\tif (nesting && ((want_nesting = atoi(nesting)) > 0))\n+\t\ttr2env_event_nesting_wanted = want_nesting;\n+\n+\tbrief = getenv(TR2_ENVVAR_EVENT_BRIEF);\n+\tif (brief && ((want_brief = atoi(brief)) > 0))\n+\t\ttr2env_event_brief = want_brief;\n+\n+\treturn want;\n+}\n+\n+static void fn_term(void)\n+{\n+\ttr2_dst_trace_disable(&tr2dst_event);\n+}\n+\n+/*\n+ * Append common key-value pairs to the currently open JSON object.\n+ *     \"event:\"<event_name>\"\n+ *      \"sid\":\"<sid>\"\n+ *   \"thread\":\"<thread_name>\"\n+ *     \"time\":\"<time>\"\n+ *     \"file\":\"<filename>\"\n+ *     \"line\":<line_number>\n+ *     \"repo\":<repo_id>\n+ */\n+static void event_fmt_prepare(const char *event_name,\n+\t\t\t      const char *file, int line,\n+\t\t\t      const struct repository *repo,\n+\t\t\t      struct json_writer *jw)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tstruct tr2_tbuf tb_now;\n+\n+\tjw_object_string(jw, \"event\", event_name);\n+\tjw_object_string(jw, \"sid\", tr2_sid_get());\n+\tjw_object_string(jw, \"thread\", ctx->thread_name.buf);\n+\n+\t/*\n+\t * In brief mode, only emit <time> on these 2 event types.\n+\t */\n+\tif (!tr2env_event_brief ||\n+\t    !strcmp(event_name, \"version\") ||\n+\t    !strcmp(event_name, \"atexit\")) {\n+\t\ttr2_tbuf_utc_time(&tb_now);\n+\t\tjw_object_string(jw, \"time\", tb_now.buf);\n+\t}\n+\n+\tif (!tr2env_event_brief && file && *file) {\n+\t\tjw_object_string(jw, \"file\", file);\n+\t\tjw_object_intmax(jw, \"line\", line);\n+\t}\n+\n+\tif (repo)\n+\t\tjw_object_intmax(jw, \"repo\", repo->trace2_repo_id);\n+}\n+\n+static void fn_version_fl(const char *file, int line)\n+{\n+\tconst char *event_name = \"version\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"evt\", TR2_EVENT_VERSION);\n+\tjw_object_string(&jw, \"exe\", git_version_string);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_start_fl(const char *file, int line, const char **argv)\n+{\n+\tconst char *event_name = \"start\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tjw_array_argv(&jw, argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_exit_fl(const char *file, int line,\n+\t\t       uint64_t us_elapsed_absolute, int code)\n+{\n+\tconst char *event_name = \"exit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_signal(uint64_t us_elapsed_absolute, int signo)\n+{\n+\tconst char *event_name = \"signal\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, __FILE__, __LINE__, NULL, &jw);\n+\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\tjw_object_intmax(&jw, \"signo\", signo);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_atexit(uint64_t us_elapsed_absolute, int code)\n+{\n+\tconst char *event_name = \"atexit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, __FILE__, __LINE__, NULL, &jw);\n+\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void maybe_add_string_va(struct json_writer *jw,\n+\t\t\t\tconst char *field_name,\n+\t\t\t\tconst char *fmt, va_list ap)\n+{\n+\tif (fmt && *fmt && ap) {\n+\t\tva_list copy_ap;\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\n+\t\tva_copy(copy_ap, ap);\n+\t\tstrbuf_vaddf(&buf, fmt, copy_ap);\n+\t\tva_end(copy_ap);\n+\n+\t\tjw_object_string(jw, field_name, buf.buf);\n+\t\tstrbuf_release(&buf);\n+\t\treturn;\n+\t}\n+\n+\tif (fmt && *fmt) {\n+\t\tjw_object_string(jw, field_name, fmt);\n+\t\treturn;\n+\t}\n+}\n+\n+static void fn_error_va_fl(const char *file, int line,\n+\t\t\t   const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"error\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tmaybe_add_string_va(&jw, \"msg\", fmt, ap);\n+\t/*\n+\t * Also emit the format string as a field in case\n+\t * post-processors want to aggregate common error\n+\t * messages by type without argument fields (such\n+\t * as pathnames or branch names) cluttering it up.\n+\t */\n+\tif (fmt && *fmt)\n+\t\tjw_object_string(&jw, \"fmt\", fmt);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_command_path_fl(const char *file, int line,\n+\t\t\t       const char *pathname)\n+{\n+\tconst char *event_name = \"cmd_path\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"path\", pathname);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_command_verb_fl(const char *file, int line,\n+\t\t\t       const char *command_verb,\n+\t\t\t       const char *verb_hierarchy)\n+{\n+\tconst char *event_name = \"cmd_verb\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"name\", command_verb);\n+\tif (verb_hierarchy && *verb_hierarchy)\n+\t\tjw_object_string(&jw, \"hierarchy\", verb_hierarchy);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_command_subverb_fl(const char *file, int line,\n+\t\t\t       const char *command_subverb)\n+{\n+\tconst char *event_name = \"cmd_subverb\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"name\", command_subverb);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_alias_fl(const char *file, int line,\n+\t\t\tconst char *alias, const char **argv)\n+{\n+\tconst char *event_name = \"alias\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"alias\", alias);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tjw_array_argv(&jw, argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_child_start_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      const struct child_process *cmd)\n+{\n+\tconst char *event_name = \"child_start\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"child_id\", cmd->trace2_child_id);\n+\tif (cmd->trace2_hook_name) {\n+\t\tjw_object_string(&jw, \"child_class\", \"hook\");\n+\t\tjw_object_string(&jw, \"hook_name\", cmd->trace2_hook_name);\n+\t} else {\n+\t\tconst char *child_class = ((cmd->trace2_child_class) ?\n+\t\t\t\t\t   cmd->trace2_child_class : \"?\");\n+\t\tjw_object_string(&jw, \"child_class\", child_class);\n+\t}\n+\tif (cmd->dir)\n+\t\tjw_object_string(&jw, \"cd\", cmd->dir);\n+\tjw_object_bool(&jw, \"use_shell\", cmd->use_shell);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tif (cmd->git_cmd)\n+\t\tjw_array_string(&jw, \"git\");\n+\tjw_array_argv(&jw, cmd->argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_child_exit_fl(const char *file, int line,\n+\t\t\t     uint64_t us_elapsed_absolute,\n+\t\t\t     int cid, int pid, int code,\n+\t\t\t     uint64_t us_elapsed_child)\n+{\n+\tconst char *event_name = \"child_exit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_rel = (double)us_elapsed_child / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"child_id\", cid);\n+\tjw_object_intmax(&jw, \"pid\", pid);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\n+\tjw_release(&jw);\n+}\n+\n+static void fn_thread_start_fl(const char *file, int line,\n+\t\t\t       uint64_t us_elapsed_absolute)\n+{\n+\tconst char *event_name = \"thread_start\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_thread_exit_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      uint64_t us_elapsed_thread)\n+{\n+\tconst char *event_name = \"thread_exit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_rel = (double)us_elapsed_thread / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_exec_fl(const char *file, int line,\n+\t\t       uint64_t us_elapsed_absolute,\n+\t\t       int exec_id, const char *exe, const char **argv)\n+{\n+\tconst char *event_name = \"exec\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"exec_id\", exec_id);\n+\tif (exe)\n+\t\tjw_object_string(&jw, \"exe\", exe);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tjw_array_argv(&jw, argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_exec_result_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      int exec_id, int code)\n+{\n+\tconst char *event_name = \"exec_result\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"exec_id\", exec_id);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_param_fl(const char *file, int line,\n+\t\t\tconst char *param, const char *value)\n+{\n+\tconst char *event_name = \"def_param\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"param\", param);\n+\tjw_object_string(&jw, \"value\", value);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_repo_fl(const char *file, int line,\n+\t\t       const struct repository *repo)\n+{\n+\tconst char *event_name = \"def_repo\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\tjw_object_string(&jw, \"worktree\", repo->worktree);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t const char *category,\n+\t\t\t\t\t const char *label,\n+\t\t\t\t\t const struct repository *repo,\n+\t\t\t\t\t const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_enter\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(\n+\t\t\tevent_name, file, line, repo, &jw);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tif (category)\n+\t\t\tjw_object_string(&jw, \"category\", category);\n+\t\tif (label)\n+\t\t\tjw_object_string(&jw, \"label\", label);\n+\t\tmaybe_add_string_va(&jw, \"msg\", fmt, ap);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+static void fn_region_leave_printf_va_fl(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t uint64_t us_elapsed_region,\n+\t\t\t\t\t const char *category,\n+\t\t\t\t\t const char *label,\n+\t\t\t\t\t const struct repository *repo,\n+\t\t\t\t\t const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_leave\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\t\tdouble t_rel = (double)us_elapsed_region / 1000000.0;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tif (category)\n+\t\t\tjw_object_string(&jw, \"category\", category);\n+\t\tif (label)\n+\t\t\tjw_object_string(&jw, \"label\", label);\n+\t\tmaybe_add_string_va(&jw, \"msg\", fmt, ap);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+static void fn_data_fl(const char *file, int line,\n+\t\t       uint64_t us_elapsed_absolute,\n+\t\t       uint64_t us_elapsed_region,\n+\t\t       const char *category,\n+\t\t       const struct repository *repo,\n+\t\t       const char *key,\n+\t\t       const char *value)\n+{\n+\tconst char *event_name = \"data\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\t\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\t\tdouble t_rel = (double)us_elapsed_region / 1000000.0;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\t\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tjw_object_string(&jw, \"category\", category);\n+\t\tjw_object_string(&jw, \"key\", key);\n+\t\tjw_object_string(&jw, \"value\", value);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+static void fn_data_json_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute,\n+\t\t\t    uint64_t us_elapsed_region,\n+\t\t\t    const char *category,\n+\t\t\t    const struct repository *repo,\n+\t\t\t    const char *key,\n+\t\t\t    const struct json_writer *value)\n+{\n+\tconst char *event_name = \"data_json\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\t\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\t\tdouble t_rel = (double)us_elapsed_region / 1000000.0;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\t\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tjw_object_string(&jw, \"category\", category);\n+\t\tjw_object_string(&jw, \"key\", key);\n+\t\tjw_object_sub_jw(&jw, \"value\", value);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+struct tr2_tgt tr2_tgt_event =\n+{\n+\t&tr2dst_event,\n+\n+\tfn_init,\n+\tfn_term,\n+\n+\tfn_version_fl,\n+\tfn_start_fl,\n+\tfn_exit_fl,\n+\tfn_signal,\n+\tfn_atexit,\n+\tfn_error_va_fl,\n+\tfn_command_path_fl,\n+\tfn_command_verb_fl,\n+\tfn_command_subverb_fl,\n+\tfn_alias_fl,\n+\tfn_child_start_fl,\n+\tfn_child_exit_fl,\n+\tfn_thread_start_fl,\n+\tfn_thread_exit_fl,\n+\tfn_exec_fl,\n+\tfn_exec_result_fl,\n+\tfn_param_fl,\n+\tfn_repo_fl,\n+\tfn_region_enter_printf_va_fl,\n+\tfn_region_leave_printf_va_fl,\n+\tfn_data_fl,\n+\tfn_data_json_fl,\n+\tNULL, /* printf */\n+};\ndiff --git a/trace2/tr2_tgt_normal.c b/trace2/tr2_tgt_normal.c\nnew file mode 100644\nindex 0000000000..602eba5112\n--- /dev/null\n+++ b/trace2/tr2_tgt_normal.c\n@@ -0,0 +1,331 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"run-command.h\"\n+#include \"quote.h\"\n+#include \"version.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_tbuf.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+static struct tr2_dst tr2dst_normal = { \"GIT_TR2\", 0, 0, 0 };\n+\n+/*\n+ * Set this environment variable to true to omit the \"<time> <file>:<line>\"\n+ * fields from each line written to the builtin normal target.\n+ *\n+ * Unit tests may want to use this to help with testing.\n+ */\n+#define TR2_ENVVAR_NORMAL_BRIEF \"GIT_TR2_BRIEF\"\n+static int tr2env_normal_brief;\n+\n+#define TR2FMT_NORMAL_FL_WIDTH       (50)\n+\n+static int fn_init(void)\n+{\n+\tint want = tr2_dst_trace_want(&tr2dst_normal);\n+\tint want_brief;\n+\tchar *brief;\n+\n+\tif (!want)\n+\t\treturn want;\n+\n+\tbrief = getenv(TR2_ENVVAR_NORMAL_BRIEF);\n+\tif (brief && *brief &&\n+\t    ((want_brief = git_parse_maybe_bool(brief)) != -1))\n+\t\ttr2env_normal_brief = want_brief;\n+\n+\treturn want;\n+}\n+\n+static void fn_term(void)\n+{\n+\ttr2_dst_trace_disable(&tr2dst_normal);\n+}\n+\n+static void normal_fmt_prepare(const char *file, int line, struct strbuf *buf)\n+{\n+\tstrbuf_setlen(buf,0);\n+\n+\tif (!tr2env_normal_brief) {\n+\t\tstruct tr2_tbuf tb_now;\n+\n+\t\ttr2_tbuf_local_time(&tb_now);\n+\t\tstrbuf_addstr(buf, tb_now.buf);\n+\t\tstrbuf_addch(buf, ' ');\n+\n+\t\tif (file && *file)\n+\t\t\tstrbuf_addf(buf, \"%s:%d \", file, line);\n+\t\twhile (buf->len < TR2FMT_NORMAL_FL_WIDTH)\n+\t\t\tstrbuf_addch(buf, ' ');\n+\t}\n+}\n+\n+static void normal_io_write_fl(const char *file, int line,\n+\t\t\t       const struct strbuf *buf_payload)\n+{\n+\tstruct strbuf buf_line = STRBUF_INIT;\n+\n+\tnormal_fmt_prepare(file, line, &buf_line);\n+\tstrbuf_addbuf(&buf_line, buf_payload);\n+\ttr2_dst_write_line(&tr2dst_normal, &buf_line);\n+\tstrbuf_release(&buf_line);\n+}\n+\n+static void fn_version_fl(const char *file, int line)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"version %s\", git_version_string);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_start_fl(const char *file, int line, const char **argv)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"start \");\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exit_fl(const char *file, int line,\n+\t\t       uint64_t us_elapsed_absolute, int code)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"exit elapsed:%.6f code:%d\",\n+\t\t    elapsed, code);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_signal(uint64_t us_elapsed_absolute, int signo)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"signal elapsed:%.6f code:%d\",\n+\t\t    elapsed, signo);\n+\tnormal_io_write_fl(__FILE__, __LINE__, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_atexit(uint64_t us_elapsed_absolute, int code)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"atexit elapsed:%.6f code:%d\",\n+\t\t    elapsed, code);\n+\tnormal_io_write_fl(__FILE__, __LINE__, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void maybe_append_string_va(struct strbuf *buf,\n+\t\t\t\t   const char *fmt, va_list ap)\n+{\n+\tif (fmt && *fmt && ap) {\n+\t\tva_list copy_ap;\n+\n+\t\tva_copy(copy_ap, ap);\n+\t\tstrbuf_vaddf(buf, fmt, copy_ap);\n+\t\tva_end(copy_ap);\n+\t\treturn;\n+\t}\n+\n+\tif (fmt && *fmt) {\n+\t\tstrbuf_addstr(buf, fmt);\n+\t\treturn;\n+\t}\n+}\n+\n+static void fn_error_va_fl(const char *file, int line,\n+\t\t\t   const char *fmt, va_list ap)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"error \");\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_path_fl(const char *file, int line,\n+\t\t\t       const char *pathname)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"cmd_path %s\", pathname);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_verb_fl(const char *file, int line,\n+\t\t\t       const char *command_verb,\n+\t\t\t       const char *verb_hierarchy)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"cmd_verb %s\", command_verb);\n+\tif (verb_hierarchy && *verb_hierarchy)\n+\t\tstrbuf_addf(&buf_payload, \" (%s)\", verb_hierarchy);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_subverb_fl(const char *file, int line,\n+\t\t\t       const char *command_subverb)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"cmd_subverb %s\", command_subverb);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_alias_fl(const char *file, int line, const char *alias,\n+\t\t\tconst char **argv)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"alias %s ->\", alias);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_start_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      const struct child_process *cmd)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"child_start[%d] \", cmd->trace2_child_id);\n+\n+\tif (cmd->dir) {\n+\t\tstrbuf_addstr(&buf_payload, \" cd\");\n+\t\tsq_quote_buf_pretty(&buf_payload, cmd->dir);\n+\t\tstrbuf_addstr(&buf_payload, \"; \");\n+\t}\n+\n+\t/*\n+\t * TODO if (cmd->env) { Consider dumping changes to environment. }\n+\t * See trace_add_env() in run-command.c as used by original trace.c\n+\t */\n+\n+\tif (cmd->git_cmd)\n+\t\tstrbuf_addstr(&buf_payload, \"git\");\n+\tsq_quote_argv_pretty(&buf_payload, cmd->argv);\n+\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_exit_fl(const char *file, int line,\n+\t\t\t     uint64_t us_elapsed_absolute,\n+\t\t\t     int cid, int pid, int code,\n+\t\t\t     uint64_t us_elapsed_child)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_child / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"child_exit[%d] pid:%d code:%d elapsed:%.6f\",\n+\t\t    cid, pid, code, elapsed);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_fl(const char *file, int line,\n+\t\t       uint64_t us_elapsed_absolute,\n+\t\t       int exec_id, const char *exe, const char **argv)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"exec[%d] \", exec_id);\n+\tif (exe)\n+\t\tstrbuf_addstr(&buf_payload, exe);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_result_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      int exec_id, int code)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"exec_result[%d] code:%d\", exec_id, code);\n+\tif (code > 0)\n+\t\tstrbuf_addf(&buf_payload, \" err:%s\", strerror(code));\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_param_fl(const char *file, int line, const char *param,\n+\t\t\tconst char *value)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"def_param %s=%s\", param, value);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_repo_fl(const char *file, int line,\n+\t\t       const struct repository *repo)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"worktree \");\n+\tsq_quote_buf_pretty(&buf_payload, repo->worktree);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_printf_va_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute,\n+\t\t\t    const char *fmt, va_list ap)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+struct tr2_tgt tr2_tgt_normal =\n+{\n+\t&tr2dst_normal,\n+\n+\tfn_init,\n+\tfn_term,\n+\n+\tfn_version_fl,\n+\tfn_start_fl,\n+\tfn_exit_fl,\n+\tfn_signal,\n+\tfn_atexit,\n+\tfn_error_va_fl,\n+\tfn_command_path_fl,\n+\tfn_command_verb_fl,\n+\tfn_command_subverb_fl,\n+\tfn_alias_fl,\n+\tfn_child_start_fl,\n+\tfn_child_exit_fl,\n+\tNULL, /* thread_start */\n+\tNULL, /* thread_exit */\n+\tfn_exec_fl,\n+\tfn_exec_result_fl,\n+\tfn_param_fl,\n+\tfn_repo_fl,\n+\tNULL, /* region_enter */\n+\tNULL, /* region_leave */\n+\tNULL, /* data */\n+\tNULL, /* data_json */\n+\tfn_printf_va_fl,\n+};\ndiff --git a/trace2/tr2_tgt_perf.c b/trace2/tr2_tgt_perf.c\nnew file mode 100644\nindex 0000000000..011cf5d5fc\n--- /dev/null\n+++ b/trace2/tr2_tgt_perf.c\n@@ -0,0 +1,573 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"run-command.h\"\n+#include \"quote.h\"\n+#include \"version.h\"\n+#include \"json-writer.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_sid.h\"\n+#include \"trace2/tr2_tbuf.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+static struct tr2_dst tr2dst_perf   = { \"GIT_TR2_PERF\", 0, 0, 0 };\n+\n+/*\n+ * Set this environment variable to true to omit the \"<time> <file>:<line>\"\n+ * fields from each line written to the builtin performance target.\n+ *\n+ * Unit tests may want to use this to help with testing.\n+ */\n+#define TR2_ENVVAR_PERF_BRIEF \"GIT_TR2_PERF_BRIEF\"\n+static int tr2env_perf_brief;\n+\n+#define TR2FMT_PERF_FL_WIDTH       (50)\n+#define TR2FMT_PERF_MAX_EVENT_NAME (12)\n+#define TR2FMT_PERF_REPO_WIDTH     (4)\n+#define TR2FMT_PERF_CATEGORY_WIDTH (10)\n+\n+#define TR2_DOTS_BUFFER_SIZE (100)\n+#define TR2_INDENT (2)\n+#define TR2_INDENT_LENGTH(ctx) (((ctx)->nr_open_regions - 1) * TR2_INDENT)\n+\n+static struct strbuf dots = STRBUF_INIT;\n+\n+static int fn_init(void)\n+{\n+\tint want = tr2_dst_trace_want(&tr2dst_perf);\n+\tint want_brief;\n+\tchar *brief;\n+\n+\tif (!want)\n+\t\treturn want;\n+\n+\tstrbuf_addchars(&dots, '.', TR2_DOTS_BUFFER_SIZE);\n+\n+\tbrief = getenv(TR2_ENVVAR_PERF_BRIEF);\n+\tif (brief && *brief &&\n+\t    ((want_brief = git_parse_maybe_bool(brief)) != -1))\n+\t\ttr2env_perf_brief = want_brief;\n+\n+\treturn want;\n+}\n+\n+static void fn_term(void)\n+{\n+\ttr2_dst_trace_disable(&tr2dst_perf);\n+\n+\tstrbuf_release(&dots);\n+}\n+\n+/*\n+ * Format trace line prefix in human-readable classic format for\n+ * the performance target:\n+ *     \"[<time> [<file>:<line>] <bar>] <nr_parents> <bar>\n+ *         <thread_name> <bar> <event_name> <bar> [<repo>] <bar>\n+ *         [<elapsed_absolute>] [<elapsed_relative>] <bar>\n+ *         [<category>] <bar> [<dots>] \"\n+ */\n+static void perf_fmt_prepare(\n+\tconst char *event_name, struct tr2tls_thread_ctx *ctx,\n+\tconst char *file, int line, const struct repository *repo,\n+\tuint64_t *p_us_elapsed_absolute, uint64_t *p_us_elapsed_relative,\n+\tconst char *category, struct strbuf *buf)\n+{\n+\tint len;\n+\n+\tstrbuf_setlen(buf,0);\n+\n+\tif (!tr2env_perf_brief) {\n+\n+\t\tstruct tr2_tbuf tb_now;\n+\n+\t\ttr2_tbuf_local_time(&tb_now);\n+\t\tstrbuf_addstr(buf, tb_now.buf);\n+\t\tstrbuf_addch(buf, ' ');\n+\n+\t\tif (file && *file)\n+\t\t\tstrbuf_addf(buf, \"%s:%d \", file, line);\n+\t\twhile (buf->len < TR2FMT_PERF_FL_WIDTH)\n+\t\t\tstrbuf_addch(buf, ' ');\n+\n+\t\tstrbuf_addstr(buf, \"| \");\n+\t}\n+\n+\tstrbuf_addf(buf, \"d%d | \", tr2_sid_depth());\n+\tstrbuf_addf(buf, \"%-*s | %-*s | \",\n+\t\t    TR2_MAX_THREAD_NAME, ctx->thread_name.buf,\n+\t\t    TR2FMT_PERF_MAX_EVENT_NAME, event_name);\n+\n+\tlen = buf->len + TR2FMT_PERF_REPO_WIDTH;\n+\tif (repo)\n+\t\tstrbuf_addf(buf, \"r%d \", repo->trace2_repo_id);\n+\twhile (buf->len < len)\n+\t\tstrbuf_addch(buf, ' ' );\n+\tstrbuf_addstr(buf, \"| \");\n+\n+\tif (p_us_elapsed_absolute)\n+\t\tstrbuf_addf(buf, \"%9.6f | \",\n+\t\t\t    ((double)(*p_us_elapsed_absolute)) / 1000000.0);\n+\telse\n+\t\tstrbuf_addf(buf, \"%9s | \", \" \");\n+\n+\tif (p_us_elapsed_relative)\n+\t\tstrbuf_addf(buf, \"%9.6f | \",\n+\t\t\t    ((double)(*p_us_elapsed_relative)) / 1000000.0);\n+\telse\n+\t\tstrbuf_addf(buf, \"%9s | \", \" \");\n+\t\t\n+\tstrbuf_addf(buf, \"%-*s | \", TR2FMT_PERF_CATEGORY_WIDTH,\n+\t\t    (category ? category : \"\"));\n+\n+\tif (ctx->nr_open_regions > 0) {\n+\t\tint len_indent = TR2_INDENT_LENGTH(ctx);\n+\t\twhile (len_indent > dots.len) {\n+\t\t\tstrbuf_addbuf(buf, &dots);\n+\t\t\tlen_indent -= dots.len;\n+\t\t}\n+\t\tstrbuf_addf(buf, \"%.*s\", len_indent, dots.buf);\n+\t}\n+}\n+\n+static void perf_io_write_fl(\n+\tconst char *file, int line,\n+\tconst char *event_name,\n+\tconst struct repository *repo,\n+\tuint64_t *p_us_elapsed_absolute,\n+\tuint64_t *p_us_elapsed_relative,\n+\tconst char *category,\n+\tconst struct strbuf *buf_payload)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tstruct strbuf buf_line = STRBUF_INIT;\n+\n+\tperf_fmt_prepare(event_name, ctx, file, line, repo,\n+\t\t\t p_us_elapsed_absolute, p_us_elapsed_relative,\n+\t\t\t category, &buf_line);\n+\tstrbuf_addbuf(&buf_line, buf_payload);\n+\ttr2_dst_write_line(&tr2dst_perf, &buf_line);\n+\tstrbuf_release(&buf_line);\n+}\n+\n+static void fn_version_fl(const char *file, int line)\n+{\n+\tconst char *event_name = \"version\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, git_version_string);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_start_fl(const char *file, int line, const char **argv)\n+{\n+\tconst char *event_name = \"start\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exit_fl(const char *file, int line,\n+\t\t       uint64_t us_elapsed_absolute, int code)\n+{\n+\tconst char *event_name = \"exit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"code:%d\", code);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_signal(uint64_t us_elapsed_absolute, int signo)\n+{\n+\tconst char *event_name = \"signal\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"signo:%d\", signo);\n+\n+\tperf_io_write_fl(__FILE__, __LINE__, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_atexit(uint64_t us_elapsed_absolute, int code)\n+{\n+\tconst char *event_name = \"atexit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"code:%d\", code);\n+\n+\tperf_io_write_fl(__FILE__, __LINE__, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void maybe_append_string_va(struct strbuf *buf,\n+\t\t\t\t   const char *fmt, va_list ap)\n+{\n+\tif (fmt && *fmt && ap) {\n+\t\tva_list copy_ap;\n+\n+\t\tva_copy(copy_ap, ap);\n+\t\tstrbuf_vaddf(buf, fmt, copy_ap);\n+\t\tva_end(copy_ap);\n+\t\treturn;\n+\t}\n+\n+\tif (fmt && *fmt) {\n+\t\tstrbuf_addstr(buf, fmt);\n+\t\treturn;\n+\t}\n+}\n+\n+static void fn_error_va_fl(const char *file, int line,\n+\t\t\t   const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"error\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_path_fl(const char *file, int line,\n+\t\t\t       const char *pathname)\n+{\n+\tconst char *event_name = \"cmd_path\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, pathname);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_verb_fl(const char *file, int line,\n+\t\t\t       const char *command_verb,\n+\t\t\t       const char *verb_hierarchy)\n+{\n+\tconst char *event_name = \"cmd_verb\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, command_verb);\n+\tif (verb_hierarchy && *verb_hierarchy)\n+\t\tstrbuf_addf(&buf_payload, \" (%s)\", verb_hierarchy);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_subverb_fl(const char *file, int line,\n+\t\t\t       const char *command_subverb)\n+{\n+\tconst char *event_name = \"cmd_subverb\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, command_subverb);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_alias_fl(const char *file, int line, const char *alias,\n+\t\t\tconst char **argv)\n+{\n+\tconst char *event_name = \"alias\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"alias:%s argv:\", alias);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_start_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      const struct child_process *cmd)\n+{\n+\tconst char *event_name = \"child_start\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tif (cmd->trace2_hook_name) {\n+\t\tstrbuf_addf(&buf_payload, \"[ch%d] class:hook hook:%s\",\n+\t\t\t    cmd->trace2_child_id, cmd->trace2_hook_name);\n+\t} else {\n+\t\tconst char *child_class = ((cmd->trace2_child_class) ?\n+\t\t\t\t\t   cmd->trace2_child_class : \"?\");\n+\t\tstrbuf_addf(&buf_payload, \"[ch%d] class:%s\",\n+\t\t\t    cmd->trace2_child_id, child_class);\n+\t}\n+\n+\tif (cmd->dir) {\n+\t\tstrbuf_addstr(&buf_payload, \" cd:\");\n+\t\tsq_quote_buf_pretty(&buf_payload, cmd->dir);\n+\t}\n+\n+\tstrbuf_addstr(&buf_payload, \" argv:\");\n+\tif (cmd->git_cmd)\n+\t\tstrbuf_addstr(&buf_payload, \" git\");\n+\tsq_quote_argv_pretty(&buf_payload, cmd->argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_exit_fl(const char *file, int line,\n+\t\t\t     uint64_t us_elapsed_absolute,\n+\t\t\t     int cid, int pid, int code,\n+\t\t\t     uint64_t us_elapsed_child)\n+{\n+\tconst char *event_name = \"child_exit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"[ch%d] pid:%d code:%d\", cid, pid, code);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, &us_elapsed_child, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_thread_start_fl(const char *file, int line,\n+\t\t\t\t     uint64_t us_elapsed_absolute)\n+{\n+\tconst char *event_name = \"thread_start\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_thread_exit_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      uint64_t us_elapsed_thread)\n+{\n+\tconst char *event_name = \"thread_exit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, &us_elapsed_thread, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_fl(const char *file, int line,\n+\t\t       uint64_t us_elapsed_absolute,\n+\t\t       int exec_id, const char *exe, const char **argv)\n+{\n+\tconst char *event_name = \"exec\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"id:%d \", exec_id);\n+\tstrbuf_addstr(&buf_payload, \"argv:\");\n+\tif (exe)\n+\t\tstrbuf_addf(&buf_payload, \" %s\", exe);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_result_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      int exec_id, int code)\n+{\n+\tconst char *event_name = \"exec_result\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"id:%d code:%d\", exec_id, code);\n+\tif (code > 0)\n+\t\tstrbuf_addf(&buf_payload, \" err:%s\", strerror(code));\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_param_fl(const char *file, int line,\n+\t\t\tconst char *param, const char *value)\n+{\n+\tconst char *event_name = \"def_param\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"%s:%s\", param, value);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_repo_fl(const char *file, int line,\n+\t\t       const struct repository *repo)\n+{\n+\tconst char *event_name = \"def_repo\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"worktree:\");\n+\tsq_quote_buf_pretty(&buf_payload, repo->worktree);\n+\n+\tperf_io_write_fl(file, line, event_name, repo,\n+\t\t\t NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t const char *category,\n+\t\t\t\t\t const char *label,\n+\t\t\t\t\t const struct repository *repo,\n+\t\t\t\t\t const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_enter\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tif (label)\n+\t\tstrbuf_addf(&buf_payload, \"label:%s \", label);\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, repo,\n+\t\t\t &us_elapsed_absolute, NULL, category,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_region_leave_printf_va_fl(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t uint64_t us_elapsed_region,\n+\t\t\t\t\t const char *category,\n+\t\t\t\t\t const char *label,\n+\t\t\t\t\t const struct repository *repo,\n+\t\t\t\t\t const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_leave\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tif (label)\n+\t\tstrbuf_addf(&buf_payload, \"label:%s \", label);\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, repo,\n+\t\t\t &us_elapsed_absolute, &us_elapsed_region, category,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_data_fl(const char *file, int line,\n+\t\t       uint64_t us_elapsed_absolute,\n+\t\t       uint64_t us_elapsed_region,\n+\t\t       const char *category,\n+\t\t       const struct repository *repo,\n+\t\t       const char *key,\n+\t\t       const char *value)\n+{\n+\tconst char *event_name = \"data\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"%s:%s\", key, value);\n+\n+\tperf_io_write_fl(file, line, event_name, repo,\n+\t\t\t &us_elapsed_absolute, &us_elapsed_region, category,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_data_json_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute,\n+\t\t\t    uint64_t us_elapsed_region,\n+\t\t\t    const char *category,\n+\t\t\t    const struct repository *repo,\n+\t\t\t    const char *key,\n+\t\t\t    const struct json_writer *value)\n+{\n+\tconst char *event_name = \"data_json\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"%s:%s\", key, value->json.buf);\n+\n+\tperf_io_write_fl(file, line, event_name, repo,\n+\t\t\t &us_elapsed_absolute, &us_elapsed_region, category,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_printf_va_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute,\n+\t\t\t    const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"printf\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+struct tr2_tgt tr2_tgt_perf =\n+{\n+\t&tr2dst_perf,\n+\n+\tfn_init,\n+\tfn_term,\n+\n+\tfn_version_fl,\n+\tfn_start_fl,\n+\tfn_exit_fl,\n+\tfn_signal,\n+\tfn_atexit,\n+\tfn_error_va_fl,\n+\tfn_command_path_fl,\n+\tfn_command_verb_fl,\n+\tfn_command_subverb_fl,\n+\tfn_alias_fl,\n+\tfn_child_start_fl,\n+\tfn_child_exit_fl,\n+\tfn_thread_start_fl,\n+\tfn_thread_exit_fl,\n+\tfn_exec_fl,\n+\tfn_exec_result_fl,\n+\tfn_param_fl,\n+\tfn_repo_fl,\n+\tfn_region_enter_printf_va_fl,\n+\tfn_region_leave_printf_va_fl,\n+\tfn_data_fl,\n+\tfn_data_json_fl,\n+\tfn_printf_va_fl,\n+};\ndiff --git a/trace2/tr2_tls.c b/trace2/tr2_tls.c\nnew file mode 100644\nindex 0000000000..f5e304428c\n--- /dev/null\n+++ b/trace2/tr2_tls.c\n@@ -0,0 +1,164 @@\n+#include \"cache.h\"\n+#include \"thread-utils.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+/*\n+ * Initialize size of the thread stack for nested regions.\n+ * This is used to store nested region start times.  Note that\n+ * this stack is per-thread and not per-trace-key.\n+ */\n+#define TR2_REGION_NESTING_INITIAL_SIZE (100)\n+\n+static struct tr2tls_thread_ctx *tr2tls_thread_main;\n+static uint64_t tr2tls_us_start_main;\n+\n+static pthread_mutex_t tr2tls_mutex;\n+static pthread_key_t tr2tls_key;\n+\n+static int tr2_next_thread_id; /* modify under lock */\n+\n+struct tr2tls_thread_ctx *tr2tls_create_self(const char *thread_name)\n+{\n+\tuint64_t us_now = getnanotime() / 1000;\n+\tstruct tr2tls_thread_ctx *ctx = xcalloc(1, sizeof(*ctx));\n+\n+\t/*\n+\t * Implicitly \"tr2tls_push_self()\" to capture the thread's start\n+\t * time in array_us_start[0].  For the main thread this gives us the\n+\t * application run time.\n+\t */\n+\tctx->alloc = TR2_REGION_NESTING_INITIAL_SIZE;\n+\tctx->array_us_start = (uint64_t*)xcalloc(ctx->alloc, sizeof(uint64_t));\n+\tctx->array_us_start[ctx->nr_open_regions++] = us_now;\n+\n+\tctx->thread_id = tr2tls_locked_increment(&tr2_next_thread_id);\n+\t\n+\tstrbuf_init(&ctx->thread_name, 0);\n+\tif (ctx->thread_id)\n+\t\tstrbuf_addf(&ctx->thread_name, \"th%02d:\", ctx->thread_id);\n+\tstrbuf_addstr(&ctx->thread_name, thread_name);\n+\tif (ctx->thread_name.len > TR2_MAX_THREAD_NAME)\n+\t\tstrbuf_setlen(&ctx->thread_name, TR2_MAX_THREAD_NAME);\n+\n+\tpthread_setspecific(tr2tls_key, ctx);\n+\n+\treturn ctx;\n+}\n+\n+struct tr2tls_thread_ctx *tr2tls_get_self(void)\n+{\n+\tstruct tr2tls_thread_ctx * ctx = pthread_getspecific(tr2tls_key);\n+\n+\t/*\n+\t * If the thread-proc did not call trace2_thread_start(), we won't\n+\t * have any TLS data associated with the current thread.  Fix it\n+\t * here and silently continue.\n+\t */\n+\tif (!ctx)\n+\t\tctx = tr2tls_create_self(\"unknown\");\n+\n+\treturn ctx;\n+}\n+\n+int tr2tls_is_main_thread(void)\n+{\n+\tstruct tr2tls_thread_ctx * ctx = pthread_getspecific(tr2tls_key);\n+\n+\treturn ctx == tr2tls_thread_main;\n+}\n+\n+void tr2tls_unset_self(void)\n+{\n+\tstruct tr2tls_thread_ctx * ctx;\n+\n+\tctx = tr2tls_get_self();\n+\n+\tpthread_setspecific(tr2tls_key, NULL);\n+\n+\tfree(ctx->array_us_start);\n+\tfree(ctx);\n+}\n+\n+void tr2tls_push_self(uint64_t us_now)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\n+\tALLOC_GROW(ctx->array_us_start, ctx->nr_open_regions + 1, ctx->alloc);\n+\tctx->array_us_start[ctx->nr_open_regions++] = us_now;\n+}\n+\n+void tr2tls_pop_self(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\n+\tif (!ctx->nr_open_regions)\n+\t\tBUG(\"no open regions in thread '%s'\", ctx->thread_name.buf);\n+\n+\tctx->nr_open_regions--;\n+}\n+\n+void tr2tls_pop_unwind_self(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\n+\twhile (ctx->nr_open_regions > 1)\n+\t\ttr2tls_pop_self();\n+}\n+\n+uint64_t tr2tls_region_elasped_self(uint64_t us)\n+{\n+\tstruct tr2tls_thread_ctx *ctx;\n+\tuint64_t us_start;\n+\n+\tctx = tr2tls_get_self();\n+\tif (!ctx->nr_open_regions)\n+\t\treturn 0;\n+\t\n+\tus_start = ctx->array_us_start[ctx->nr_open_regions - 1];\n+\n+\treturn us - us_start;\n+}\n+\n+uint64_t tr2tls_absolute_elapsed(uint64_t us)\n+{\n+\tif (!tr2tls_thread_main)\n+\t\treturn 0;\n+\n+\treturn us - tr2tls_us_start_main;\n+}\n+\n+void tr2tls_init(void)\n+{\n+\tpthread_key_create(&tr2tls_key, NULL);\n+\tinit_recursive_mutex(&tr2tls_mutex);\n+\n+\ttr2tls_thread_main = tr2tls_create_self(\"main\");\n+\t/*\n+\t * Keep a copy of the absolute start time of the main thread\n+\t * in a fixed variable since other threads need to access it.\n+\t * This also eliminates the need to lock accesses to the main\n+\t * thread's array (because of reallocs).\n+\t */\n+\ttr2tls_us_start_main = tr2tls_thread_main->array_us_start[0];\n+}\n+\n+void tr2tls_release(void)\n+{\n+\ttr2tls_unset_self();\n+\ttr2tls_thread_main = NULL;\n+\n+\tpthread_mutex_destroy(&tr2tls_mutex);\n+\tpthread_key_delete(tr2tls_key);\n+}\n+\n+int tr2tls_locked_increment(int *p)\n+{\n+\tint current_value;\n+\n+\tpthread_mutex_lock(&tr2tls_mutex);\n+\tcurrent_value = *p;\n+\t*p = current_value + 1;\n+\tpthread_mutex_unlock(&tr2tls_mutex);\n+\n+\treturn current_value;\n+}\ndiff --git a/trace2/tr2_tls.h b/trace2/tr2_tls.h\nnew file mode 100644\nindex 0000000000..99ea9018ce\n--- /dev/null\n+++ b/trace2/tr2_tls.h\n@@ -0,0 +1,95 @@\n+#ifndef TR2_TLS_H\n+#define TR2_TLS_H\n+\n+/*\n+ * Arbitry limit for thread names for column alignment.\n+ */\n+#define TR2_MAX_THREAD_NAME (24)\n+\n+struct tr2tls_thread_ctx {\n+\tstruct strbuf thread_name;\n+\tuint64_t *array_us_start;\n+\tint alloc;\n+\tint nr_open_regions; /* plays role of \"nr\" in ALLOC_GROW */\n+\tint thread_id;\n+};\n+\n+/*\n+ * Create TLS data for the current thread.  This gives us a place to\n+ * put per-thread data, such as thread start time, function nesting\n+ * and a per-thread label for our messages.\n+ *\n+ * We assume the first thread is \"main\".  Other threads are given\n+ * non-zero thread-ids to help distinguish messages from concurrent\n+ * threads.\n+ *\n+ * Truncate the thread name if necessary to help with column alignment\n+ * in printf-style messages.\n+ *\n+ * In this and all following functions the term \"self\" refers to the\n+ * current thread.\n+ */\n+struct tr2tls_thread_ctx *tr2tls_create_self(const char *thread_name);\n+\n+/*\n+ * Get our TLS data.\n+ */\n+struct tr2tls_thread_ctx *tr2tls_get_self(void);\n+\n+/*\n+ * return true if the current thread is the main thread.\n+ */\n+int tr2tls_is_main_thread(void);\n+\n+/*\n+ * Free our TLS data.\n+ */\n+void tr2tls_unset_self(void);\n+\n+/*\n+ * Begin a new nested region and remember the start time.\n+ */\n+void tr2tls_push_self(uint64_t us_now);\n+\n+/*\n+ * End the innermost nested region.\n+ */\n+void tr2tls_pop_self(void);\n+\n+/*\n+ * Pop any extra (above the first) open regions on the current\n+ * thread and discard.  During a thread-exit, we should only\n+ * have region[0] that was pushed in trace2_thread_start() if\n+ * the thread exits normally.\n+ */\n+void tr2tls_pop_unwind_self(void);\n+\n+/*\n+ * Compute the elapsed time since the innermost region in the\n+ * current thread started and the given time (usually now).\n+ */\n+uint64_t tr2tls_region_elasped_self(uint64_t us);\n+\n+/*\n+ * Compute the elapsed time since the main thread started\n+ * and the given time (usually now).  This is assumed to\n+ * be the absolute run time of the process.\n+ */\n+uint64_t tr2tls_absolute_elapsed(uint64_t us);\n+\n+/*\n+ * Initialize the tr2 TLS system.\n+ */\n+void tr2tls_init(void);\n+\n+/*\n+ * Free all tr2 TLS resources.\n+ */\n+void tr2tls_release(void);\n+\n+/*\n+ * Protected increment of an integer.\n+ */\n+int tr2tls_locked_increment(int *p);\n+\n+#endif /* TR2_TLS_H */\ndiff --git a/trace2/tr2_verb.c b/trace2/tr2_verb.c\nnew file mode 100644\nindex 0000000000..52be27c29f\n--- /dev/null\n+++ b/trace2/tr2_verb.c\n@@ -0,0 +1,30 @@\n+#include \"cache.h\"\n+#include \"trace2/tr2_verb.h\"\n+\n+#define TR2_ENVVAR_PARENT_VERB \"GIT_TR2_PARENT_VERB\"\n+\n+static struct strbuf tr2verb_hierarchy = STRBUF_INIT;\n+\n+void tr2_verb_append_hierarchy(const char *verb)\n+{\n+\tconst char *parent_verb = getenv(TR2_ENVVAR_PARENT_VERB);\n+\n+\tstrbuf_reset(&tr2verb_hierarchy);\n+\tif (parent_verb && *parent_verb) {\n+\t\tstrbuf_addstr(&tr2verb_hierarchy, parent_verb);\n+\t\tstrbuf_addch(&tr2verb_hierarchy, '/');\n+\t}\n+\tstrbuf_addstr(&tr2verb_hierarchy, verb);\n+\n+\tsetenv(TR2_ENVVAR_PARENT_VERB, tr2verb_hierarchy.buf, 1);\n+}\n+\n+const char *tr2_verb_get_hierarchy(void)\n+{\n+\treturn tr2verb_hierarchy.buf;\n+}\n+\n+void tr2_verb_release(void)\n+{\n+\tstrbuf_release(&tr2verb_hierarchy);\n+}\ndiff --git a/trace2/tr2_verb.h b/trace2/tr2_verb.h\nnew file mode 100644\nindex 0000000000..f84f873463\n--- /dev/null\n+++ b/trace2/tr2_verb.h\n@@ -0,0 +1,24 @@\n+#ifndef TR2_VERB_H\n+#define TR2_VERB_H\n+\n+/*\n+ * Append the current git command's \"verb\" to the list being maintained\n+ * in the environment.\n+ *\n+ * The hierarchy for a top-level git command is just the current verb.\n+ * For a child git process, the hierarchy lists the verbs of the parent\n+ * git processes (much like the SID).\n+ *\n+ * The hierarchy for the current process will be exported to the environment\n+ * and inherited by child processes.\n+ */\n+void tr2_verb_append_hierarchy(const char *verb);\n+\n+/*\n+ * Get the verb hierarchy for the current process.\n+ */\n+const char *tr2_verb_get_hierarchy(void);\n+\n+void tr2_verb_release(void);\n+\n+#endif /* TR2_VERB_H */\ndiff --git a/usage.c b/usage.c\nindex cc803336bd..5ec7511e11 100644\n--- a/usage.c\n+++ b/usage.c\n@@ -22,17 +22,48 @@ void vreportf(const char *prefix, const char *err, va_list params)\n static NORETURN void usage_builtin(const char *err, va_list params)\n {\n \tvreportf(\"usage: \", err, params);\n+\n+\t/*\n+\t * When we detect a usage error *before* the command dispatch in\n+\t * cmd_main(), we don't know what verb to report.  Force it to this\n+\t * to facilitate post-processing.\n+\t */\n+\ttrace2_cmd_verb(\"_usage_\");\n+\n+\t/*\n+\t * Currently, the (err, params) are usually just the static usage\n+\t * string which isn't very useful here.  Usually, the call site\n+\t * manually calls fprintf(stderr,...) with the actual detailed\n+\t * syntax error before calling usage().\n+\t *\n+\t * TODO It would be nice to update the call sites to pass both\n+\t * the static usage string and the detailed error message.\n+\t */\n+\n \texit(129);\n }\n \n static NORETURN void die_builtin(const char *err, va_list params)\n {\n+\t/*\n+\t * We call this trace2 function first and expect it to va_copy 'params'\n+\t * before using it (because an 'ap' can only be walked once).\n+\t */\n+\ttrace2_cmd_error_va(err, params);\n+\n \tvreportf(\"fatal: \", err, params);\n+\n \texit(128);\n }\n \n static void error_builtin(const char *err, va_list params)\n {\n+\t/*\n+\t * We call this trace2 function first and expect it to va_copy 'params'\n+\t * before using it (because an 'ap' can only be walked once).\n+\t */\n+\ttrace2_cmd_error_va(err, params);\n+\n \tvreportf(\"error: \", err, params);\n }\n \n-- \ngitgitgadget\n\n"},{"id":"367881","messageId":"b590f1910007cc7a8cb0d7a5b026bb978ce16386.1548712060.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 06/14] trace2:data: add trace2 sub-process classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-28T21:47:46Z","receivedAt":"2019-01-28T21:48:05Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 classification for long-running processes\nstarted in sub-process.c\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n sub-process.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/sub-process.c b/sub-process.c\nindex 8d2a1707cf..3f4af93555 100644\n--- a/sub-process.c\n+++ b/sub-process.c\n@@ -88,6 +88,7 @@ int subprocess_start(struct hashmap *hashmap, struct subprocess_entry *entry, co\n \tprocess->out = -1;\n \tprocess->clean_on_exit = 1;\n \tprocess->clean_on_exit_handler = subprocess_exit_handler;\n+\tprocess->trace2_child_class = \"subprocess\";\n \n \terr = start_command(process);\n \tif (err) {\n-- \ngitgitgadget\n\n"},{"id":"367882","messageId":"f9d689a54b8f797ab598fe0fb8d2a2182620beb1.1548712060.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v2 04/14] trace2:data: add trace2 regions to wt-status","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-28T21:47:44Z","receivedAt":"2019-01-28T21:48:06Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2_region_enter() and trace2_region_leave() calls around the\nvarious phases of a status scan.  This gives elapsed time for each\nphase in the GIT_TR2_PERF and GIT_TR2_EVENT trace target.\n\nAlso, these Trace2 calls now use s->repo rather than the_repository.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n wt-status.c | 23 +++++++++++++++++++++--\n 1 file changed, 21 insertions(+), 2 deletions(-)\n\ndiff --git a/wt-status.c b/wt-status.c\nindex 0fe3bcd4cd..7c5ba4e902 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -748,12 +748,23 @@ static int has_unmerged(struct wt_status *s)\n \n void wt_status_collect(struct wt_status *s)\n {\n+\ttrace2_region_enter(\"status\", \"worktrees\", s->repo);\n \twt_status_collect_changes_worktree(s);\n-\tif (s->is_initial)\n+\ttrace2_region_leave(\"status\", \"worktrees\", s->repo);\n+\n+\tif (s->is_initial) {\n+\t\ttrace2_region_enter(\"status\", \"initial\", s->repo);\n \t\twt_status_collect_changes_initial(s);\n-\telse\n+\t\ttrace2_region_leave(\"status\", \"initial\", s->repo);\n+\t} else {\n+\t\ttrace2_region_enter(\"status\", \"index\", s->repo);\n \t\twt_status_collect_changes_index(s);\n+\t\ttrace2_region_leave(\"status\", \"index\", s->repo);\n+\t}\n+\n+\ttrace2_region_enter(\"status\", \"untracked\", s->repo);\n \twt_status_collect_untracked(s);\n+\ttrace2_region_leave(\"status\", \"untracked\", s->repo);\n \n \twt_status_get_state(s->repo, &s->state, s->branch && !strcmp(s->branch, \"HEAD\"));\n \tif (s->state.merge_in_progress && !has_unmerged(s))\n@@ -2291,6 +2302,12 @@ static void wt_porcelain_v2_print(struct wt_status *s)\n \n void wt_status_print(struct wt_status *s)\n {\n+\ttrace2_data_intmax(\"status\", s->repo, \"count/changed\", s->change.nr);\n+\ttrace2_data_intmax(\"status\", s->repo, \"count/untracked\", s->untracked.nr);\n+\ttrace2_data_intmax(\"status\", s->repo, \"count/ignored\", s->ignored.nr);\n+\n+\ttrace2_region_enter(\"status\", \"print\", s->repo);\n+\n \tswitch (s->status_format) {\n \tcase STATUS_FORMAT_SHORT:\n \t\twt_shortstatus_print(s);\n@@ -2309,6 +2326,8 @@ void wt_status_print(struct wt_status *s)\n \t\twt_longstatus_print(s);\n \t\tbreak;\n \t}\n+\n+\ttrace2_region_leave(\"status\", \"print\", s->repo);\n }\n \n /**\n-- \ngitgitgadget\n\n"},{"id":"367894","messageId":"xmqqd0oga0b6.fsf@gitster-ct.c.googlers.com","threadId":"50293","inReplyTo":"851aa8f34d7223a3d872033abf2f2a95517b3b23.1548712060.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 14/14] trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-01-28T22:23:41Z","receivedAt":"2019-01-28T22:23:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +test_expect_success 'perf stream, return code 0' '\n> +\ttest_when_finished \"rm trace.perf actual expect\" &&\n> +\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" && export GIT_TR2_PERF &&\n> +\t$TT trace2 001return 0 &&\n> +\tunset GIT_TR2_PERF &&\n\nDo not do this.  Stoppage in any different one of these steps would\nleave the state of the process in different state and affects later\ntests (e.g. TR2_PERF may or may not be exported).\n\nIs $TT always \"test-tool\" (i.e. the binary, not a shell function)?\nIf so, consider using a single-short export, i.e.\n\n\tGIT_TR2_PERF=\"...\" test-tool trace2 001return 0 &&\n\n"},{"id":"368056","messageId":"180fc774-d080-0ff4-72a5-c54dd77bc59b@jeffhostetler.com","threadId":"50293","inReplyTo":"xmqqd0oga0b6.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 14/14] trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2019-01-29T22:08:09Z","receivedAt":"2019-01-29T22:08:12Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 1/28/2019 5:23 PM, Junio C Hamano wrote:\n> \"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> +test_expect_success 'perf stream, return code 0' '\n>> +\ttest_when_finished \"rm trace.perf actual expect\" &&\n>> +\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" && export GIT_TR2_PERF &&\n>> +\t$TT trace2 001return 0 &&\n>> +\tunset GIT_TR2_PERF &&\n> \n> Do not do this.  Stoppage in any different one of these steps would\n> leave the state of the process in different state and affects later\n> tests (e.g. TR2_PERF may or may not be exported).\n> \n> Is $TT always \"test-tool\" (i.e. the binary, not a shell function)?\n> If so, consider using a single-short export, i.e.\n> \n> \tGIT_TR2_PERF=\"...\" test-tool trace2 001return 0 &&\n> \n\nI'll redo the fix for this to use the inline form\nand send a new version tomorrow.\n\nThanks\nJeff\n"},{"id":"368150","messageId":"731e57f4-3474-9fd4-8b3e-812f648a8bed@jeffhostetler.com","threadId":"50293","inReplyTo":"20190125200309.GB72177@google.com","subject":"Re: [PATCH 00/14] Trace2 tracing facility","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2019-01-30T18:45:58Z","receivedAt":"2019-01-30T18:46:02Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 1/25/2019 3:03 PM, Josh Steadmon wrote:\n> On 2019.01.22 13:22, Jeff Hostetler via GitGitGadget wrote:\n>> This patch series contains a greatly refactored version of my original\n>> Trace2 series [1] from August 2018.\n>>\n>> A new design doc in Documentation/technical/api-trace2.txt (in the first\n>> commit) explains the relationship of Trace2 to the current tracing facility.\n>> Calls to the current tracing facility have not been changed, rather new\n>> trace2 calls have been added so that both continue to work in parallel for\n>> the time being.\n>>\n...\n> \n> Several patches in this series have many style diffs as reported by\n> clang-format. Not all the diffs actually improve readability, but many\n> do. If you have clang-format installed, you can run:\n> \n> git clang-format --style file --diff --extensions c,h ${commit}^ ${commit}\n> \n> for each commit in the series to see what it thinks needs to be changed.\n\nThanks for the pointer.  I'll address this in my V3 version.\nYes, there were lots of complaints.  Some were more noise than\nanything else, but there were some good ones in there.\n\n\n> \n> Other than that, I don't have any comments apart from what the other\n> reviewers have already mentioned.\n> \n> Thanks for the series!\n> \n\nThanks,\nJeff\n"},{"id":"368165","messageId":"e8b82266400a158719f3a3eb0bc3d5c0d99a0e83.1548877863.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 03/14] trace2: collect platform-specific process information","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T19:51:07Z","receivedAt":"2019-01-30T19:51:11Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd optional platform-specific code to log information about\nthe current process.\n\nOn Windows, this includes whether git.exe is running under a\ndebugger and information about the ancestors of the process.\n\nThe purpose of this information is to help indicate if the\nprocess was launched interactively or in the background by\nan IDE, for example.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n common-main.c           |   1 +\n compat/win32/ancestry.c | 101 ++++++++++++++++++++++++++++++++++++++++\n config.mak.uname        |   2 +\n trace2.h                |  14 ++++++\n 4 files changed, 118 insertions(+)\n create mode 100644 compat/win32/ancestry.c\n\ndiff --git a/common-main.c b/common-main.c\nindex 6dbdc4adf2..d484aec209 100644\n--- a/common-main.c\n+++ b/common-main.c\n@@ -37,6 +37,7 @@ int main(int argc, const char **argv)\n \n \ttrace2_initialize();\n \ttrace2_cmd_start(argv);\n+\ttrace2_collect_process_info();\n \n \tgit_resolve_executable_dir(argv[0]);\n \ndiff --git a/compat/win32/ancestry.c b/compat/win32/ancestry.c\nnew file mode 100644\nindex 0000000000..253199f812\n--- /dev/null\n+++ b/compat/win32/ancestry.c\n@@ -0,0 +1,101 @@\n+#include \"../../cache.h\"\n+#include \"../../json-writer.h\"\n+#include <Psapi.h>\n+#include <tlHelp32.h>\n+\n+/*\n+ * Find the process data for the given PID in the given snapshot\n+ * and update the PROCESSENTRY32 data.\n+ */\n+static int find_pid(DWORD pid, HANDLE hSnapshot, PROCESSENTRY32 *pe32)\n+{\n+\tpe32->dwSize = sizeof(PROCESSENTRY32);\n+\n+\tif (Process32First(hSnapshot, pe32)) {\n+\t\tdo {\n+\t\t\tif (pe32->th32ProcessID == pid)\n+\t\t\t\treturn 1;\n+\t\t} while (Process32Next(hSnapshot, pe32));\n+\t}\n+\treturn 0;\n+}\n+\n+/*\n+ * Accumulate JSON array:\n+ *     [\n+ *         exe-name-parent,\n+ *         exe-name-grand-parent,\n+ *         ...\n+ *     ]\n+ *\n+ * Note: we only report the filename of the process executable; the\n+ *       only way to get its full pathname is to use OpenProcess()\n+ *       and GetModuleFileNameEx() or QueryfullProcessImageName()\n+ *       and that seems rather expensive (on top of the cost of\n+ *       getting the snapshot).\n+ */\n+static void get_processes(struct json_writer *jw, HANDLE hSnapshot)\n+{\n+\tPROCESSENTRY32 pe32;\n+\tDWORD pid;\n+\n+\tpid = GetCurrentProcessId();\n+\n+\t/* We only want parent processes, so skip self. */\n+\tif (!find_pid(pid, hSnapshot, &pe32))\n+\t\treturn;\n+\tpid = pe32.th32ParentProcessID;\n+\n+\twhile (find_pid(pid, hSnapshot, &pe32)) {\n+\t\tjw_array_string(jw, pe32.szExeFile);\n+\n+\t\tpid = pe32.th32ParentProcessID;\n+\t}\n+}\n+\n+/*\n+ * Emit JSON data for the current and parent processes.  Individual\n+ * trace2 targets can decide how to actually print it.\n+ */\n+static void get_ancestry(void)\n+{\n+\tHANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);\n+\n+\tif (hSnapshot != INVALID_HANDLE_VALUE) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\t\tjw_array_begin(&jw, 0);\n+\t\tget_processes(&jw, hSnapshot);\n+\t\tjw_end(&jw);\n+\n+\t\ttrace2_data_json(\"process\", the_repository, \"windows/ancestry\",\n+\t\t\t\t &jw);\n+\n+\t\tjw_release(&jw);\n+\t\tCloseHandle(hSnapshot);\n+\t}\n+}\n+\n+/*\n+ * Is a debugger attached to the current process?\n+ *\n+ * This will catch debug runs (where the debugger started the process).\n+ * This is the normal case.  Since this code is called during our startup,\n+ * it will not report instances where a debugger is attached dynamically\n+ * to a running git process, but that is relatively rare.\n+ */\n+static void get_is_being_debugged(void)\n+{\n+\tif (IsDebuggerPresent())\n+\t\ttrace2_data_intmax(\"process\", the_repository,\n+\t\t\t\t   \"windows/debugger_present\", 1);\n+}\n+\n+void trace2_collect_process_info(void)\n+{\n+\tif (!trace2_is_enabled())\n+\t\treturn;\n+\n+\tget_is_being_debugged();\n+\tget_ancestry();\n+}\ndiff --git a/config.mak.uname b/config.mak.uname\nindex 7b36a1dfe7..853aa2e77b 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -391,6 +391,7 @@ ifeq ($(uname_S),Windows)\n \tBASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE\n \tCOMPAT_OBJS = compat/msvc.o compat/winansi.o \\\n \t\tcompat/win32/pthread.o compat/win32/syslog.o \\\n+\t\tcompat/win32/ancestry.o \\\n \t\tcompat/win32/dirent.o\n \tCOMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\\\".exe\\\"\n \tBASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE\n@@ -545,6 +546,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))\n \tCOMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32\n \tCOMPAT_CFLAGS += -DSTRIP_EXTENSION=\\\".exe\\\"\n \tCOMPAT_OBJS += compat/mingw.o compat/winansi.o \\\n+\t\tcompat/win32/ancestry.o \\\n \t\tcompat/win32/path-utils.o \\\n \t\tcompat/win32/pthread.o compat/win32/syslog.o \\\n \t\tcompat/win32/dirent.o\ndiff --git a/trace2.h b/trace2.h\nindex a0e99d9c26..cb11a46366 100644\n--- a/trace2.h\n+++ b/trace2.h\n@@ -363,4 +363,18 @@ __attribute__((format (printf, 1, 2)))\n void trace2_printf(const char *fmt, ...);\n #endif\n \n+/*\n+ * Optional platform-specific code to dump information about the\n+ * current and any parent process(es).  This is intended to allow\n+ * post-processors to know who spawned this git instance and anything\n+ * else the platform may be able to tell us about the current process.\n+ */\n+#if defined(GIT_WINDOWS_NATIVE)\n+void trace2_collect_process_info(void);\n+#else\n+#define trace2_collect_process_info() \\\n+\tdo {                          \\\n+\t} while (0)\n+#endif\n+\n #endif /* TRACE2_H */\n-- \ngitgitgadget\n\n"},{"id":"368166","messageId":"0d59a6f2ebcb7d07b766ef00bfec3d248ca2e7da.1548877863.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 04/14] trace2:data: add trace2 regions to wt-status","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T19:51:07Z","receivedAt":"2019-01-30T19:51:13Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2_region_enter() and trace2_region_leave() calls around the\nvarious phases of a status scan.  This gives elapsed time for each\nphase in the GIT_TR2_PERF and GIT_TR2_EVENT trace target.\n\nAlso, these Trace2 calls now use s->repo rather than the_repository.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n wt-status.c | 24 ++++++++++++++++++++++--\n 1 file changed, 22 insertions(+), 2 deletions(-)\n\ndiff --git a/wt-status.c b/wt-status.c\nindex 0fe3bcd4cd..434636850e 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -748,12 +748,23 @@ static int has_unmerged(struct wt_status *s)\n \n void wt_status_collect(struct wt_status *s)\n {\n+\ttrace2_region_enter(\"status\", \"worktrees\", s->repo);\n \twt_status_collect_changes_worktree(s);\n-\tif (s->is_initial)\n+\ttrace2_region_leave(\"status\", \"worktrees\", s->repo);\n+\n+\tif (s->is_initial) {\n+\t\ttrace2_region_enter(\"status\", \"initial\", s->repo);\n \t\twt_status_collect_changes_initial(s);\n-\telse\n+\t\ttrace2_region_leave(\"status\", \"initial\", s->repo);\n+\t} else {\n+\t\ttrace2_region_enter(\"status\", \"index\", s->repo);\n \t\twt_status_collect_changes_index(s);\n+\t\ttrace2_region_leave(\"status\", \"index\", s->repo);\n+\t}\n+\n+\ttrace2_region_enter(\"status\", \"untracked\", s->repo);\n \twt_status_collect_untracked(s);\n+\ttrace2_region_leave(\"status\", \"untracked\", s->repo);\n \n \twt_status_get_state(s->repo, &s->state, s->branch && !strcmp(s->branch, \"HEAD\"));\n \tif (s->state.merge_in_progress && !has_unmerged(s))\n@@ -2291,6 +2302,13 @@ static void wt_porcelain_v2_print(struct wt_status *s)\n \n void wt_status_print(struct wt_status *s)\n {\n+\ttrace2_data_intmax(\"status\", s->repo, \"count/changed\", s->change.nr);\n+\ttrace2_data_intmax(\"status\", s->repo, \"count/untracked\",\n+\t\t\t   s->untracked.nr);\n+\ttrace2_data_intmax(\"status\", s->repo, \"count/ignored\", s->ignored.nr);\n+\n+\ttrace2_region_enter(\"status\", \"print\", s->repo);\n+\n \tswitch (s->status_format) {\n \tcase STATUS_FORMAT_SHORT:\n \t\twt_shortstatus_print(s);\n@@ -2309,6 +2327,8 @@ void wt_status_print(struct wt_status *s)\n \t\twt_longstatus_print(s);\n \t\tbreak;\n \t}\n+\n+\ttrace2_region_leave(\"status\", \"print\", s->repo);\n }\n \n /**\n-- \ngitgitgadget\n\n"},{"id":"368167","messageId":"60b56d6a8fe9c0e37563f66809cc12c98c9eb21d.1548877863.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 01/14] trace2: Documentation/technical/api-trace2.txt","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T19:51:05Z","receivedAt":"2019-01-30T19:51:14Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreated design document for Trace2 feature.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Documentation/technical/api-trace2.txt | 1347 ++++++++++++++++++++++++\n 1 file changed, 1347 insertions(+)\n create mode 100644 Documentation/technical/api-trace2.txt\n\ndiff --git a/Documentation/technical/api-trace2.txt b/Documentation/technical/api-trace2.txt\nnew file mode 100644\nindex 0000000000..193518e55a\n--- /dev/null\n+++ b/Documentation/technical/api-trace2.txt\n@@ -0,0 +1,1347 @@\n+= Trace2 API\n+\n+The Trace2 API can be used to print debug, performance, and telemetry\n+information to stderr or a file.  The Trace2 feature is inactive unless\n+explicitly enabled by enabling one or more Trace2 Targets.\n+\n+The Trace2 API is intended to replace the existing (Trace1)\n+printf-style tracing provided by the existing `GIT_TRACE` and\n+`GIT_TRACE_PERFORMANCE` facilities.  During initial implementation,\n+Trace2 and Trace1 may operate in parallel.\n+\n+The Trace2 API defines a set of high-level messages with known fields,\n+such as (`start`: `argv`) and (`exit`: {`exit-code`, `elapsed-time`}).\n+\n+Trace2 instrumentation throughout the Git code base sends Trace2\n+messages to the enabled Trace2 Targets.  Targets transform these\n+messages content into purpose-specific formats and write events to\n+their data streams.  In this manner, the Trace2 API can drive\n+many different types of analysis.\n+\n+Targets are defined using a VTable allowing easy extension to other\n+formats in the future.  This might be used to define a binary format,\n+for example.\n+\n+== Trace2 Targets\n+\n+Trace2 defines the following set of Trace2 Targets.\n+Format details are given in a later section.\n+\n+`GIT_TR2` (NORMAL)::\n+\n+\ta simple printf format like GIT_TRACE.\n++\n+------------\n+$ export GIT_TR2=~/log.normal\n+$ git version\n+git version 2.20.1.155.g426c96fcdb\n+------------\n++\n+------------\n+$ cat ~/log.normal\n+12:28:42.620009 common-main.c:38                  version 2.20.1.155.g426c96fcdb\n+12:28:42.620989 common-main.c:39                  start git version\n+12:28:42.621101 git.c:432                         cmd_verb version (version)\n+12:28:42.621215 git.c:662                         exit elapsed:0.001227 code:0\n+12:28:42.621250 trace2/tr2_tgt_normal.c:124       atexit elapsed:0.001265 code:0\n+------------\n+\n+`GIT_TR2_PERF` (PERF)::\n+\n+\ta column-based format to replace GIT_TRACE_PERFORMANCE suitable for\n+\tdevelopment and testing, possibly to complement tools like gprof.\n++\n+------------\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git version\n+git version 2.20.1.155.g426c96fcdb\n+------------\n++\n+------------\n+$ cat ~/log.perf\n+12:28:42.620675 common-main.c:38                  | d0 | main                     | version      |     |           |           |            | 2.20.1.155.g426c96fcdb\n+12:28:42.621001 common-main.c:39                  | d0 | main                     | start        |     |           |           |            | git version\n+12:28:42.621111 git.c:432                         | d0 | main                     | cmd_verb     |     |           |           |            | version (version)\n+12:28:42.621225 git.c:662                         | d0 | main                     | exit         |     |  0.001227 |           |            | code:0\n+12:28:42.621259 trace2/tr2_tgt_perf.c:211         | d0 | main                     | atexit       |     |  0.001265 |           |            | code:0\n+------------\n+\n+`GIT_TR2_EVENT` (EVENT)::\n+\n+\ta JSON-based format of event data suitable for telemetry analysis.\n++\n+------------\n+$ export GIT_TR2_EVENT=~/log.event\n+$ git version\n+git version 2.20.1.155.g426c96fcdb\n+------------\n++\n+------------\n+$ cat ~/log.event\n+{\"event\":\"version\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.620713\",\"file\":\"common-main.c\",\"line\":38,\"evt\":\"1\",\"exe\":\"2.20.1.155.g426c96fcdb\"}\n+{\"event\":\"start\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621027\",\"file\":\"common-main.c\",\"line\":39,\"argv\":[\"git\",\"version\"]}\n+{\"event\":\"cmd_verb\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621122\",\"file\":\"git.c\",\"line\":432,\"name\":\"version\",\"hierarchy\":\"version\"}\n+{\"event\":\"exit\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621236\",\"file\":\"git.c\",\"line\":662,\"t_abs\":0.001227,\"code\":0}\n+{\"event\":\"atexit\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621268\",\"file\":\"trace2/tr2_tgt_event.c\",\"line\":163,\"t_abs\":0.001265,\"code\":0}\n+------------\n+\n+== Enabling a Target\n+\n+A Trace2 Target is enabled when the corresponding environment variable\n+(`GIT_TR2`, `GIT_TR2_PERF`, or `GIT_TR2_EVENT`) is set.  The following\n+values are recognized.\n+\n+`0`::\n+`false`::\n+\n+\tDisables the target.\n+\n+`1`::\n+`true`::\n+\n+\tEnables the target and writes stream to `STDERR`.\n+\n+`[2-9]`::\n+\n+\tEnables the target and writes to the already opened file descriptor.\n+\n+`<absolute-pathname>`::\n+\n+\tEnables the target, opens and writes to the file in append mode.\n+\n+`af_unix:<absolute-pathname>`::\n+\n+\tEnables the target, opens and writes to a Unix Domain Socket\n+\t(on platforms that support them).\n+\n+== Trace2 API\n+\n+All public Trace2 functions and macros are defined in `trace2.h` and\n+`trace2.c`.  All public symbols are prefixed with `trace2_`.\n+\n+There are no public Trace2 data structures.\n+\n+The Trace2 code also defines a set of private functions and data types\n+in the `trace2/` directory.  These symbols are prefixed with `tr2_`\n+and should only be used by functions in `trace2.c`.\n+\n+== Conventions for Public Functions and Macros\n+\n+The functions defined by the Trace2 API are declared and documented\n+in `trace2.h`.  It defines the API functions and wrapper macros for\n+Trace2.\n+\n+Some functions have a `_fl()` suffix to indicate that they take `file`\n+and `line-number` arguments.\n+\n+Some functions have a `_va_fl()` suffix to indicate that they also\n+take a `va_list` argument.\n+\n+Some functions have a `_printf_fl()` suffix to indicate that they also\n+take a varargs argument.\n+\n+There are CPP wrapper macros and ifdefs to hide most of these details.\n+See `trace2.h` for more details.  The following discussion will only\n+describe the simplified forms.\n+\n+== Public API\n+\n+All Trace2 API functions send a messsage to all of the active\n+Trace2 Targets.  This section describes the set of available\n+messages.\n+\n+It helps to divide these functions into groups for discussion\n+purposes.\n+\n+=== Basic Command Messages\n+\n+These are concerned with the lifetime of the overall git process.\n+\n+`void trace2_initialize()`::\n+\n+\tDetermines if any Trace2 Targets should be enabled and\n+\tinitializes the Trace2 facility.  This includes starting the\n+\telapsed time clocks and thread local storage (TLS).\n++\n+This function emits a \"version\" message containing the version of git\n+and the Trace2 protocol.\n++\n+This function should be called from `main()` as early as possible in\n+the life of the process.\n+\n+`int trace2_is_enabled()`::\n+\n+\tReturns 1 if Trace2 is enabled (at least one target is\n+\tactive).\n+\n+`void trace2_cmd_start(int argc, const char **argv)`::\n+\n+\tEmits a \"start\" message containing the process command line\n+\targuments.\n+\n+`int trace2_cmd_exit(int exit_code)`::\n+\n+\tEmits an \"exit\" message containing the process exit-code and\n+\telapsed time.\n++\n+Returns the exit-code.\n+\n+`void trace2_cmd_error(const char *fmt, va_list ap)`::\n+\n+\tEmits an \"error\" message containing a formatted error message.\n+\n+`void trace2_cmd_path(const char *pathname)`::\n+\n+\tEmits a \"cmd_path\" message with the full pathname of the\n+\tcurrent process.\n+\n+=== Command Detail Messages\n+\n+These are concerned with describing the specific Git command\n+after the command line, config, and environment are inspected.\n+\n+`void trace2_cmd_verb(const char *command_verb)`::\n+\n+\tEmits a \"cmd_verb\" message with the canonical name of\n+\t(usually) builtin command, for example \"status\" or \"checkout\".\n+\n+`void trace2_cmd_subverb(const char *command_subverb)`::\n+\n+\tEmits a \"cmd_subverb\" message with a qualifier name to further\n+\tdescribe the current git command.\n++\n+This message is intended to be used with git commands having multiple\n+major modes.  For example, a \"checkout\" command can checkout a new\n+branch or can checkout a single file, so the checkout code could\n+emit a subverb message of \"branch\" or \"file\".\n+\n+`void trace2_cmd_alias(const char *alias, const char **argv_expansion)`::\n+\n+\tEmits an \"alias\" message containing the alias used and the\n+\targument expansion.\n+\n+`void trace2_def_param(const char *parameter, const char *value)`::\n+\n+\tEmits a \"def_param\" message containing a key/value pair.\n++\n+This message is intended to report some global aspect of the current\n+command, such as a configuration setting or command line switch that\n+significantly affects program performance or behavior, such as\n+`core.abbrev`, `status.showUntrackedFiles`, or `--no-ahead-behind`.\n+\n+`void trace2_cmd_list_config()`::\n+\n+\tEmits a \"def_param\" messages for \"important\" configuration\n+\tsettings.\n++\n+The environment variable `GIT_TR2_CONFIG_PARAMS` can be set to a\n+list of patterns of important configuration settings, for example:\n+`core.*,remote.*.url`.  This function will iterate over all config\n+settings and emit a \"def_param\" message for each match.\n+\n+`void trace2_cmd_set_config(const char *key, const char *value)`::\n+\n+\tEmits a \"def_param\" message for a specific configuration\n+\tsetting IFF it matches the `GIT_TR2_CONFIG_PARAMS` pattern.\n++\n+This is used to hook into `git_config_set()` and catch any\n+configuration changes and update a value previously reported by\n+`trace2_cmd_list_config()`.\n+\n+`void trace2_def_repo(struct repository *repo)`::\n+\n+\tRegisters a repository with the Trace2 layer.  Assigns a\n+\tunique \"repo-id\" to `repo->trace2_repo_id`.\n++\n+Emits a \"worktree\" messages containing the repo-id and the worktree\n+pathname.\n++\n+Region and data messages (described later) may refer to this repo-id.\n++\n+The main/top-level repository will have repo-id value 1 (aka \"r1\").\n++\n+The repo-id field is in anticipation of future in-proc submodule\n+repositories.\n+\n+=== Child Process Messages\n+\n+These are concerned with the various spawned child processes,\n+including shell scripts, git commands, editors, pagers, and hooks.\n+\n+`void trace2_child_start(struct child_process *cmd)`::\n+\n+\tEmits a \"child_start\" message containing the \"child-id\",\n+\t\"child-argv\", and \"child-classification\".\n++\n+Before calling this, set `cmd->trace2_child_class` to a name\n+describing the type of child process, for example \"editor\".\n++\n+This function assigns a unique \"child-id\" to `cmd->trace2_child_id`.\n+This field is used later during the \"child_exit\" message to associate\n+it with the \"child_start\" message.\n++\n+This function should be called before spawning the child process.\n+\n+`void trace2_child_exit(struct child_proess *cmd, int child_exit_code)`::\n+\n+\tEmits a \"child_exit\" message containing the \"child-id\",\n+\tthe child's elapsed time and exit-code.\n++\n+The reported elapsed time includes the process creation overhead and\n+time spend waiting for it to exit, so it may be slightly longer than\n+the time reported by the child itself.\n++\n+This function should be called after reaping the child process.\n+\n+`int trace2_exec(const char *exe, const char **argv)`::\n+\n+\tEmits a \"exec\" message containing the \"exec-id\" and the\n+\targv of the new process.\n++\n+This function should be called before calling one of the `exec()`\n+variants, such as `execvp()`.\n++\n+This function returns a unique \"exec-id\".  This value is used later\n+if the exec() fails and a \"exec-result\" message is necessary.\n+\n+`void trace2_exec_result(int exec_id, int error_code)`::\n+\n+\tEmits a \"exec_result\" message containing the \"exec-id\"\n+\tand the error code.\n++\n+On Unix-based systems, `exec()` does not return if successful.\n+This message is used to indicate that the `exec()` failed and\n+that the current program is continuing.\n+\n+=== Git Thread Messages\n+\n+These messages are concerned with Git thread usage.\n+\n+`void trace2_thread_start(const char *thread_name)`::\n+\n+\tEmits a \"thread_start\" message.\n++\n+The `thread_name` field should be a descriptive name, such as the\n+unique name of the thread-proc.  A unique \"thread-id\" will be added\n+to the name to uniquely identify thread instances.\n++\n+Region and data messages (described later) may refer to this thread\n+name.\n++\n+This function must be called by the thread-proc of the new thread\n+(so that TLS data is properly initialized) and not by the caller\n+of `pthread_create()`.\n+\n+`void trace2_thread_exit()`::\n+\n+\tEmits a \"thread_exit\" message containing the thread name\n+\tand the thread elapsed time.\n++\n+This function must be called by the thread-proc before it returns\n+(so that the coorect TLS data is used and cleaned up.  It should\n+not be called by the caller of `pthread_join()`.\n+\n+=== Region and Data Messages\n+\n+These are concerned with recording performance data\n+over regions or spans of code.\n+\n+`void trace2_region_enter(const char *category, const char *label, const struct repository *repo)`::\n+   \n+`void trace2_region_enter_printf(const char *category, const char *label, const struct repository *repo, const char *fmt, ...)`::\n+\n+`void trace2_region_enter_printf_va(const char *category, const char *label, const struct repository *repo, const char *fmt, va_list ap)`::\n+\n+\tEmits a thread-relative \"region_enter\" message with optional\n+\tprintf string.\n++\n+This function pushes a new region nesting stack level on the current\n+thread and starts a clock for the new stack frame.\n++\n+The `category` field is an arbitrary category name used to classify\n+regions by feature area, such as \"status\" or \"index\".  At this time\n+it is only just printed along with the rest of the message.  It may\n+be used in the future to filter messages.\n++\n+The `label` field is an arbitrary label used to describe the activity\n+being started, such as \"read_recursive\" or \"do_read_index\".\n++\n+The `repo` field, if set, will be used to get the \"repo-id\", so that\n+recursive oerations can be attributed to the correct repository.\n+\n+`void trace2_region_leave(const char *category, const char *label, const struct repository *repo)`::\n+\n+`void trace2_region_leave_printf(const char *category, const char *label, const struct repository *repo, const char *fmt, ...)`::\n+\n+`void trace2_region_leave_printf_va(const char *category, const char *label, const struct repository *repo, const char *fmt, va_list ap)`::\n+\n+\tEmits a thread-relative \"region_leave\" message with optional\n+\tprintf string.\n++\n+This function pops the region nesting stack on the current thread\n+and reports the elapsed time of the stack frame.\n++\n+The `category`, `label`, and `repo` fields are the same as above.\n+The `category` and `label` do not need to match the correpsonding\n+\"region_enter\" message, but it makes the data stream easier to\n+understand.\n+\n+`void trace2_data_string(const char *category, const struct repository *repo, const char *key, const char * value)`::\n+\n+`void trace2_data_intmax(const char *category, const struct repository *repo, const char *key, intmax value)`::\n+\n+`void trace2_data_json(const char *category, const struct repository *repo, const char *key, const struct json_writer *jw)`::\n+\n+\tEmits a region- and thread-relative \"data\" or \"data_json\" message.\n++\n+This is a key/value pair message containing information about the\n+current thread, region stack, and repository.  This could be used\n+to print the number of files in a directory during a multi-threaded\n+recursive tree walk.\n+\n+`void trace2_printf(const char *fmt, ...)`::\n+\n+`void trace2_printf_va(const char *fmt, va_list ap)`::\n+\n+\tEmits a region- and thread-relative \"printf\" message.\n+\n+== Trace2 Target Formats\n+\n+=== NORMAL Format\n+\n+NORMAL format is enabled when the `GIT_TR2` environment variable is\n+set.\n+\n+Events are written as lines of the form:\n+\n+------------\n+[<time> SP <filename>:<line> SP+] <event-name> [[SP] <event-message>] LF\n+------------\n+\n+`<event-name>`::\n+\n+\tis the event name.\n+\n+`<event-message>`::\n+\tis a free-form printf message intended for human consumption.\n++\n+Note that this may contain embedded LF or CRLF characters that are\n+not escaped, so the event may spill across multiple lines.\n+\n+If `GIT_TR2_BRIEF` is true, the `time`, `filename`, and `line` fields\n+are omitted.\n+\n+This target is intended to be more of a summary (like GIT_TRACE) and\n+less detailed than the other targets.  It ignores thread, region, and\n+data messages, for example.\n+\n+=== PERF Format\n+\n+PERF format is enabled when the `GIT_TR2_PERF` environment variable\n+is set.\n+\n+Events are written as lines of the form:\n+\n+------------\n+[<time> SP <filename>:<line> SP+\n+    BAR SP] d<depth> SP\n+    BAR SP <thread-name> SP+\n+    BAR SP <event-name> SP+\n+    BAR SP [r<repo-id>] SP+\n+    BAR SP [<t_abs>] SP+\n+    BAR SP [<t_rel>] SP+\n+    BAR SP [<category>] SP+\n+    BAR SP DOTS* <perf-event-message>\n+    LF\n+------------\n+\n+`<depth>`::\n+\tis the git process depth.  This is the number of parent\n+\tgit processes.  A top-level git command has depth value \"d0\".\n+\tA child of it has depth value \"d1\".  A second level child\n+\thas depth value \"d2\" and so on.\n+\n+`<thread-name>`::\n+\tis a unique name for the thread.  The primary thread\n+\tis called \"main\".  Other thread names are of the form \"th%d:%s\"\n+\tand include a unique number and the name of the thread-proc.\n+\n+`<event-name>`::\n+\tis the event name.\n+\n+`<repo-id>`::\n+\twhen present, is a number indicating the repository\n+\tin use.  A `def_repo` event is emitted when a repository is\n+\topened.  This defines the repo-id and associated worktree.\n+\tSubsequent repo-specific events will reference this repo-id.\n++\n+Currently, this is always \"r1\" for the main repository.\n+This field is in anticipation of in-proc submodules in the future.\n+\n+`<t_abs>`::\n+\twhen present, is the absolute time in seconds since the\n+\tprogram started.\n+\n+`<t_rel>`::\n+\twhen present, is time in seconds relative to the start of\n+\tthe current region.  For a thread-exit event, it is the elapsed\n+\ttime of the thread.\n+\n+`<category>`::\n+\tis present on region and data events and is used to\n+\tindicate a broad category, such as \"index\" or \"status\".\n+\n+`<perf-event-message>`::\n+\tis a free-form printf message intended for human consumption.\n+\n+------------\n+15:33:33.532712 wt-status.c:2310                  | d0 | main                     | region_enter | r1  |  0.126064 |           | status     | label:print\n+15:33:33.532712 wt-status.c:2331                  | d0 | main                     | region_leave | r1  |  0.127568 |  0.001504 | status     | label:print\n+------------\n+\n+If `GIT_TR2_PERF_BRIEF` is true, the `time`, `file`, and `line`\n+fields are omitted.\n+\n+------------\n+d0 | main                     | region_leave | r1  |  0.011717 |  0.009122 | index      | label:preload\n+------------\n+\n+The PERF target is intended for interactive performance analysis\n+during development and is quite noisy.\n+\n+=== EVENT Format\n+\n+EVENT format is enabled when the `GIT_TR2_EVENT` environment\n+variable is set.\n+\n+Each event is a JSON-object containing multiple key/value pairs\n+written as a single line and followed by a LF.\n+\n+------------\n+'{' <key> ':' <value> [',' <key> ':' <value>]* '}' LF\n+------------\n+\n+Some key/value pairs are common to all events and some are\n+event-specific.\n+\n+==== Common Key/Value Pairs\n+\n+The following key/value pairs are common to all events:\n+\n+------------\n+{\n+\t\"event\":\"version\",\n+\t\"sid\":\"1547659722619736-11614\",\n+\t\"thread\":\"main\",\n+\t\"time\":\"2019-01-16 17:28:42.620713\",\n+\t\"file\":\"common-main.c\",\n+\t\"line\":38,\n+\t...\n+}\n+------------\n+\n+`\"event\":<event>`::\n+\tis the event name.\n+\n+`\"sid\":<sid>`::\n+\tis the session-id.  This is a unique string to identify the\n+\tprocess instance to allow all events emitted by a process to\n+\tbe identified.  A session-id is used instead of a PID because\n+\tPIDs are recycled by the OS.  For child git processes, the\n+\tsession-id is prepended with the session-id of the parent git\n+\tprocess to allow parent-child relationships to be identified\n+\tduring post-processing.\n+\n+`\"thread\":<thread>`::\n+\tis the thread name.\n+\n+`\"time\":<time>`::\n+\tis the UTC time of the event.\n+\n+`\"file\":<filename>`::\n+\tis source file generating the event.\n+\n+`\"line\":<line-number>`::\n+\tis the integer source line number generating the event.\n+\n+`\"repo\":<repo-id>`::\n+\twhen present, is the integer repo-id as described previously.\n+\n+If `GIT_TR2_EVENT_BRIEF` is true, the `file` and `line` fields are omitted\n+from all events and the `time` field is only present on the \"start\" and\n+\"atexit\" events.\n+\n+==== Event-Specific Key/Value Pairs\n+\n+`\"version\"`::\n+\tThis event gives the version of the executable and the EVENT format.\n++\n+------------\n+{\n+\t\"event\":\"version\",\n+\t...\n+\t\"evt\":\"1\",\t\t       # EVENT format version\n+\t\"exe\":\"2.20.1.155.g426c96fcdb\" # git version\n+}\n+------------\n+\n+`\"start\"`::\n+\tThis event contains the complete argv received by main().\n++\n+------------\n+{\n+\t\"event\":\"start\",\n+\t...\n+\t\"argv\":[\"git\",\"version\"]\n+}\n+------------\n+\n+`\"exit\"`::\n+\tThis event is emitted when git calls `exit()`.\n++\n+------------\n+{\n+\t\"event\":\"exit\",\n+\t...\n+\t\"t_abs\":0.001227, # elapsed time in seconds\n+\t\"code\":0\t  # exit code\n+}\n+------------\n+\n+`\"atexit\"`::\n+\tThis event is emitted by the Trace2 `atexit` routine during\n+\tfinal shutdown.  It should be the last event emitted by the\n+\tprocess.\n++\n+(The elapsed time reported here is greater than the time reported in\n+the \"exit\" event because it runs after all other atexit tasks have\n+completed.)\n++\n+------------\n+{\n+\t\"event\":\"atexit\",\n+\t...\n+\t\"t_abs\":0.001227, # elapsed time in seconds\n+\t\"code\":0          # exit code\n+}\n+------------\n+\n+`\"signal\"`::\n+\tThis event is emitted when the program is terminated by a user\n+\tsignal.  Depending on the platform, the signal event may\n+\tprevent the \"atexit\" event from being generated.\n++\n+------------\n+{\n+\t\"event\":\"signal\",\n+\t...\n+\t\"t_abs\":0.001227,  # elapsed time in seconds\n+\t\"signal\":13        # SIGTERM, SIGINT, etc.\n+}\n+------------\n+\n+`\"error\"`::\n+\tThis event is emitted when one of the `error()`, `die()`,\n+\tor `usage()` functions are called.\n++\n+------------\n+{\n+\t\"event\":\"error\",\n+\t...\n+\t\"msg\":\"invalid option: --cahced\", # formatted error message\n+\t\"fmt\":\"invalid option: %s\"\t  # error format string\n+}\n+------------\n++\n+The error event may be emitted more than once.  The format string\n+allows post-processors to group errors by type without worrying\n+about specific error arguments.\n+\n+`\"cmd_path\"`::\n+\tThis event contains the discovered full path of the git\n+\texecutable (on platforms that are configured to resolve it).\n++\n+------------\n+{\n+\t\"event\":\"cmd_path\",\n+\t...\n+\t\"path\":\"C:/work/gfw/git.exe\"\n+}\n+------------\n+\n+`\"cmd_verb\"`::\n+\tThis event contains the primary verb for this git process\n+\tand the hierarchy of verbs from parent git processes.\n++\n+------------\n+{\n+\t\"event\":\"cmd_verb\",\n+\t...\n+\t\"name\":\"pack-objects\",\n+\t\"hierarchy\":\"push/pack-objects\"\n+}\n+------------\n++\n+Normally, the \"name\" field contains the canonical name of the\n+command.  When a canonical verb is not available, one of\n+these special values are used:\n++\n+------------\n+\"_query_\"            # \"git --html-path\"\n+\"_run_dashed_\"       # when \"git foo\" tries to run \"git-foo\"\n+\"_run_shell_alias_\"  # alias expansion to a shell command\n+\"_run_git_alias_\"    # alias expansion to a git command\n+\"_usage_\"            # usage error\n+------------\n+\n+`\"cmd_subverb\"`::\n+\tThis event, when present, describes the variant of the main\n+\tverb.  This event may be emitted more than once.\n++\n+------------\n+{\n+\t\"event\":\"cmd_subverb\",\n+\t...\n+\t\"name\":\"branch\"\n+}\n+------------\n++\n+The \"name\" field is an arbitrary string that makes sense in the\n+context of the primary verb. For example, checkout can checkout a\n+branch or an individual file.  And these variations typically have\n+different performance characteristics that are not comparable.\n+\n+`\"alias\"`::\n+\tThis event is present when an alias is expanded.\n++\n+------------\n+{\n+\t\"event\":\"alias\",\n+\t...\n+\t\"alias\":\"l\",\t\t # registered alias\n+\t\"argv\":[\"log\",\"--graph\"] # alias expansion\n+}\n+------------\n+\n+`\"child_start\"`::\n+\tThis event describes a child process that is about to be\n+\tspawned.\n++\n+------------\n+{\n+\t\"event\":\"child_start\",\n+\t...\n+\t\"child_id\":2,\n+\t\"child_class\":\"?\",\n+\t\"use_shell\":false,\n+\t\"argv\":[\"git\",\"rev-list\",\"--objects\",\"--stdin\",\"--not\",\"--all\",\"--quiet\"]\n+\n+\t\"hook_name\":\"<hook_name>\"  # present when child_class is \"hook\"\n+\t\"cd\":\"<path>\"\t\t   # present when cd is required\n+}\n+------------\n++\n+The \"child_id\" field can be used to match this child_start with the\n+corresponding child_exit event.\n++\n+The \"child_class\" field is a rough classification, such as \"editor\",\n+\"pager\", \"transport/*\", and \"hook\".  Unclassified children are classified\n+with \"?\".\n+\n+`\"child_exit\"`::\n+\tThis event is generated after the current process has returned\n+\tfrom the waitpid() and collected the exit information from the\n+\tchild.\n++\n+------------\n+{\n+\t\"event\":\"child_exit\",\n+\t...\n+\t\"child_id\":2,\n+\t\"pid\":14708,\t # child PID\n+\t\"code\":0,\t # child exit-code\n+\t\"t_rel\":0.110605 # observed run-time of child process\n+}\n+------------\n++\n+Note that the session-id of the child process is not available to\n+the current/spawning process, so the child's PID is reported here as\n+a hint for post-processing.  (But it is only a hint because the child\n+proces may be a shell script which doesn't have a session-id.)\n++\n+Note that the `t_rel` field contains the observed run time in seconds\n+for the child process (starting before the fork/exec/spawn and\n+stopping after the waitpid() and includes OS process creation overhead).\n+So this time will be slightly larger than the atexit time reported by\n+the child process itself.\n+\n+`\"exec\"`::\n+\tThis event is generated before git attempts to `exec()`\n+\tanother command rather than starting a child process.\n++\n+------------\n+{\n+\t\"event\":\"exec\",\n+\t...\n+\t\"exec_id\":0,\n+\t\"exe\":\"git\",\n+\t\"argv\":[\"foo\", \"bar\"]\n+}\n+------------\n++\n+The \"exec_id\" field is a command-unique id and is only useful if the\n+`exec()` fails and a corresponding exec_result event is generated.\n+\n+`\"exec_result\"`::\n+\tThis event is generated if the `exec()` fails and control\n+\treturns to the current git command.\n++\n+------------\n+{\n+\t\"event\":\"exec_result\",\n+\t...\n+\t\"exec_id\":0,\n+\t\"code\":1      # error code (errno) from exec()\n+}\n+------------\n+\n+`\"thread_start\"`::\n+\tThis event is generated when a thread is started.  It is\n+\tgenerated from *within* the new thread's thread-proc (for TLS\n+\treasons).\n++\n+------------\n+{\n+\t\"event\":\"thread_start\",\n+\t...\n+\t\"thread\":\"th02:preload_thread\" # thread name\n+}\n+------------\n+\n+`\"thread_exit\"`::\n+\tThis event is generated when a thread exits.  It is generated\n+\tfrom *within* the thread's thread-proc (for TLS reasons).\n++\n+------------\n+{\n+\t\"event\":\"thread_exit\",\n+\t...\n+\t\"thread\":\"th02:preload_thread\", # thread name\n+\t\"t_rel\":0.007328                # thread elapsed time\n+}\n+------------\n+\n+`\"def_param\"`::\n+\tThis event is generated to log a global parameter.\n++\n+------------\n+{\n+\t\"event\":\"def_param\",\n+\t...\n+\t\"param\":\"core.abbrev\",\n+\t\"value\":\"7\"\n+}\n+------------\n+\n+`\"def_repo\"`::\n+\tThis event defines a repo-id and associates it with the root\n+\tof the worktree.\n++\n+------------\n+{\n+\t\"event\":\"def_repo\",\n+\t...\n+\t\"repo\":1,\n+\t\"worktree\":\"/Users/jeffhost/work/gfw\"\n+}\n+------------\n++\n+As stated earlier, the repo-id is currently always 1, so there will\n+only be one def_repo event.  Later, if in-proc submodules are\n+supported, a def_repo event should be emitted for each submodule\n+visited.\n+\n+`\"region_enter\"`::\n+\tThis event is generated when entering a region.\n++\n+------------\n+{\n+\t\"event\":\"region_enter\",\n+\t...\n+\t\"repo\":1,                # optional\n+\t\"nesting\":1,             # current region stack depth\n+\t\"category\":\"index\",      # optional\n+\t\"label\":\"do_read_index\", # optional\n+\t\"msg\":\".git/index\"       # optional\n+}\n+------------\n++\n+The `category` field may be used in a future enhancement to\n+do category-based filtering.\n++\n+The `GIT_TR2_EVENT_NESTING` environment variable can be used to\n+filter deeply nested regions and data events.  It defaults to \"2\".\n+\n+`\"region_leave\"`::\n+\tThis event is generated when leaving a region.\n++\n+------------\n+{\n+\t\"event\":\"region_leave\",\n+\t...\n+\t\"repo\":1,                # optional\n+\t\"t_rel\":0.002876,        # time spent in region in seconds\n+\t\"nesting\":1,             # region stack depth\n+\t\"category\":\"index\",      # optional\n+\t\"label\":\"do_read_index\", # optional\n+\t\"msg\":\".git/index\"       # optional\n+}\n+------------\n+\n+`\"data\"`::\n+\tThis event is generated to log a thread- and region-local\n+\tkey/value pair.\n++\n+------------\n+{\n+\t\"event\":\"data\",\n+\t...\n+\t\"repo\":1,              # optional\n+\t\"t_abs\":0.024107,      # absolute elapsed time\n+\t\"t_rel\":0.001031,      # elapsed time in region/thread\n+\t\"nesting\":2,           # region stack depth\n+\t\"category\":\"index\",\n+\t\"key\":\"read/cache_nr\",\n+\t\"value\":\"3552\"\n+}\n+------------\n++\n+The \"value\" field may be an integer or a string.\n+\n+`\"data-json\"`::\n+\tThis event is generated to log a pre-formatted JSON string\n+\tcontaining structured data.\n++\n+------------\n+{\n+\t\"event\":\"data_json\",\n+\t...\n+\t\"repo\":1,              # optional\n+\t\"t_abs\":0.015905,\n+\t\"t_rel\":0.015905,\n+\t\"nesting\":1,\n+\t\"category\":\"process\",\n+\t\"key\":\"windows/ancestry\",\n+\t\"value\":[\"bash.exe\",\"bash.exe\"]\n+}\n+------------\n+\n+== Example Trace2 API Usage\n+\n+Here is a hypothetical usage of the Trace2 API showing the intended\n+usage (without worrying about the actual Git details).\n+\n+Initialization::\n+\n+\tInitialization happens in `main()`.  Behind the scenes, an\n+\t`atexit` and `signal` handler are registered.\n++\n+----------------\n+int main(int argc, const char **argv)\n+{\n+\tint exit_code;\n+\n+\ttrace2_initialize();\n+\ttrace2_cmd_start(argv);\n+\n+\texit_code = cmd_main(argc, argv);\n+\n+\ttrace2_cmd_exit(exit_code);\n+\n+\treturn exit_code;\n+}\n+----------------\n+\n+Command Details::\n+\n+\tAfter the basics are established, additional command\n+\tinformation can be sent to Trace2 as it is discovered.\n++\n+----------------\n+int cmd_checkout(int argc, const char **argv)\n+{\n+\ttrace2_cmd_verb(\"checkout\");\n+\ttrace2_cmd_subverb(\"branch\");\n+\ttrace2_def_repo(the_repository);\n+\n+\t// emit \"def_param\" messages for \"interesting\" config settings.\n+\ttrace2_cmd_list_config();\n+\n+\tif (do_something())\n+\t    trace2_cmd_error(\"Path '%s': cannot do something\", path);\n+\n+\treturn 0;\n+}\n+----------------\n+\n+Child Processes::\n+\n+\tWrap code spawning child processes.\n++\n+----------------\n+void run_child(...)\n+{\n+\tint child_exit_code;\n+\tstruct child_process cmd = CHILD_PROCESS_INIT;\n+\t...\n+\tcmd.trace2_child_class = \"editor\";\n+\n+\ttrace2_child_start(&cmd);\n+\tchild_exit_code = spawn_child_and_wait_for_it();\n+\ttrace2_child_exit(&cmd, child_exit_code);\n+}\n+----------------\n++\n+For example, the following fetch command spawned ssh, index-pack,\n+rev-list, and gc.  This example also shows that fetch took\n+5.199 seconds and of that 4.932 was in ssh.\n++\n+----------------\n+$ export GIT_TR2_BRIEF=1\n+$ export GIT_TR2=~/log.normal\n+$ git fetch origin\n+...\n+----------------\n++\n+----------------\n+$ cat ~/log.normal\n+version 2.20.1.vfs.1.1.47.g534dbe1ad1\n+start git fetch origin\n+worktree /Users/jeffhost/work/gfw\n+cmd_verb fetch (fetch)\n+child_start[0] ssh git@github.com ...\n+child_start[1] git index-pack ...\n+... (Trace2 events from child processes omitted)\n+child_exit[1] pid:14707 code:0 elapsed:0.076353\n+child_exit[0] pid:14706 code:0 elapsed:4.931869\n+child_start[2] git rev-list ...\n+... (Trace2 events from child process omitted)\n+child_exit[2] pid:14708 code:0 elapsed:0.110605\n+child_start[3] git gc --auto\n+... (Trace2 events from child process omitted)\n+child_exit[3] pid:14709 code:0 elapsed:0.006240\n+exit elapsed:5.198503 code:0\n+atexit elapsed:5.198541 code:0\n+----------------\n++\n+When a git process is a (direct or indirect) child of another\n+git process, it inherits Trace2 context information.  This\n+allows the child to print a verb hierarchy.  This example\n+shows gc as child[3] of fetch.  When the gc process reports\n+its verb as \"gc\", it also reports the hierarchy as \"fetch/gc\".\n+(In this example, trace2 messages from the child process is\n+indented for clarity.)\n++\n+----------------\n+$ export GIT_TR2_BRIEF=1\n+$ export GIT_TR2=~/log.normal\n+$ git fetch origin\n+...\n+----------------\n++\n+----------------\n+$ cat ~/log.normal\n+version 2.20.1.160.g5676107ecd.dirty\n+start git fetch official\n+worktree /Users/jeffhost/work/gfw\n+cmd_verb fetch (fetch)\n+...\n+child_start[3] git gc --auto\n+    version 2.20.1.160.g5676107ecd.dirty\n+    start /Users/jeffhost/work/gfw/git gc --auto\n+    worktree /Users/jeffhost/work/gfw\n+    cmd_verb gc (fetch/gc)\n+    exit elapsed:0.001959 code:0\n+    atexit elapsed:0.001997 code:0\n+child_exit[3] pid:20303 code:0 elapsed:0.007564\n+exit elapsed:3.868938 code:0\n+atexit elapsed:3.868970 code:0\n+----------------\n+\n+Regions::\n+\n+\tRegions can be use to time an interesting section of code.\n++\n+----------------\n+void wt_status_collect(struct wt_status *s)\n+{\n+\ttrace2_region_enter(\"status\", \"worktrees\", s->repo);\n+\twt_status_collect_changes_worktree(s);\n+\ttrace2_region_leave(\"status\", \"worktrees\", s->repo);\n+\n+\ttrace2_region_enter(\"status\", \"index\", s->repo);\n+\twt_status_collect_changes_index(s);\n+\ttrace2_region_leave(\"status\", \"index\", s->repo);\n+\n+\ttrace2_region_enter(\"status\", \"untracked\", s->repo);\n+\twt_status_collect_untracked(s);\n+\ttrace2_region_leave(\"status\", \"untracked\", s->repo);\n+}\n+\n+void wt_status_print(struct wt_status *s)\n+{\n+\ttrace2_region_enter(\"status\", \"print\", s->repo);\n+\tswitch (s->status_format) {\n+\t    ...\n+\t}\n+\ttrace2_region_leave(\"status\", \"print\", s->repo);\n+}\n+----------------\n++\n+In this example, scanning for untracked files ran from +0.012568 to\n++0.027149 (since the process started) and took 0.014581 seconds.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+\n+$ cat ~/log.perf\n+d0 | main                     | version      |     |           |           |            | 2.20.1.160.g5676107ecd.dirty\n+d0 | main                     | start        |     |           |           |            | git status\n+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n+d0 | main                     | cmd_verb     |     |           |           |            | status (status)\n+...\n+d0 | main                     | region_enter | r1  |  0.010988 |           | status     | label:worktrees\n+d0 | main                     | region_leave | r1  |  0.011236 |  0.000248 | status     | label:worktrees\n+d0 | main                     | region_enter | r1  |  0.011260 |           | status     | label:index\n+d0 | main                     | region_leave | r1  |  0.012542 |  0.001282 | status     | label:index\n+d0 | main                     | region_enter | r1  |  0.012568 |           | status     | label:untracked\n+d0 | main                     | region_leave | r1  |  0.027149 |  0.014581 | status     | label:untracked\n+d0 | main                     | region_enter | r1  |  0.027411 |           | status     | label:print\n+d0 | main                     | region_leave | r1  |  0.028741 |  0.001330 | status     | label:print\n+d0 | main                     | exit         |     |  0.028778 |           |            | code:0\n+d0 | main                     | atexit       |     |  0.028809 |           |            | code:0\n+----------------\n++\n+Regions may be nested.  This causes messages to be indented in the\n+PERF target, for example.\n+Elapsed times are relative to the start of the correpsonding nesting\n+level as expected.  For example, if we add region message to:\n++\n+----------------\n+static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n+\tstruct index_state *istate, const char *base, int baselen,\n+\tstruct untracked_cache_dir *untracked, int check_only,\n+\tint stop_at_first_file, const struct pathspec *pathspec)\n+{\n+\tenum path_treatment state, subdir_state, dir_state = path_none;\n+\n+\ttrace2_region_enter_printf(\"dir\", \"read_recursive\", NULL, \"%.*s\", baselen, base);\n+\t...\n+\ttrace2_region_leave_printf(\"dir\", \"read_recursive\", NULL, \"%.*s\", baselen, base);\n+\treturn dir_state;\n+}\n+----------------\n++\n+We can further investigate the time spent scanning for untracked files.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+$ cat ~/log.perf\n+d0 | main                     | version      |     |           |           |            | 2.20.1.162.gb4ccea44db.dirty\n+d0 | main                     | start        |     |           |           |            | git status\n+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n+d0 | main                     | cmd_verb     |     |           |           |            | status (status)\n+...\n+d0 | main                     | region_enter | r1  |  0.015047 |           | status     | label:untracked\n+d0 | main                     | region_enter |     |  0.015132 |           | dir        | ..label:read_recursive\n+d0 | main                     | region_enter |     |  0.016341 |           | dir        | ....label:read_recursive vcs-svn/\n+d0 | main                     | region_leave |     |  0.016422 |  0.000081 | dir        | ....label:read_recursive vcs-svn/\n+d0 | main                     | region_enter |     |  0.016446 |           | dir        | ....label:read_recursive xdiff/\n+d0 | main                     | region_leave |     |  0.016522 |  0.000076 | dir        | ....label:read_recursive xdiff/\n+d0 | main                     | region_enter |     |  0.016612 |           | dir        | ....label:read_recursive git-gui/\n+d0 | main                     | region_enter |     |  0.016698 |           | dir        | ......label:read_recursive git-gui/po/\n+d0 | main                     | region_enter |     |  0.016810 |           | dir        | ........label:read_recursive git-gui/po/glossary/\n+d0 | main                     | region_leave |     |  0.016863 |  0.000053 | dir        | ........label:read_recursive git-gui/po/glossary/\n+...\n+d0 | main                     | region_enter |     |  0.031876 |           | dir        | ....label:read_recursive builtin/\n+d0 | main                     | region_leave |     |  0.032270 |  0.000394 | dir        | ....label:read_recursive builtin/\n+d0 | main                     | region_leave |     |  0.032414 |  0.017282 | dir        | ..label:read_recursive\n+d0 | main                     | region_leave | r1  |  0.032454 |  0.017407 | status     | label:untracked\n+...\n+d0 | main                     | exit         |     |  0.034279 |           |            | code:0\n+d0 | main                     | atexit       |     |  0.034322 |           |            | code:0\n+----------------\n++\n+Trace2 regions are similar to the existing trace_performance_enter()\n+and trace_performance_leave() routines, but are thread safe and\n+maintain per-thread stacks of timers.\n+\n+Data Messages::\n+\n+\tData messages added to a region.\n++\n+----------------\n+int read_index_from(struct index_state *istate, const char *path,\n+\tconst char *gitdir)\n+{\n+\ttrace2_region_enter_printf(\"index\", \"do_read_index\", the_repository, \"%s\", path);\n+\n+\t...\n+\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/version\", istate->version);\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/cache_nr\", istate->cache_nr);\n+\n+\ttrace2_region_leave_printf(\"index\", \"do_read_index\", the_repository, \"%s\", path);\n+}\n+----------------\n++\n+This example shows that the index contained 3552 entries.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+$ cat ~/log.perf\n+d0 | main                     | version      |     |           |           |            | 2.20.1.156.gf9916ae094.dirty\n+d0 | main                     | start        |     |           |           |            | git status\n+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n+d0 | main                     | cmd_verb     |     |           |           |            | status (status)\n+d0 | main                     | region_enter | r1  |  0.001791 |           | index      | label:do_read_index .git/index\n+d0 | main                     | data         | r1  |  0.002494 |  0.000703 | index      | ..read/version:2\n+d0 | main                     | data         | r1  |  0.002520 |  0.000729 | index      | ..read/cache_nr:3552\n+d0 | main                     | region_leave | r1  |  0.002539 |  0.000748 | index      | label:do_read_index .git/index\n+...\n+----------------\n+\n+Thread Events::\n+\n+\tThread messages added to a thread-proc.\n++\n+For example, the multithreaded preload-index code can be\n+instrumented with a region around the thread pool and then\n+per-thread start and exit events within the threadproc.\n++\n+----------------\n+static void *preload_thread(void *_data)\n+{\n+\t// start the per-thread clock and emit a message.\n+\ttrace2_thread_start(\"preload_thread\");\n+\n+\t// report which chunk of the array this thread was assigned.\n+\ttrace2_data_intmax(\"index\", the_repository, \"offset\", p->offset);\n+\ttrace2_data_intmax(\"index\", the_repository, \"count\", nr);\n+\n+\tdo {\n+\t    ...\n+\t} while (--nr > 0);\n+\t...\n+\n+\t// report elapsed time taken by this thread.\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+void preload_index(struct index_state *index,\n+\tconst struct pathspec *pathspec,\n+\tunsigned int refresh_flags)\n+{\n+\ttrace2_region_enter(\"index\", \"preload\", the_repository);\n+\n+\tfor (i = 0; i < threads; i++) {\n+\t    ... /* create thread */\n+\t}\n+\n+\tfor (i = 0; i < threads; i++) {\n+\t    ... /* join thread */\n+\t}\n+\n+\ttrace2_region_leave(\"index\", \"preload\", the_repository);\n+}\n+----------------\n++\n+In this example preload_index() was executed by the `main` thread\n+and started the `preload` region.  Seven threads, named\n+`th01:preload_thread` through `th07:preload_thread`, were started.\n+Events from each thread are atomically appended to the shared target\n+stream as they occur so they may appear in random order with respect\n+other threads. Finally, the main thread waits for the threads to\n+finish and leaves the region.\n++\n+Data events are tagged with the active thread name.  They are used\n+to report the per-thread parameters.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+$ cat ~/log.perf\n+...\n+d0 | main                     | region_enter | r1  |  0.002595 |           | index      | label:preload\n+d0 | th01:preload_thread      | thread_start |     |  0.002699 |           |            |\n+d0 | th02:preload_thread      | thread_start |     |  0.002721 |           |            |\n+d0 | th01:preload_thread      | data         | r1  |  0.002736 |  0.000037 | index      | offset:0\n+d0 | th02:preload_thread      | data         | r1  |  0.002751 |  0.000030 | index      | offset:2032\n+d0 | th03:preload_thread      | thread_start |     |  0.002711 |           |            |\n+d0 | th06:preload_thread      | thread_start |     |  0.002739 |           |            |\n+d0 | th01:preload_thread      | data         | r1  |  0.002766 |  0.000067 | index      | count:508\n+d0 | th06:preload_thread      | data         | r1  |  0.002856 |  0.000117 | index      | offset:2540\n+d0 | th03:preload_thread      | data         | r1  |  0.002824 |  0.000113 | index      | offset:1016\n+d0 | th04:preload_thread      | thread_start |     |  0.002710 |           |            |\n+d0 | th02:preload_thread      | data         | r1  |  0.002779 |  0.000058 | index      | count:508\n+d0 | th06:preload_thread      | data         | r1  |  0.002966 |  0.000227 | index      | count:508\n+d0 | th07:preload_thread      | thread_start |     |  0.002741 |           |            |\n+d0 | th07:preload_thread      | data         | r1  |  0.003017 |  0.000276 | index      | offset:3048\n+d0 | th05:preload_thread      | thread_start |     |  0.002712 |           |            |\n+d0 | th05:preload_thread      | data         | r1  |  0.003067 |  0.000355 | index      | offset:1524\n+d0 | th05:preload_thread      | data         | r1  |  0.003090 |  0.000378 | index      | count:508\n+d0 | th07:preload_thread      | data         | r1  |  0.003037 |  0.000296 | index      | count:504\n+d0 | th03:preload_thread      | data         | r1  |  0.002971 |  0.000260 | index      | count:508\n+d0 | th04:preload_thread      | data         | r1  |  0.002983 |  0.000273 | index      | offset:508\n+d0 | th04:preload_thread      | data         | r1  |  0.007311 |  0.004601 | index      | count:508\n+d0 | th05:preload_thread      | thread_exit  |     |  0.008781 |  0.006069 |            |\n+d0 | th01:preload_thread      | thread_exit  |     |  0.009561 |  0.006862 |            |\n+d0 | th03:preload_thread      | thread_exit  |     |  0.009742 |  0.007031 |            |\n+d0 | th06:preload_thread      | thread_exit  |     |  0.009820 |  0.007081 |            |\n+d0 | th02:preload_thread      | thread_exit  |     |  0.010274 |  0.007553 |            |\n+d0 | th07:preload_thread      | thread_exit  |     |  0.010477 |  0.007736 |            |\n+d0 | th04:preload_thread      | thread_exit  |     |  0.011657 |  0.008947 |            |\n+d0 | main                     | region_leave | r1  |  0.011717 |  0.009122 | index      | label:preload\n+...\n+d0 | main                     | exit         |     |  0.029996 |           |            | code:0\n+d0 | main                     | atexit       |     |  0.030027 |           |            | code:0\n+----------------\n++\n+In this example, the preload region took 0.009122 seconds.  The 7 threads\n+took between 0.006069 and 0.008947 seconds to work on their portion of\n+the index.  Thread \"th01\" worked on 508 items at offset 0.  Thread \"th02\"\n+worked on 508 items at offset 2032.  Thread \"th04\" worked on 508 itemts\n+at offset 508.\n++\n+This example also shows that thread names are assigned in a racy manner\n+as each thread starts and allocates TLS storage.\n+\n+== Future Work\n+\n+=== Relationship to the Existing Trace Api (api-trace.txt)\n+\n+There are a few issues to resolve before we can completely\n+switch to Trace2.\n+\n+* Updating existing tests that assume GIT_TRACE format messages.\n+\n+* How to best handle custom GIT_TRACE_<key> messages?\n+\n+** The GIT_TRACE_<key> mechanism allows each <key> to write to a\n+different file (in addition to just stderr).\n+\n+** Do we want to maintain that ability or simply write to the existing\n+Trace2 targets (and convert <key> to a \"category\").\n+\n-- \ngitgitgadget\n\n"},{"id":"368168","messageId":"785f6f866e1c4b5848abfeb0b74ea641cea2cce7.1548877863.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 06/14] trace2:data: add trace2 sub-process classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T19:51:09Z","receivedAt":"2019-01-30T19:51:16Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 classification for long-running processes\nstarted in sub-process.c\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n sub-process.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/sub-process.c b/sub-process.c\nindex 8d2a1707cf..3f4af93555 100644\n--- a/sub-process.c\n+++ b/sub-process.c\n@@ -88,6 +88,7 @@ int subprocess_start(struct hashmap *hashmap, struct subprocess_entry *entry, co\n \tprocess->out = -1;\n \tprocess->clean_on_exit = 1;\n \tprocess->clean_on_exit_handler = subprocess_exit_handler;\n+\tprocess->trace2_child_class = \"subprocess\";\n \n \terr = start_command(process);\n \tif (err) {\n-- \ngitgitgadget\n\n"},{"id":"368169","messageId":"52aace533b296efe9b41268a80e82b25c0cc54b5.1548877863.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 08/14] trace2:data: add trace2 hook classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T19:51:11Z","receivedAt":"2019-01-30T19:51:16Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nClassify certain child processes as hooks.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/am.c           | 1 +\n builtin/receive-pack.c | 4 ++++\n builtin/worktree.c     | 1 +\n sequencer.c            | 2 ++\n transport.c            | 1 +\n 5 files changed, 9 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 95370313b6..a81e61c1bc 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -468,6 +468,7 @@ static int run_post_rewrite_hook(const struct am_state *state)\n \n \tcp.in = xopen(am_path(state, \"rewritten\"), O_RDONLY);\n \tcp.stdout_to_stderr = 1;\n+\tcp.trace2_hook_name = \"post-rewrite\";\n \n \tret = run_command(&cp);\n \ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 33187bd8e9..6eef8575b9 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -694,6 +694,8 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,\n \tproc.argv = argv;\n \tproc.in = -1;\n \tproc.stdout_to_stderr = 1;\n+\tproc.trace2_hook_name = hook_name;\n+\n \tif (feed_state->push_options) {\n \t\tint i;\n \t\tfor (i = 0; i < feed_state->push_options->nr; i++)\n@@ -807,6 +809,7 @@ static int run_update_hook(struct command *cmd)\n \tproc.stdout_to_stderr = 1;\n \tproc.err = use_sideband ? -1 : 0;\n \tproc.argv = argv;\n+\tproc.trace2_hook_name = \"update\";\n \n \tcode = start_command(&proc);\n \tif (code)\n@@ -1190,6 +1193,7 @@ static void run_update_post_hook(struct command *commands)\n \tproc.no_stdin = 1;\n \tproc.stdout_to_stderr = 1;\n \tproc.err = use_sideband ? -1 : 0;\n+\tproc.trace2_hook_name = \"post-update\";\n \n \tif (!start_command(&proc)) {\n \t\tif (use_sideband)\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 3f9907fcc9..6cc094a453 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -402,6 +402,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\t\tcp.dir = path;\n \t\t\tcp.env = env;\n \t\t\tcp.argv = NULL;\n+\t\t\tcp.trace2_hook_name = \"post-checkout\";\n \t\t\targv_array_pushl(&cp.args, absolute_path(hook),\n \t\t\t\t\t oid_to_hex(&null_oid),\n \t\t\t\t\t oid_to_hex(&commit->object.oid),\ndiff --git a/sequencer.c b/sequencer.c\nindex 213815dbfc..c1e36196a1 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1103,6 +1103,7 @@ static int run_rewrite_hook(const struct object_id *oldoid,\n \tproc.argv = argv;\n \tproc.in = -1;\n \tproc.stdout_to_stderr = 1;\n+\tproc.trace2_hook_name = \"post-rewrite\";\n \n \tcode = start_command(&proc);\n \tif (code)\n@@ -3784,6 +3785,7 @@ static int pick_commits(struct repository *r,\n \t\t\t\thook.in = open(rebase_path_rewritten_list(),\n \t\t\t\t\tO_RDONLY);\n \t\t\t\thook.stdout_to_stderr = 1;\n+\t\t\t\thook.trace2_hook_name = \"post-rewrite\";\n \t\t\t\targv_array_push(&hook.args, post_rewrite_hook);\n \t\t\t\targv_array_push(&hook.args, \"rebase\");\n \t\t\t\t/* we don't care if this hook failed */\ndiff --git a/transport.c b/transport.c\nindex 99678153c1..ed5a733c4a 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1061,6 +1061,7 @@ static int run_pre_push_hook(struct transport *transport,\n \n \tproc.argv = argv;\n \tproc.in = -1;\n+\tproc.trace2_hook_name = \"pre-push\";\n \n \tif (start_command(&proc)) {\n \t\tfinish_command(&proc);\n-- \ngitgitgadget\n\n"},{"id":"368171","messageId":"d50832263863adf0c2240de80bc410e651365f1f.1548877863.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 10/14] pack-objects: add trace2 regions","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T19:51:13Z","receivedAt":"2019-01-30T19:51:18Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWhen studying the performance of 'git push' we would like to know\nhow much time is spent at various parts of the command. One area\nthat could cause performance trouble is 'git pack-objects'.\n\nAdd trace2 regions around the three main actions taken in this\ncommand:\n\n1. Enumerate objects.\n2. Prepare pack.\n3. Write pack-file.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/pack-objects.c | 16 +++++++++++++++-\n 1 file changed, 15 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 0a70d04604..8a64c2868e 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -33,6 +33,7 @@\n #include \"object-store.h\"\n #include \"dir.h\"\n #include \"midx.h\"\n+#include \"trace2.h\"\n \n #define IN_PACK(obj) oe_in_pack(&to_pack, obj)\n #define SIZE(obj) oe_size(&to_pack, obj)\n@@ -3472,6 +3473,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n+\ttrace2_region_enter(\"pack-objects\", \"enumerate-objects\",\n+\t\t\t    the_repository);\n \tprepare_packing_data(the_repository, &to_pack);\n \n \tif (progress)\n@@ -3486,12 +3489,23 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)\n \tif (include_tag && nr_result)\n \t\tfor_each_ref(add_ref_tag, NULL);\n \tstop_progress(&progress_state);\n+\ttrace2_region_leave(\"pack-objects\", \"enumerate-objects\",\n+\t\t\t    the_repository);\n \n \tif (non_empty && !nr_result)\n \t\treturn 0;\n-\tif (nr_result)\n+\tif (nr_result) {\n+\t\ttrace2_region_enter(\"pack-objects\", \"prepare-pack\",\n+\t\t\t\t    the_repository);\n \t\tprepare_pack(window, depth);\n+\t\ttrace2_region_leave(\"pack-objects\", \"prepare-pack\",\n+\t\t\t\t    the_repository);\n+\t}\n+\n+\ttrace2_region_enter(\"pack-objects\", \"write-pack-file\", the_repository);\n \twrite_pack_file();\n+\ttrace2_region_leave(\"pack-objects\", \"write-pack-file\", the_repository);\n+\n \tif (progress)\n \t\tfprintf_ln(stderr,\n \t\t\t   _(\"Total %\"PRIu32\" (delta %\"PRIu32\"),\"\n-- \ngitgitgadget\n\n"},{"id":"368170","messageId":"a2bcc6fd2804377a5599acf9bc6b99a017e83575.1548877863.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 09/14] trace2:data: add trace2 instrumentation to index read/write","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T19:51:12Z","receivedAt":"2019-01-30T19:51:19Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 events to measure reading and writing the index.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n read-cache.c | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 50 insertions(+), 1 deletion(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex bfff271a3d..240531d70d 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -2232,6 +2232,16 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)\n \t\tload_index_extensions(&p);\n \t}\n \tmunmap((void *)mmap, mmap_size);\n+\n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/version\",\n+\t\t\t   istate->version);\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/cache_nr\",\n+\t\t\t   istate->cache_nr);\n+\n \treturn istate->cache_nr;\n \n unmap:\n@@ -2263,9 +2273,17 @@ int read_index_from(struct index_state *istate, const char *path,\n \tif (istate->initialized)\n \t\treturn istate->cache_nr;\n \n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_region_enter_printf(\"index\", \"do_read_index\", the_repository,\n+\t\t\t\t   \"%s\", path);\n \ttrace_performance_enter();\n \tret = do_read_index(istate, path, 0);\n \ttrace_performance_leave(\"read cache %s\", path);\n+\ttrace2_region_leave_printf(\"index\", \"do_read_index\", the_repository,\n+\t\t\t\t   \"%s\", path);\n \n \tsplit_index = istate->split_index;\n \tif (!split_index || is_null_oid(&split_index->base_oid)) {\n@@ -2281,7 +2299,11 @@ int read_index_from(struct index_state *istate, const char *path,\n \n \tbase_oid_hex = oid_to_hex(&split_index->base_oid);\n \tbase_path = xstrfmt(\"%s/sharedindex.%s\", gitdir, base_oid_hex);\n+\ttrace2_region_enter_printf(\"index\", \"shared/do_read_index\",\n+\t\t\t\t   the_repository, \"%s\", base_path);\n \tret = do_read_index(split_index->base, base_path, 1);\n+\ttrace2_region_leave_printf(\"index\", \"shared/do_read_index\",\n+\t\t\t\t   the_repository, \"%s\", base_path);\n \tif (!oideq(&split_index->base_oid, &split_index->base->oid))\n \t\tdie(_(\"broken index, expect %s in %s, got %s\"),\n \t\t    base_oid_hex, base_path,\n@@ -2988,6 +3010,16 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \tistate->timestamp.sec = (unsigned int)st.st_mtime;\n \tistate->timestamp.nsec = ST_MTIME_NSEC(st);\n \ttrace_performance_since(start, \"write index, changed mask = %x\", istate->cache_changed);\n+\n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_data_intmax(\"index\", the_repository, \"write/version\",\n+\t\t\t   istate->version);\n+\ttrace2_data_intmax(\"index\", the_repository, \"write/cache_nr\",\n+\t\t\t   istate->cache_nr);\n+\n \treturn 0;\n }\n \n@@ -3007,7 +3039,18 @@ static int commit_locked_index(struct lock_file *lk)\n static int do_write_locked_index(struct index_state *istate, struct lock_file *lock,\n \t\t\t\t unsigned flags)\n {\n-\tint ret = do_write_index(istate, lock->tempfile, 0);\n+\tint ret;\n+\n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_region_enter_printf(\"index\", \"do_write_index\", the_repository,\n+\t\t\t\t   \"%s\", lock->tempfile->filename.buf);\n+\tret = do_write_index(istate, lock->tempfile, 0);\n+\ttrace2_region_leave_printf(\"index\", \"do_write_index\", the_repository,\n+\t\t\t\t   \"%s\", lock->tempfile->filename.buf);\n+\n \tif (ret)\n \t\treturn ret;\n \tif (flags & COMMIT_LOCK)\n@@ -3092,7 +3135,13 @@ static int write_shared_index(struct index_state *istate,\n \tint ret;\n \n \tmove_cache_to_base_index(istate);\n+\n+\ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n+\t\t\t\t   the_repository, \"%s\", (*temp)->filename.buf);\n \tret = do_write_index(si->base, *temp, 1);\n+\ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n+\t\t\t\t   the_repository, \"%s\", (*temp)->filename.buf);\n+\n \tif (ret)\n \t\treturn ret;\n \tret = adjust_shared_perm(get_tempfile_path(*temp));\n-- \ngitgitgadget\n\n"},{"id":"368172","messageId":"2513091f779df302d3d86f21e51e97840654d45f.1548877863.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 05/14] trace2:data: add editor/pager child classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T19:51:09Z","receivedAt":"2019-01-30T19:51:20Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 process classification for editor and pager\nchild processes.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n editor.c | 1 +\n pager.c  | 1 +\n 2 files changed, 2 insertions(+)\n\ndiff --git a/editor.c b/editor.c\nindex c985eee1f9..71547674ab 100644\n--- a/editor.c\n+++ b/editor.c\n@@ -78,6 +78,7 @@ static int launch_specified_editor(const char *editor, const char *path,\n \t\tp.argv = args;\n \t\tp.env = env;\n \t\tp.use_shell = 1;\n+\t\tp.trace2_child_class = \"editor\";\n \t\tif (start_command(&p) < 0)\n \t\t\treturn error(\"unable to start editor '%s'\", editor);\n \ndiff --git a/pager.c b/pager.c\nindex a768797fcf..4168460ae9 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -100,6 +100,7 @@ void prepare_pager_args(struct child_process *pager_process, const char *pager)\n \targv_array_push(&pager_process->args, pager);\n \tpager_process->use_shell = 1;\n \tsetup_pager_env(&pager_process->env_array);\n+\tpager_process->trace2_child_class = \"pager\";\n }\n \n void setup_pager(void)\n-- \ngitgitgadget\n\n"},{"id":"368173","messageId":"33e431be8812732235b6d5555e2c589fc85a8223.1548877863.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 11/14] trace2:data: add subverb to checkout command","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T19:51:14Z","receivedAt":"2019-01-30T19:51:22Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/checkout.c | 7 +++++++\n 1 file changed, 7 insertions(+)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 6fadf412e8..8939ae99ed 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -262,6 +262,8 @@ static int checkout_paths(const struct checkout_opts *opts,\n \tstruct lock_file lock_file = LOCK_INIT;\n \tint nr_checkouts = 0;\n \n+\ttrace2_cmd_subverb(opts->patch_mode ? \"patch\" : \"path\");\n+\n \tif (opts->track != BRANCH_TRACK_UNSPECIFIED)\n \t\tdie(_(\"'%s' cannot be used with updating paths\"), \"--track\");\n \n@@ -952,6 +954,9 @@ static int switch_branches(const struct checkout_opts *opts,\n \tvoid *path_to_free;\n \tstruct object_id rev;\n \tint flag, writeout_error = 0;\n+\n+\ttrace2_cmd_subverb(\"branch\");\n+\n \tmemset(&old_branch_info, 0, sizeof(old_branch_info));\n \told_branch_info.path = path_to_free = resolve_refdup(\"HEAD\", 0, &rev, &flag);\n \tif (old_branch_info.path)\n@@ -1189,6 +1194,8 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)\n \tint status;\n \tstruct strbuf branch_ref = STRBUF_INIT;\n \n+\ttrace2_cmd_subverb(\"unborn\");\n+\n \tif (!opts->new_branch)\n \t\tdie(_(\"You are on a branch yet to be born\"));\n \tstrbuf_addf(&branch_ref, \"refs/heads/%s\", opts->new_branch);\n-- \ngitgitgadget\n\n"},{"id":"368174","messageId":"pull.108.v3.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v2.git.gitgitgadget@gmail.com","subject":"[PATCH v3 00/14] Trace2 tracing facility","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T19:51:04Z","receivedAt":"2019-01-30T19:51:23Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"V3 addresses: [] re-fix the trace2 tests using an inline environment\nvariable rather than exporting and unsetting. [] overhaul the design\ndocument to include prototype declarations and more file format information.\n[] incorporate most of the suggestions from clang-format. [] add ability to\ntrace to a unix domain socket. [] added forward declarations suggested by\nRamsay. [] rebased onto current master to fixup conflict with\nsb/submodule-recursive-fetch-gets-the-tip that was merged yesterday.\n\n\n----------------------------------------------------------------------------\n\nV2 addresses: [] \"jh/trace2\" bad interaction with \"js/vsts-ci\" in \"pu\". []\ncoccinelle warnings in trace2/tr2_tgt_perf.c reported during CI testing.\n\n\n----------------------------------------------------------------------------\n\nThis patch series contains a greatly refactored version of my original\nTrace2 series [1] from August 2018.\n\nA new design doc in Documentation/technical/api-trace2.txt (in the first\ncommit) explains the relationship of Trace2 to the current tracing facility.\nCalls to the current tracing facility have not been changed, rather new\ntrace2 calls have been added so that both continue to work in parallel for\nthe time being.\n\n[1] https://public-inbox.org/git/pull.29.git.gitgitgadget@gmail.com/\n\nCc: gitster@pobox.comCc: peff@peff.netCc: jrnieder@gmail.com\n\nDerrick Stolee (1):\n  pack-objects: add trace2 regions\n\nJeff Hostetler (13):\n  trace2: Documentation/technical/api-trace2.txt\n  trace2: create new combined trace facility\n  trace2: collect platform-specific process information\n  trace2:data: add trace2 regions to wt-status\n  trace2:data: add editor/pager child classification\n  trace2:data: add trace2 sub-process classification\n  trace2:data: add trace2 transport child classification\n  trace2:data: add trace2 hook classification\n  trace2:data: add trace2 instrumentation to index read/write\n  trace2:data: add subverb to checkout command\n  trace2:data: add subverb to reset command\n  trace2:data: add subverb for rebase\n  trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh\n\n Documentation/technical/api-trace2.txt | 1347 ++++++++++++++++++++++++\n Makefile                               |   11 +\n builtin/am.c                           |    1 +\n builtin/checkout.c                     |    7 +\n builtin/pack-objects.c                 |   16 +-\n builtin/rebase.c                       |   17 +\n builtin/receive-pack.c                 |    4 +\n builtin/reset.c                        |    6 +\n builtin/submodule--helper.c            |    9 +-\n builtin/worktree.c                     |    1 +\n cache.h                                |    1 +\n common-main.c                          |   13 +-\n compat/mingw.c                         |   11 +-\n compat/mingw.h                         |    3 +-\n compat/win32/ancestry.c                |  101 ++\n config.c                               |    2 +\n config.mak.uname                       |    2 +\n connect.c                              |    3 +\n editor.c                               |    1 +\n exec-cmd.c                             |    2 +\n git-compat-util.h                      |    7 +\n git.c                                  |   65 ++\n pager.c                                |    1 +\n read-cache.c                           |   51 +-\n remote-curl.c                          |    7 +\n repository.c                           |    2 +\n repository.h                           |    3 +\n run-command.c                          |   59 +-\n run-command.h                          |   13 +-\n sequencer.c                            |    2 +\n sh-i18n--envsubst.c                    |    3 +\n sub-process.c                          |    1 +\n submodule.c                            |   11 +-\n t/helper/test-parse-options.c          |    3 +\n t/helper/test-tool.c                   |    4 +\n t/helper/test-tool.h                   |    1 +\n t/helper/test-trace2.c                 |  274 +++++\n t/t0001-init.sh                        |    1 +\n t/t0210-trace2-normal.sh               |  135 +++\n t/t0210/scrub_normal.perl              |   48 +\n t/t0211-trace2-perf.sh                 |  153 +++\n t/t0211/scrub_perf.perl                |   76 ++\n t/t0212-trace2-event.sh                |  234 ++++\n t/t0212/parse_events.perl              |  251 +++++\n trace2.c                               |  807 ++++++++++++++\n trace2.h                               |  380 +++++++\n trace2/tr2_cfg.c                       |   90 ++\n trace2/tr2_cfg.h                       |   19 +\n trace2/tr2_dst.c                       |  198 ++++\n trace2/tr2_dst.h                       |   36 +\n trace2/tr2_sid.c                       |   67 ++\n trace2/tr2_sid.h                       |   18 +\n trace2/tr2_tbuf.c                      |   32 +\n trace2/tr2_tbuf.h                      |   23 +\n trace2/tr2_tgt.h                       |  131 +++\n trace2/tr2_tgt_event.c                 |  590 +++++++++++\n trace2/tr2_tgt_normal.c                |  325 ++++++\n trace2/tr2_tgt_perf.c                  |  536 ++++++++++\n trace2/tr2_tls.c                       |  164 +++\n trace2/tr2_tls.h                       |   97 ++\n trace2/tr2_verb.c                      |   30 +\n trace2/tr2_verb.h                      |   24 +\n transport-helper.c                     |    2 +\n transport.c                            |    1 +\n usage.c                                |   31 +\n wt-status.c                            |   24 +-\n 66 files changed, 6567 insertions(+), 21 deletions(-)\n create mode 100644 Documentation/technical/api-trace2.txt\n create mode 100644 compat/win32/ancestry.c\n create mode 100644 t/helper/test-trace2.c\n create mode 100755 t/t0210-trace2-normal.sh\n create mode 100644 t/t0210/scrub_normal.perl\n create mode 100755 t/t0211-trace2-perf.sh\n create mode 100644 t/t0211/scrub_perf.perl\n create mode 100755 t/t0212-trace2-event.sh\n create mode 100644 t/t0212/parse_events.perl\n create mode 100644 trace2.c\n create mode 100644 trace2.h\n create mode 100644 trace2/tr2_cfg.c\n create mode 100644 trace2/tr2_cfg.h\n create mode 100644 trace2/tr2_dst.c\n create mode 100644 trace2/tr2_dst.h\n create mode 100644 trace2/tr2_sid.c\n create mode 100644 trace2/tr2_sid.h\n create mode 100644 trace2/tr2_tbuf.c\n create mode 100644 trace2/tr2_tbuf.h\n create mode 100644 trace2/tr2_tgt.h\n create mode 100644 trace2/tr2_tgt_event.c\n create mode 100644 trace2/tr2_tgt_normal.c\n create mode 100644 trace2/tr2_tgt_perf.c\n create mode 100644 trace2/tr2_tls.c\n create mode 100644 trace2/tr2_tls.h\n create mode 100644 trace2/tr2_verb.c\n create mode 100644 trace2/tr2_verb.h\n\n\nbase-commit: b5101f929789889c2e536d915698f58d5c5c6b7a\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-108%2Fjeffhostetler%2Fcore-trace2-2019-v0-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-108/jeffhostetler/core-trace2-2019-v0-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/108\n\nRange-diff vs v2:\n\n  1:  1a90de9dab <  -:  ---------- trace2: Documentation/technical/api-trace2.txt\n  -:  ---------- >  1:  60b56d6a8f trace2: Documentation/technical/api-trace2.txt\n  2:  ea39b76d31 !  2:  bfe2fe0bbe trace2: create new combined trace facility\n     @@ -65,12 +65,10 @@\n      -\t\t\t       update_clone_start_failure,\n      -\t\t\t       update_clone_task_finished,\n      -\t\t\t       suc);\n     -+\trun_processes_parallel_tr2(suc->max_jobs,\n     -+\t\t\t\t   update_clone_get_next_task,\n     ++\trun_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task,\n      +\t\t\t\t   update_clone_start_failure,\n     -+\t\t\t\t   update_clone_task_finished,\n     -+\t\t\t\t   suc,\n     -+\t\t\t\t   \"submodule\", \"parallel/update\");\n     ++\t\t\t\t   update_clone_task_finished, suc, \"submodule\",\n     ++\t\t\t\t   \"parallel/update\");\n       \n       \t/*\n       \t * We saved the output and put it out all at once now.\n     @@ -427,7 +425,7 @@\n      +\t * we skip this for Windows because the compat layer already\n      +\t * has to emulate the execvp() call anyway.\n      +\t */\n     -+\tint exec_id = trace2_exec(file, (const char**)argv);\n     ++\tint exec_id = trace2_exec(file, (const char **)argv);\n      +#endif\n      +\n       \tif (!execvp(file, argv))\n     @@ -493,8 +491,8 @@\n      +\treturn run_command_v_opt_cd_env_tr2(argv, opt, dir, env, NULL);\n      +}\n      +\n     -+int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir, const char *const *env,\n     -+\t\t\t\t const char *tr2_class)\n     ++int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,\n     ++\t\t\t\t const char *const *env, const char *tr2_class)\n       {\n       \tstruct child_process cmd = CHILD_PROCESS_INIT;\n       \tcmd.argv = argv;\n     @@ -519,18 +517,15 @@\n       \treturn 0;\n       }\n      +\n     -+int run_processes_parallel_tr2(int n,\n     -+\t\t\t       get_next_task_fn get_next_task,\n     ++int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task,\n      +\t\t\t       start_failure_fn start_failure,\n     -+\t\t\t       task_finished_fn task_finished,\n     -+\t\t\t       void *pp_cb,\n     -+\t\t\t       const char *tr2_category,\n     -+\t\t\t       const char *tr2_label)\n     ++\t\t\t       task_finished_fn task_finished, void *pp_cb,\n     ++\t\t\t       const char *tr2_category, const char *tr2_label)\n      +{\n      +\tint result;\n      +\n     -+\ttrace2_region_enter_printf(tr2_category, tr2_label, NULL,\n     -+\t\t\t\t   \"max:%d\", ((n < 1) ? online_cpus() : n));\n     ++\ttrace2_region_enter_printf(tr2_category, tr2_label, NULL, \"max:%d\",\n     ++\t\t\t\t   ((n < 1) ? online_cpus() : n));\n      +\n      +\tresult = run_processes_parallel(n, get_next_task, start_failure,\n      +\t\t\t\t\ttask_finished, pp_cb);\n     @@ -539,7 +534,6 @@\n      +\n      +\treturn result;\n      +}\n     -+\n      \n       diff --git a/run-command.h b/run-command.h\n       --- a/run-command.h\n     @@ -568,8 +562,8 @@\n        * To unset an environment variable use just \"VAR\".\n        */\n       int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env);\n     -+int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir, const char *const *env,\n     -+\t\t\t\t const char *tr2_class);\n     ++int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,\n     ++\t\t\t\t const char *const *env, const char *tr2_class);\n       \n       /**\n        * Execute the given command, sending \"in\" to its stdin, and capturing its\n     @@ -577,13 +571,9 @@\n       \t\t\t   start_failure_fn,\n       \t\t\t   task_finished_fn,\n       \t\t\t   void *pp_cb);\n     -+int run_processes_parallel_tr2(int n,\n     -+\t\t\t       get_next_task_fn,\n     -+\t\t\t       start_failure_fn,\n     -+\t\t\t       task_finished_fn,\n     -+\t\t\t       void *pp_cb,\n     -+\t\t\t       const char *tr2_category,\n     -+\t\t\t       const char *tr2_label);\n     ++int run_processes_parallel_tr2(int n, get_next_task_fn, start_failure_fn,\n     ++\t\t\t       task_finished_fn, void *pp_cb,\n     ++\t\t\t       const char *tr2_category, const char *tr2_label);\n       \n       #endif\n      \n     @@ -612,9 +602,9 @@\n       --- a/submodule.c\n       +++ b/submodule.c\n      @@\n     - \t/* default value, \"--submodule-prefix\" and its value are added later */\n       \n     - \tcalculate_changed_submodule_paths(r);\n     + \tcalculate_changed_submodule_paths(r, &spf.changed_submodule_names);\n     + \tstring_list_sort(&spf.changed_submodule_names);\n      -\trun_processes_parallel(max_parallel_jobs,\n      -\t\t\t       get_next_submodule,\n      -\t\t\t       fetch_start_failure,\n     @@ -727,9 +717,8 @@\n      +\t     tgt_j;\t\t\t\t\t\\\n      +\t     j++, tgt_j = tr2_tgt_builtins[j])\n      +\n     -+#define for_each_wanted_builtin(j, tgt_j)\t\\\n     -+\tfor_each_builtin(j, tgt_j)\t\t\\\n     -+\tif (tr2_dst_trace_want(tgt_j->pdst))\n     ++#define for_each_wanted_builtin(j, tgt_j) \\\n     ++\tfor_each_builtin(j, tgt_j) if (tr2_dst_trace_want(tgt_j->pdst))\n      +\n      +/*\n      + * Force (rather than lazily) initialize any of the requested\n     @@ -745,7 +734,8 @@\n      +\tint j;\n      +\tint sum = 0;\n      +\n     -+\tfor_each_builtin(j, tgt_j) {\n     ++\tfor_each_builtin(j, tgt_j)\n     ++\t{\n      +\t\tif (tgt_j->pfn_init())\n      +\t\t\tsum++;\n      +\t}\n     @@ -763,7 +753,8 @@\n      +\tstruct tr2_tgt *tgt_j;\n      +\tint j;\n      +\n     -+\tfor_each_builtin(j, tgt_j) {\n     ++\tfor_each_builtin(j, tgt_j)\n     ++\t{\n      +\t\ttgt_j->pfn_term();\n      +\t}\n      +}\n     @@ -794,8 +785,9 @@\n      +\t * the trace output if someone calls die(), for example.\n      +\t */\n      +\ttr2tls_pop_unwind_self();\n     -+\t\n     -+\tfor_each_wanted_builtin(j, tgt_j) {\n     ++\n     ++\tfor_each_wanted_builtin(j, tgt_j)\n     ++\t{\n      +\t\tif (tgt_j->pfn_atexit)\n      +\t\t\ttgt_j->pfn_atexit(us_elapsed_absolute,\n      +\t\t\t\t\t  tr2main_exit_code);\n     @@ -821,7 +813,8 @@\n      +\tus_now = getnanotime() / 1000;\n      +\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j) {\n     ++\tfor_each_wanted_builtin(j, tgt_j)\n     ++\t{\n      +\t\tif (tgt_j->pfn_signal)\n      +\t\t\ttgt_j->pfn_signal(us_elapsed_absolute, signo);\n      +\t}\n     @@ -851,7 +844,8 @@\n      +\t/*\n      +\t * Emit 'version' message on each active builtin target.\n      +\t */\n     -+\tfor_each_wanted_builtin(j, tgt_j) {\n     ++\tfor_each_wanted_builtin(j, tgt_j)\n     ++\t{\n      +\t\tif (tgt_j->pfn_version_fl)\n      +\t\t\ttgt_j->pfn_version_fl(file, line);\n      +\t}\n     @@ -870,7 +864,8 @@\n      +\tif (!trace2_enabled)\n      +\t\treturn;\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j) {\n     ++\tfor_each_wanted_builtin(j, tgt_j)\n     ++\t{\n      +\t\tif (tgt_j->pfn_start_fl)\n      +\t\t\ttgt_j->pfn_start_fl(file, line, argv);\n      +\t}\n     @@ -893,16 +888,18 @@\n      +\tus_now = getnanotime() / 1000;\n      +\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j) {\n     ++\tfor_each_wanted_builtin(j, tgt_j)\n     ++\t{\n      +\t\tif (tgt_j->pfn_exit_fl)\n     -+\t\t\ttgt_j->pfn_exit_fl(file, line, us_elapsed_absolute, code);\n     ++\t\t\ttgt_j->pfn_exit_fl(file, line, us_elapsed_absolute,\n     ++\t\t\t\t\t   code);\n      +\t}\n      +\n      +\treturn code;\n      +}\n      +\n     -+void trace2_cmd_error_va_fl(const char *file, int line,\n     -+\t\t\t    const char *fmt, va_list ap)\n     ++void trace2_cmd_error_va_fl(const char *file, int line, const char *fmt,\n     ++\t\t\t    va_list ap)\n      +{\n      +\tstruct tr2_tgt *tgt_j;\n      +\tint j;\n     @@ -914,7 +911,8 @@\n      +\t * We expect each target function to treat 'ap' as constant\n      +\t * and use va_copy (because an 'ap' can only be walked once).\n      +\t */\n     -+\tfor_each_wanted_builtin(j, tgt_j) {\n     ++\tfor_each_wanted_builtin(j, tgt_j)\n     ++\t{\n      +\t\tif (tgt_j->pfn_error_va_fl)\n      +\t\t\ttgt_j->pfn_error_va_fl(file, line, fmt, ap);\n      +\t}\n     @@ -928,7 +926,8 @@\n      +\tif (!trace2_enabled)\n      +\t\treturn;\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j) {\n     ++\tfor_each_wanted_builtin(j, tgt_j)\n     ++\t{\n      +\t\tif (tgt_j->pfn_command_path_fl)\n      +\t\t\ttgt_j->pfn_command_path_fl(file, line, pathname);\n      +\t}\n     @@ -946,7 +945,8 @@\n      +\ttr2_verb_append_hierarchy(command_verb);\n      +\thierarchy = tr2_verb_get_hierarchy();\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j) {\n     ++\tfor_each_wanted_builtin(j, tgt_j)\n     ++\t{\n      +\t\tif (tgt_j->pfn_command_verb_fl)\n      +\t\t\ttgt_j->pfn_command_verb_fl(file, line, command_verb,\n      +\t\t\t\t\t\t   hierarchy);\n     @@ -962,15 +962,16 @@\n      +\tif (!trace2_enabled)\n      +\t\treturn;\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j) {\n     ++\tfor_each_wanted_builtin(j, tgt_j)\n     ++\t{\n      +\t\tif (tgt_j->pfn_command_subverb_fl)\n      +\t\t\ttgt_j->pfn_command_subverb_fl(file, line,\n      +\t\t\t\t\t\t      command_subverb);\n      +\t}\n      +}\n      +\n     -+void trace2_cmd_alias_fl(const char *file, int line,\n     -+\t\t\t const char *alias, const char **argv)\n     ++void trace2_cmd_alias_fl(const char *file, int line, const char *alias,\n     ++\t\t\t const char **argv)\n      +{\n      +\tstruct tr2_tgt *tgt_j;\n      +\tint j;\n     @@ -978,7 +979,8 @@\n      +\tif (!trace2_enabled)\n      +\t\treturn;\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j) {\n     ++\tfor_each_wanted_builtin(j, tgt_j)\n     ++\t{\n      +\t\tif (tgt_j->pfn_alias_fl)\n      +\t\t\ttgt_j->pfn_alias_fl(file, line, alias, argv);\n      +\t}\n     @@ -992,8 +994,8 @@\n      +\ttr2_cfg_list_config_fl(file, line);\n      +}\n      +\n     -+void trace2_cmd_set_config_fl(const char *file, int line,\n     -+\t\t\t      const char *key, const char *value)\n     ++void trace2_cmd_set_config_fl(const char *file, int line, const char *key,\n     ++\t\t\t      const char *value)\n      +{\n      +\tif (!trace2_enabled)\n      +\t\treturn;\n     @@ -1018,15 +1020,15 @@\n      +\tcmd->trace2_child_id = tr2tls_locked_increment(&tr2_next_child_id);\n      +\tcmd->trace2_child_us_start = us_now;\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j) {\n     ++\tfor_each_wanted_builtin(j, tgt_j)\n     ++\t{\n      +\t\tif (tgt_j->pfn_child_start_fl)\n      +\t\t\ttgt_j->pfn_child_start_fl(file, line,\n      +\t\t\t\t\t\t  us_elapsed_absolute, cmd);\n      +\t}\n      +}\n      +\n     -+void trace2_child_exit_fl(const char *file, int line,\n     -+\t\t\t  struct child_process *cmd,\n     ++void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd,\n      +\t\t\t  int child_exit_code)\n      +{\n      +\tstruct tr2_tgt *tgt_j;\n     @@ -1040,24 +1042,25 @@\n      +\n      +\tus_now = getnanotime() / 1000;\n      +\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n     -+\t\n     ++\n      +\tif (cmd->trace2_child_us_start)\n      +\t\tus_elapsed_child = us_now - cmd->trace2_child_us_start;\n      +\telse\n      +\t\tus_elapsed_child = 0;\n     -+\t\n     -+\tfor_each_wanted_builtin(j, tgt_j) {\n     ++\n     ++\tfor_each_wanted_builtin(j, tgt_j)\n     ++\t{\n      +\t\tif (tgt_j->pfn_child_exit_fl)\n     -+\t\t\ttgt_j->pfn_child_exit_fl(\n     -+\t\t\t\tfile, line, us_elapsed_absolute,\n     -+\t\t\t\tcmd->trace2_child_id,\n     -+\t\t\t\tcmd->pid,\n     -+\t\t\t\tchild_exit_code, us_elapsed_child);\n     ++\t\t\ttgt_j->pfn_child_exit_fl(file, line,\n     ++\t\t\t\t\t\t us_elapsed_absolute,\n     ++\t\t\t\t\t\t cmd->trace2_child_id, cmd->pid,\n     ++\t\t\t\t\t\t child_exit_code,\n     ++\t\t\t\t\t\t us_elapsed_child);\n      +\t}\n      +}\n      +\n     -+int trace2_exec_fl(const char *file, int line,\n     -+\t\t   const char *exe, const char **argv)\n     ++int trace2_exec_fl(const char *file, int line, const char *exe,\n     ++\t\t   const char **argv)\n      +{\n      +\tstruct tr2_tgt *tgt_j;\n      +\tint j;\n     @@ -1073,7 +1076,8 @@\n      +\n      +\texec_id = tr2tls_locked_increment(&tr2_next_exec_id);\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j) {\n     ++\tfor_each_wanted_builtin(j, tgt_j)\n     ++\t{\n      +\t\tif (tgt_j->pfn_exec_fl)\n      +\t\t\ttgt_j->pfn_exec_fl(file, line, us_elapsed_absolute,\n      +\t\t\t\t\t   exec_id, exe, argv);\n     @@ -1095,16 +1099,15 @@\n      +\tus_now = getnanotime() / 1000;\n      +\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j) {\n     ++\tfor_each_wanted_builtin(j, tgt_j)\n     ++\t{\n      +\t\tif (tgt_j->pfn_exec_result_fl)\n     -+\t\t\ttgt_j->pfn_exec_result_fl(file, line,\n     -+\t\t\t\t\t\t  us_elapsed_absolute,\n     -+\t\t\t\t\t\t  exec_id, code);\n     ++\t\t\ttgt_j->pfn_exec_result_fl(\n     ++\t\t\t\tfile, line, us_elapsed_absolute, exec_id, code);\n      +\t}\n      +}\n      +\n     -+void trace2_thread_start_fl(const char *file, int line,\n     -+\t\t\t    const char *thread_name)\n     ++void trace2_thread_start_fl(const char *file, int line, const char *thread_name)\n      +{\n      +\tstruct tr2_tgt *tgt_j;\n      +\tint j;\n     @@ -1114,8 +1117,7 @@\n      +\tif (!trace2_enabled)\n      +\t\treturn;\n      +\n     -+\tif (tr2tls_is_main_thread())\n     -+\t{\n     ++\tif (tr2tls_is_main_thread()) {\n      +\t\t/*\n      +\t\t * We should only be called from the new thread's thread-proc,\n      +\t\t * so this is technically a bug.  But in those cases where the\n     @@ -1123,7 +1125,7 @@\n      +\t\t * are built with threading disabled), we need to allow it.\n      +\t\t *\n      +\t\t * Convert this call to a region-enter so the nesting looks\n     -+\t\t * looks correct.  \n     ++\t\t * correct.\n      +\t\t */\n      +\t\ttrace2_region_enter_printf_fl(file, line, NULL, NULL, NULL,\n      +\t\t\t\t\t      \"thread-proc on main: %s\",\n     @@ -1136,7 +1138,8 @@\n      +\n      +\ttr2tls_create_self(thread_name);\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j) {\n     ++\tfor_each_wanted_builtin(j, tgt_j)\n     ++\t{\n      +\t\tif (tgt_j->pfn_thread_start_fl)\n      +\t\t\ttgt_j->pfn_thread_start_fl(file, line,\n      +\t\t\t\t\t\t   us_elapsed_absolute);\n     @@ -1154,16 +1157,16 @@\n      +\tif (!trace2_enabled)\n      +\t\treturn;\n      +\n     -+\tif (tr2tls_is_main_thread())\n     -+\t{\n     ++\tif (tr2tls_is_main_thread()) {\n      +\t\t/*\n     -+\t\t * We should only be called from the exiting thread's thread-proc,\n     -+\t\t * so this is technically a bug.  But in those cases where the\n     -+\t\t * main thread also runs the thread-proc function (or when we\n     -+\t\t * are built with threading disabled), we need to allow it.\n     ++\t\t * We should only be called from the exiting thread's\n     ++\t\t * thread-proc, so this is technically a bug.  But in\n     ++\t\t * those cases where the main thread also runs the\n     ++\t\t * thread-proc function (or when we are built with\n     ++\t\t * threading disabled), we need to allow it.\n      +\t\t *\n     -+\t\t * Convert this call to a region-leave so the nesting looks\n     -+\t\t * looks correct.  \n     ++\t\t * Convert this call to a region-leave so the nesting\n     ++\t\t * looks correct.\n      +\t\t */\n      +\t\ttrace2_region_leave_printf_fl(file, line, NULL, NULL, NULL,\n      +\t\t\t\t\t      \"thread-proc on main\");\n     @@ -1181,7 +1184,8 @@\n      +\ttr2tls_pop_unwind_self();\n      +\tus_elapsed_thread = tr2tls_region_elasped_self(us_now);\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j) {\n     ++\tfor_each_wanted_builtin(j, tgt_j)\n     ++\t{\n      +\t\tif (tgt_j->pfn_thread_exit_fl)\n      +\t\t\ttgt_j->pfn_thread_exit_fl(file, line,\n      +\t\t\t\t\t\t  us_elapsed_absolute,\n     @@ -1191,8 +1195,8 @@\n      +\ttr2tls_unset_self();\n      +}\n      +\n     -+void trace2_def_param_fl(const char *file, int line,\n     -+\t\t\t const char *param, const char *value)\n     ++void trace2_def_param_fl(const char *file, int line, const char *param,\n     ++\t\t\t const char *value)\n      +{\n      +\tstruct tr2_tgt *tgt_j;\n      +\tint j;\n     @@ -1200,14 +1204,14 @@\n      +\tif (!trace2_enabled)\n      +\t\treturn;\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j) {\n     ++\tfor_each_wanted_builtin(j, tgt_j)\n     ++\t{\n      +\t\tif (tgt_j->pfn_param_fl)\n      +\t\t\ttgt_j->pfn_param_fl(file, line, param, value);\n      +\t}\n      +}\n      +\n     -+void trace2_def_repo_fl(const char *file, int line,\n     -+\t\t\tstruct repository *repo)\n     ++void trace2_def_repo_fl(const char *file, int line, struct repository *repo)\n      +{\n      +\tstruct tr2_tgt *tgt_j;\n      +\tint j;\n     @@ -1220,15 +1224,15 @@\n      +\n      +\trepo->trace2_repo_id = tr2tls_locked_increment(&tr2_next_repo_id);\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j) {\n     ++\tfor_each_wanted_builtin(j, tgt_j)\n     ++\t{\n      +\t\tif (tgt_j->pfn_repo_fl)\n      +\t\t\ttgt_j->pfn_repo_fl(file, line, repo);\n      +\t}\n      +}\n      +\n      +void trace2_region_enter_printf_va_fl(const char *file, int line,\n     -+\t\t\t\t      const char *category,\n     -+\t\t\t\t      const char *label,\n     ++\t\t\t\t      const char *category, const char *label,\n      +\t\t\t\t      const struct repository *repo,\n      +\t\t\t\t      const char *fmt, va_list ap)\n      +{\n     @@ -1250,59 +1254,53 @@\n      +\t * We expect each target function to treat 'ap' as constant\n      +\t * and use va_copy.\n      +\t */\n     -+\tfor_each_wanted_builtin(j, tgt_j) {\n     ++\tfor_each_wanted_builtin(j, tgt_j)\n     ++\t{\n      +\t\tif (tgt_j->pfn_region_enter_printf_va_fl)\n      +\t\t\ttgt_j->pfn_region_enter_printf_va_fl(\n     -+\t\t\t\tfile, line, us_elapsed_absolute,\n     -+\t\t\t\tcategory, label, repo, fmt, ap);\n     ++\t\t\t\tfile, line, us_elapsed_absolute, category,\n     ++\t\t\t\tlabel, repo, fmt, ap);\n      +\t}\n      +\n      +\ttr2tls_push_self(us_now);\n      +}\n      +\n     -+void trace2_region_enter_fl(const char *file, int line,\n     -+\t\t\t    const char *category,\n     -+\t\t\t    const char *label,\n     -+\t\t\t    const struct repository *repo)\n     ++void trace2_region_enter_fl(const char *file, int line, const char *category,\n     ++\t\t\t    const char *label, const struct repository *repo)\n      +{\n     -+\ttrace2_region_enter_printf_va_fl(file, line,\n     -+\t\t\t\t\t category, label, repo,\n     ++\ttrace2_region_enter_printf_va_fl(file, line, category, label, repo,\n      +\t\t\t\t\t NULL, NULL);\n      +}\n      +\n      +void trace2_region_enter_printf_fl(const char *file, int line,\n     -+\t\t\t\t   const char *category,\n     -+\t\t\t\t   const char *label,\n     ++\t\t\t\t   const char *category, const char *label,\n      +\t\t\t\t   const struct repository *repo,\n      +\t\t\t\t   const char *fmt, ...)\n      +{\n      +\tva_list ap;\n      +\n      +\tva_start(ap, fmt);\n     -+\ttrace2_region_enter_printf_va_fl(file, line,\n     -+\t\t\t\t\t category, label, repo,\n     -+\t\t\t\t\t fmt, ap);\n     ++\ttrace2_region_enter_printf_va_fl(file, line, category, label, repo, fmt,\n     ++\t\t\t\t\t ap);\n      +\tva_end(ap);\n      +}\n      +\n      +#ifndef HAVE_VARIADIC_MACROS\n      +void trace2_region_enter_printf(const char *category, const char *label,\n     -+\t\t\t\tconst struct repository *repo,\n     -+\t\t\t\tconst char *fmt, ...)\n     ++\t\t\t\tconst struct repository *repo, const char *fmt,\n     ++\t\t\t\t...)\n      +{\n      +\tva_list ap;\n      +\n      +\tva_start(ap, fmt);\n     -+\ttrace2_region_enter_printf_va_fl(NULL, 0,\n     -+\t\t\t\t\t category, label, repo,\n     -+\t\t\t\t\t fmt, ap);\n     ++\ttrace2_region_enter_printf_va_fl(NULL, 0, category, label, repo, fmt,\n     ++\t\t\t\t\t ap);\n      +\tva_end(ap);\n      +}\n      +#endif\n      +\n      +void trace2_region_leave_printf_va_fl(const char *file, int line,\n     -+\t\t\t\t      const char *category,\n     -+\t\t\t\t      const char *label,\n     ++\t\t\t\t      const char *category, const char *label,\n      +\t\t\t\t      const struct repository *repo,\n      +\t\t\t\t      const char *fmt, va_list ap)\n      +{\n     @@ -1325,67 +1323,59 @@\n      +\t * it lines up with the corresponding push/enter.\n      +\t */\n      +\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n     -+\t\n     ++\n      +\ttr2tls_pop_self();\n      +\n      +\t/*\n      +\t * We expect each target function to treat 'ap' as constant\n      +\t * and use va_copy.\n      +\t */\n     -+\tfor_each_wanted_builtin(j, tgt_j) {\n     ++\tfor_each_wanted_builtin(j, tgt_j)\n     ++\t{\n      +\t\tif (tgt_j->pfn_region_leave_printf_va_fl)\n      +\t\t\ttgt_j->pfn_region_leave_printf_va_fl(\n      +\t\t\t\tfile, line, us_elapsed_absolute,\n     -+\t\t\t\tus_elapsed_region,\n     -+\t\t\t\tcategory, label, repo,\n     -+\t\t\t\tfmt, ap);\n     ++\t\t\t\tus_elapsed_region, category, label, repo, fmt,\n     ++\t\t\t\tap);\n      +\t}\n      +}\n      +\n     -+void trace2_region_leave_fl(const char *file, int line,\n     -+\t\t\t    const char *category,\n     -+\t\t\t    const char *label,\n     -+\t\t\t    const struct repository *repo)\n     ++void trace2_region_leave_fl(const char *file, int line, const char *category,\n     ++\t\t\t    const char *label, const struct repository *repo)\n      +{\n     -+\ttrace2_region_leave_printf_va_fl(file, line,\n     -+\t\t\t\t\t category, label, repo,\n     ++\ttrace2_region_leave_printf_va_fl(file, line, category, label, repo,\n      +\t\t\t\t\t NULL, NULL);\n      +}\n      +\n      +void trace2_region_leave_printf_fl(const char *file, int line,\n     -+\t\t\t\t   const char *category,\n     -+\t\t\t\t   const char *label,\n     ++\t\t\t\t   const char *category, const char *label,\n      +\t\t\t\t   const struct repository *repo,\n      +\t\t\t\t   const char *fmt, ...)\n      +{\n      +\tva_list ap;\n      +\n      +\tva_start(ap, fmt);\n     -+\ttrace2_region_leave_printf_va_fl(file, line,\n     -+\t\t\t\t\t category, label, repo,\n     -+\t\t\t\t\t fmt, ap);\n     ++\ttrace2_region_leave_printf_va_fl(file, line, category, label, repo, fmt,\n     ++\t\t\t\t\t ap);\n      +\tva_end(ap);\n      +}\n      +\n      +#ifndef HAVE_VARIADIC_MACROS\n      +void trace2_region_leave_printf(const char *category, const char *label,\n     -+\t\t\t\tconst struct repository *repo,\n     -+\t\t\t\tconst char *fmt, ...)\n     ++\t\t\t\tconst struct repository *repo, const char *fmt,\n     ++\t\t\t\t...)\n      +{\n      +\tva_list ap;\n      +\n      +\tva_start(ap, fmt);\n     -+\ttrace2_region_leave_printf_va_fl(NULL, 0,\n     -+\t\t\t\t\t category, label, repo,\n     -+\t\t\t\t\t fmt, ap);\n     ++\ttrace2_region_leave_printf_va_fl(NULL, 0, category, label, repo, fmt,\n     ++\t\t\t\t\t ap);\n      +\tva_end(ap);\n      +}\n      +#endif\n      +\n     -+void trace2_data_string_fl(const char *file, int line,\n     -+\t\t\t   const char *category,\n     -+\t\t\t   const struct repository *repo,\n     -+\t\t\t   const char *key,\n     ++void trace2_data_string_fl(const char *file, int line, const char *category,\n     ++\t\t\t   const struct repository *repo, const char *key,\n      +\t\t\t   const char *value)\n      +{\n      +\tstruct tr2_tgt *tgt_j;\n     @@ -1401,18 +1391,17 @@\n      +\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n      +\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j) {\n     ++\tfor_each_wanted_builtin(j, tgt_j)\n     ++\t{\n      +\t\tif (tgt_j->pfn_data_fl)\n      +\t\t\ttgt_j->pfn_data_fl(file, line, us_elapsed_absolute,\n     -+\t\t\t\t\t   us_elapsed_region,\n     -+\t\t\t\t\t   category, repo, key, value);\n     ++\t\t\t\t\t   us_elapsed_region, category, repo,\n     ++\t\t\t\t\t   key, value);\n      +\t}\n      +}\n      +\n     -+void trace2_data_intmax_fl(const char *file, int line,\n     -+\t\t\t   const char *category,\n     -+\t\t\t   const struct repository *repo,\n     -+\t\t\t   const char *key,\n     ++void trace2_data_intmax_fl(const char *file, int line, const char *category,\n     ++\t\t\t   const struct repository *repo, const char *key,\n      +\t\t\t   intmax_t value)\n      +{\n      +\tstruct strbuf buf_string = STRBUF_INIT;\n     @@ -1420,15 +1409,13 @@\n      +\tif (!trace2_enabled)\n      +\t\treturn;\n      +\n     -+\tstrbuf_addf(&buf_string, \"%\"PRIdMAX, value);\n     ++\tstrbuf_addf(&buf_string, \"%\" PRIdMAX, value);\n      +\ttrace2_data_string_fl(file, line, category, repo, key, buf_string.buf);\n      +\tstrbuf_release(&buf_string);\n      +}\n      +\n     -+void trace2_data_json_fl(const char *file, int line,\n     -+\t\t\t const char *category,\n     -+\t\t\t const struct repository *repo,\n     -+\t\t\t const char *key,\n     ++void trace2_data_json_fl(const char *file, int line, const char *category,\n     ++\t\t\t const struct repository *repo, const char *key,\n      +\t\t\t const struct json_writer *value)\n      +{\n      +\tstruct tr2_tgt *tgt_j;\n     @@ -1444,16 +1431,17 @@\n      +\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n      +\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j) {\n     ++\tfor_each_wanted_builtin(j, tgt_j)\n     ++\t{\n      +\t\tif (tgt_j->pfn_data_fl)\n      +\t\t\ttgt_j->pfn_data_json_fl(file, line, us_elapsed_absolute,\n     -+\t\t\t\t\t\tus_elapsed_region,\n     -+\t\t\t\t\t\tcategory, repo, key, value);\n     ++\t\t\t\t\t\tus_elapsed_region, category,\n     ++\t\t\t\t\t\trepo, key, value);\n      +\t}\n      +}\n      +\n     -+void trace2_printf_va_fl(const char *file, int line,\n     -+\t\t\t const char *fmt, va_list ap)\n     ++void trace2_printf_va_fl(const char *file, int line, const char *fmt,\n     ++\t\t\t va_list ap)\n      +{\n      +\tstruct tr2_tgt *tgt_j;\n      +\tint j;\n     @@ -1470,15 +1458,15 @@\n      +\t * We expect each target function to treat 'ap' as constant\n      +\t * and use va_copy.\n      +\t */\n     -+\tfor_each_wanted_builtin(j, tgt_j) {\n     ++\tfor_each_wanted_builtin(j, tgt_j)\n     ++\t{\n      +\t\tif (tgt_j->pfn_printf_va_fl)\n      +\t\t\ttgt_j->pfn_printf_va_fl(file, line, us_elapsed_absolute,\n      +\t\t\t\t\t\tfmt, ap);\n      +\t}\n      +}\n      +\n     -+void trace2_printf_fl(const char *file, int line,\n     -+\t\t      const char *fmt, ...)\n     ++void trace2_printf_fl(const char *file, int line, const char *fmt, ...)\n      +{\n      +\tva_list ap;\n      +\n     @@ -1565,8 +1553,8 @@\n      + *\n      + * Write an error message to the TRACE2 targets.\n      + */\n     -+void trace2_cmd_error_va_fl(const char *file, int line,\n     -+\t\t\t    const char *fmt, va_list ap);\n     ++void trace2_cmd_error_va_fl(const char *file, int line, const char *fmt,\n     ++\t\t\t    va_list ap);\n      +\n      +#define trace2_cmd_error_va(fmt, ap) \\\n      +\ttrace2_cmd_error_va_fl(__FILE__, __LINE__, (fmt), (ap))\n     @@ -1579,8 +1567,7 @@\n      + */\n      +void trace2_cmd_path_fl(const char *file, int line, const char *pathname);\n      +\n     -+#define trace2_cmd_path(p) \\\n     -+\ttrace2_cmd_path_fl(__FILE__, __LINE__, (p))\n     ++#define trace2_cmd_path(p) trace2_cmd_path_fl(__FILE__, __LINE__, (p))\n      +\n      +/*\n      + * Emit a 'cmd_verb' event with the canonical name of the (usually)\n     @@ -1589,8 +1576,7 @@\n      + */\n      +void trace2_cmd_verb_fl(const char *file, int line, const char *command_verb);\n      +\n     -+#define trace2_cmd_verb(v) \\\n     -+\ttrace2_cmd_verb_fl(__FILE__, __LINE__, (v))\n     ++#define trace2_cmd_verb(v) trace2_cmd_verb_fl(__FILE__, __LINE__, (v))\n      +\n      +/*\n      + * Emit a 'cmd_subverb' event to further describe the command being run.\n     @@ -1601,14 +1587,13 @@\n      +void trace2_cmd_subverb_fl(const char *file, int line,\n      +\t\t\t   const char *command_subverb);\n      +\n     -+#define trace2_cmd_subverb(sv) \\\n     -+\ttrace2_cmd_subverb_fl(__FILE__, __LINE__, (sv))\n     ++#define trace2_cmd_subverb(sv) trace2_cmd_subverb_fl(__FILE__, __LINE__, (sv))\n      +\n      +/*\n      + * Emit an 'alias' expansion event.\n      + */\n     -+void trace2_cmd_alias_fl(const char *file, int line,\n     -+\t\t\t const char *alias, const char **argv);\n     ++void trace2_cmd_alias_fl(const char *file, int line, const char *alias,\n     ++\t\t\t const char **argv);\n      +\n      +#define trace2_cmd_alias(alias, argv) \\\n      +\ttrace2_cmd_alias_fl(__FILE__, __LINE__, (alias), (argv))\n     @@ -1630,8 +1615,7 @@\n      + */\n      +void trace2_cmd_list_config_fl(const char *file, int line);\n      +\n     -+#define trace2_cmd_list_config() \\\n     -+\ttrace2_cmd_list_config_fl(__FILE__, __LINE__)\n     ++#define trace2_cmd_list_config() trace2_cmd_list_config_fl(__FILE__, __LINE__)\n      +\n      +/*\n      + * Emit a \"def_param\" event for the given config key/value pair IF\n     @@ -1640,8 +1624,8 @@\n      + * Use this for new/updated config settings created/updated after\n      + * trace2_cmd_list_config() is called.\n      + */\n     -+void trace2_cmd_set_config_fl(const char *file, int line,\n     -+\t\t\t      const char *key, const char *value);\n     ++void trace2_cmd_set_config_fl(const char *file, int line, const char *key,\n     ++\t\t\t      const char *value);\n      +\n      +#define trace2_cmd_set_config(k, v) \\\n      +\ttrace2_cmd_set_config_fl(__FILE__, __LINE__, (k), (v))\n     @@ -1656,14 +1640,12 @@\n      +void trace2_child_start_fl(const char *file, int line,\n      +\t\t\t   struct child_process *cmd);\n      +\n     -+#define trace2_child_start(cmd) \\\n     -+\ttrace2_child_start_fl(__FILE__, __LINE__, (cmd))\n     ++#define trace2_child_start(cmd) trace2_child_start_fl(__FILE__, __LINE__, (cmd))\n      +\n      +/*\n     -+ * Emit a 'child_exit' event after the child process completes. \n     ++ * Emit a 'child_exit' event after the child process completes.\n      + */\n     -+void trace2_child_exit_fl(const char *file, int line,\n     -+\t\t\t  struct child_process *cmd,\n     ++void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd,\n      +\t\t\t  int child_exit_code);\n      +\n      +#define trace2_child_exit(cmd, code) \\\n     @@ -1678,11 +1660,10 @@\n      + *\n      + * Returns the \"exec_id\".\n      + */\n     -+int trace2_exec_fl(const char *file, int line,\n     -+\t\t   const char *exe, const char **argv);\n     ++int trace2_exec_fl(const char *file, int line, const char *exe,\n     ++\t\t   const char **argv);\n      +\n     -+#define trace2_exec(exe, argv) \\\n     -+\ttrace2_exec_fl(__FILE__, __LINE__, (exe), (argv))\n     ++#define trace2_exec(exe, argv) trace2_exec_fl(__FILE__, __LINE__, (exe), (argv))\n      +\n      +/*\n      + * Emit an 'exec_result' when possible.  On Unix-derived systems,\n     @@ -1705,7 +1686,7 @@\n      + * Thread names will be decorated with an instance number automatically.\n      + */\n      +void trace2_thread_start_fl(const char *file, int line,\n     -+\t\t\t\t const char *thread_name);\n     ++\t\t\t    const char *thread_name);\n      +\n      +#define trace2_thread_start(thread_name) \\\n      +\ttrace2_thread_start_fl(__FILE__, __LINE__, (thread_name))\n     @@ -1725,9 +1706,9 @@\n      + * Write a \"<param> = <value>\" pair describing some aspect of the\n      + * run such as an important configuration setting or command line\n      + * option that significantly changes command behavior.\n     -+ */ \n     -+void trace2_def_param_fl(const char *file, int line,\n     -+\t\t\t const char *param, const char *value);\n     ++ */\n     ++void trace2_def_param_fl(const char *file, int line, const char *param,\n     ++\t\t\t const char *value);\n      +\n      +#define trace2_def_param(param, value) \\\n      +\ttrace2_def_param_fl(__FILE__, __LINE__, (param), (value))\n     @@ -1738,11 +1719,9 @@\n      + *\n      + * Emits a 'worktree' event for this repo instance.\n      + */\n     -+void trace2_def_repo_fl(const char *file, int line,\n     -+\t\t\tstruct repository *repo);\n     ++void trace2_def_repo_fl(const char *file, int line, struct repository *repo);\n      +\n     -+#define trace2_def_repo(repo) \\\n     -+\ttrace2_def_repo_fl(__FILE__, __LINE__, repo)\n     ++#define trace2_def_repo(repo) trace2_def_repo_fl(__FILE__, __LINE__, repo)\n      +\n      +/*\n      + * Emit a 'region_enter' event for <category>.<label> with optional\n     @@ -1752,24 +1731,20 @@\n      + * current time.  This controls the indenting of all subsequent events\n      + * on this thread.\n      + */\n     -+void trace2_region_enter_fl(const char *file, int line,\n     -+\t\t\t    const char *category,\n     -+\t\t\t    const char *label,\n     -+\t\t\t    const struct repository *repo);\n     ++void trace2_region_enter_fl(const char *file, int line, const char *category,\n     ++\t\t\t    const char *label, const struct repository *repo);\n      +\n      +#define trace2_region_enter(category, label, repo) \\\n     -+\ttrace2_region_enter_fl( \\\n     -+\t\t__FILE__, __LINE__, (category), (label), (repo))\n     ++\ttrace2_region_enter_fl(__FILE__, __LINE__, (category), (label), (repo))\n      +\n      +void trace2_region_enter_printf_va_fl(const char *file, int line,\n     -+\t\t\t\t      const char *category,\n     -+\t\t\t\t      const char *label,\n     ++\t\t\t\t      const char *category, const char *label,\n      +\t\t\t\t      const struct repository *repo,\n      +\t\t\t\t      const char *fmt, va_list ap);\n      +\n     -+#define trace2_region_enter_printf_va(category, label, repo, fmt, ap) \\\n     -+\ttrace2_region_enter_printf_va_fl( \\\n     -+\t\t__FILE__, __LINE__, (category), (label), (repo), (fmt), (ap))\n     ++#define trace2_region_enter_printf_va(category, label, repo, fmt, ap)    \\\n     ++\ttrace2_region_enter_printf_va_fl(__FILE__, __LINE__, (category), \\\n     ++\t\t\t\t\t (label), (repo), (fmt), (ap))\n      +\n      +void trace2_region_enter_printf_fl(const char *file, int line,\n      +\t\t\t\t   const char *category, const char *label,\n     @@ -1777,14 +1752,14 @@\n      +\t\t\t\t   const char *fmt, ...);\n      +\n      +#ifdef HAVE_VARIADIC_MACROS\n     -+#define trace2_region_enter_printf(category, label, repo, ...) \\\n     -+\ttrace2_region_enter_printf_fl( \\\n     -+\t\t__FILE__, __LINE__, (category), (label), (repo), __VA_ARGS__)\n     ++#define trace2_region_enter_printf(category, label, repo, ...)                 \\\n     ++\ttrace2_region_enter_printf_fl(__FILE__, __LINE__, (category), (label), \\\n     ++\t\t\t\t      (repo), __VA_ARGS__)\n      +#else\n      +__attribute__((format (region_enter_printf, 4, 5)))\n      +void trace2_region_enter_printf(const char *category, const char *label,\n     -+\t\t\t\tconst struct repository *repo,\n     -+\t\t\t\tconst char *fmt, ...);\n     ++\t\t\t\tconst struct repository *repo, const char *fmt,\n     ++\t\t\t\t...);\n      +#endif\n      +\n      +/*\n     @@ -1794,24 +1769,20 @@\n      + * Leave current nesting level and report the elapsed time spent\n      + * in this nesting level.\n      + */\n     -+void trace2_region_leave_fl(const char *file, int line,\n     -+\t\t\t    const char *category,\n     -+\t\t\t    const char *label,\n     -+\t\t\t    const struct repository *repo);\n     ++void trace2_region_leave_fl(const char *file, int line, const char *category,\n     ++\t\t\t    const char *label, const struct repository *repo);\n      +\n      +#define trace2_region_leave(category, label, repo) \\\n     -+\ttrace2_region_leave_fl( \\\n     -+\t\t__FILE__, __LINE__, (category), (label), (repo))\n     ++\ttrace2_region_leave_fl(__FILE__, __LINE__, (category), (label), (repo))\n      +\n      +void trace2_region_leave_printf_va_fl(const char *file, int line,\n     -+\t\t\t\t      const char *category,\n     -+\t\t\t\t      const char *label,\n     ++\t\t\t\t      const char *category, const char *label,\n      +\t\t\t\t      const struct repository *repo,\n      +\t\t\t\t      const char *fmt, va_list ap);\n      +\n     -+#define trace2_region_leave_printf_va(category, label, repo, fmt, ap) \\\n     -+\ttrace2_region_leave_printf_va_fl( \\\n     -+\t\t__FILE__, __LINE__, (category), (label), (repo), (fmt), (ap))\n     ++#define trace2_region_leave_printf_va(category, label, repo, fmt, ap)    \\\n     ++\ttrace2_region_leave_printf_va_fl(__FILE__, __LINE__, (category), \\\n     ++\t\t\t\t\t (label), (repo), (fmt), (ap))\n      +\n      +void trace2_region_leave_printf_fl(const char *file, int line,\n      +\t\t\t\t   const char *category, const char *label,\n     @@ -1819,14 +1790,14 @@\n      +\t\t\t\t   const char *fmt, ...);\n      +\n      +#ifdef HAVE_VARIADIC_MACROS\n     -+#define trace2_region_leave_printf(category, label, repo, ...) \\\n     -+\ttrace2_region_leave_printf_fl( \\\n     -+\t\t__FILE__, __LINE__, (category), (label), (repo), __VA_ARGS__)\n     ++#define trace2_region_leave_printf(category, label, repo, ...)                 \\\n     ++\ttrace2_region_leave_printf_fl(__FILE__, __LINE__, (category), (label), \\\n     ++\t\t\t\t      (repo), __VA_ARGS__)\n      +#else\n      +__attribute__((format (region_leave_printf, 4, 5)))\n      +void trace2_region_leave_printf(const char *category, const char *label,\n     -+\t\t\t\tconst struct repository *repo,\n     -+\t\t\t\tconst char *fmt, ...);\n     ++\t\t\t\tconst struct repository *repo, const char *fmt,\n     ++\t\t\t\t...);\n      +#endif\n      +\n      +/*\n     @@ -1838,35 +1809,29 @@\n      + * for post-processing.  On printf-based TRACE2 targets, this is converted\n      + * into a fixed-format printf message.\n      + */\n     -+void trace2_data_string_fl(const char *file, int line,\n     -+\t\t\t   const char *category,\n     -+\t\t\t   const struct repository *repo,\n     -+\t\t\t   const char *key,\n     ++void trace2_data_string_fl(const char *file, int line, const char *category,\n     ++\t\t\t   const struct repository *repo, const char *key,\n      +\t\t\t   const char *value);\n      +\n     -+#define trace2_data_string(category, repo, key, value) \\\n     -+\ttrace2_data_string_fl( \\\n     -+\t\t__FILE__, __LINE__, (category), (repo), (key), (value))\n     ++#define trace2_data_string(category, repo, key, value)                       \\\n     ++\ttrace2_data_string_fl(__FILE__, __LINE__, (category), (repo), (key), \\\n     ++\t\t\t      (value))\n      +\n     -+void trace2_data_intmax_fl(const char *file, int line,\n     -+\t\t\t   const char *category,\n     -+\t\t\t   const struct repository *repo,\n     -+\t\t\t   const char *key,\n     ++void trace2_data_intmax_fl(const char *file, int line, const char *category,\n     ++\t\t\t   const struct repository *repo, const char *key,\n      +\t\t\t   intmax_t value);\n      +\n     -+#define trace2_data_intmax(category, repo, key, value) \\\n     -+\ttrace2_data_intmax_fl( \\\n     -+\t\t__FILE__, __LINE__, (category), (repo), (key), (value))\n     ++#define trace2_data_intmax(category, repo, key, value)                       \\\n     ++\ttrace2_data_intmax_fl(__FILE__, __LINE__, (category), (repo), (key), \\\n     ++\t\t\t      (value))\n      +\n     -+void trace2_data_json_fl(const char *file, int line,\n     -+\t\t\t const char *category,\n     -+\t\t\t const struct repository *repo,\n     -+\t\t\t const char *key,\n     ++void trace2_data_json_fl(const char *file, int line, const char *category,\n     ++\t\t\t const struct repository *repo, const char *key,\n      +\t\t\t const struct json_writer *jw);\n      +\n     -+#define trace2_data_json(category, repo, key, value) \\\n     -+\ttrace2_data_json_fl( \\\n     -+\t\t__FILE__, __LINE__, (category), (repo), (key), (value))\n     ++#define trace2_data_json(category, repo, key, value)                       \\\n     ++\ttrace2_data_json_fl(__FILE__, __LINE__, (category), (repo), (key), \\\n     ++\t\t\t    (value))\n      +\n      +/*\n      + * Emit a 'printf' event.\n     @@ -1876,8 +1841,8 @@\n      + * any formatting guidelines.  Post-processors may choose to ignore\n      + * them.\n      + */\n     -+void trace2_printf_va_fl(const char *file, int line,\n     -+\t\t\t const char *fmt, va_list ap);\n     ++void trace2_printf_va_fl(const char *file, int line, const char *fmt,\n     ++\t\t\t va_list ap);\n      +\n      +#define trace2_printf_va(fmt, ap) \\\n      +\ttrace2_printf_va_fl(__FILE__, __LINE__, (fmt), (ap))\n     @@ -1885,8 +1850,7 @@\n      +void trace2_printf_fl(const char *file, int line, const char *fmt, ...);\n      +\n      +#ifdef HAVE_VARIADIC_MACROS\n     -+#define trace2_printf(...) \\\n     -+\ttrace2_printf_fl(__FILE__, __LINE__, __VA_ARGS__)\n     ++#define trace2_printf(...) trace2_printf_fl(__FILE__, __LINE__, __VA_ARGS__)\n      +#else\n      +__attribute__((format (printf, 1, 2)))\n      +void trace2_printf(const char *fmt, ...);\n     @@ -1948,8 +1912,7 @@\n      +\ttr2_cfg_loaded = 0;\n      +}\n      +\n     -+struct tr2_cfg_data\n     -+{\n     ++struct tr2_cfg_data {\n      +\tconst char *file;\n      +\tint line;\n      +};\n     @@ -1982,15 +1945,14 @@\n      +\t\tread_early_config(tr2_cfg_cb, &data);\n      +}\n      +\n     -+void tr2_cfg_set_fl(const char *file, int line,\n     -+\t\t    const char *key, const char *value)\n     ++void tr2_cfg_set_fl(const char *file, int line, const char *key,\n     ++\t\t    const char *value)\n      +{\n      +\tstruct tr2_cfg_data data = { file, line };\n      +\n      +\tif (tr2_cfg_load_patterns() > 0)\n      +\t\ttr2_cfg_cb(key, value, &data);\n      +}\n     -+\n      \n       diff --git a/trace2/tr2_cfg.h b/trace2/tr2_cfg.h\n       new file mode 100644\n     @@ -2010,8 +1972,8 @@\n      + * Emit a \"def_param\" event for the given key/value pair IF we consider\n      + * the key to be \"interesting\".\n      + */\n     -+void tr2_cfg_set_fl(const char *file, int line,\n     -+\t\t    const char *key, const char *value);\n     ++void tr2_cfg_set_fl(const char *file, int line, const char *key,\n     ++\t\t    const char *value);\n      +\n      +void tr2_cfg_free_patterns(void);\n      +\n     @@ -2025,6 +1987,32 @@\n      +#include \"cache.h\"\n      +#include \"trace2/tr2_dst.h\"\n      +\n     ++/*\n     ++ * If a Trace2 target cannot be opened for writing, we should issue a\n     ++ * warning to stderr, but this is very annoying if the target is a pipe\n     ++ * or socket and beyond the user's control -- especially since every\n     ++ * git command (and sub-command) will print the message.  So we silently\n     ++ * eat these warnings and just discard the trace data.\n     ++ *\n     ++ * Enable the following environment variable to see these warnings.\n     ++ */\n     ++#define TR2_ENVVAR_DST_DEBUG \"GIT_TR2_DST_DEBUG\"\n     ++\n     ++static int tr2_dst_want_warning(void)\n     ++{\n     ++\tstatic int tr2env_dst_debug = -1;\n     ++\n     ++\tif (tr2env_dst_debug == -1) {\n     ++\t\tconst char *env_value = getenv(TR2_ENVVAR_DST_DEBUG);\n     ++\t\tif (!env_value || !*env_value)\n     ++\t\t\ttr2env_dst_debug = 0;\n     ++\t\telse\n     ++\t\t\ttr2env_dst_debug = atoi(env_value) > 0;\n     ++\t}\n     ++\n     ++\treturn tr2env_dst_debug;\n     ++}\n     ++\n      +void tr2_dst_trace_disable(struct tr2_dst *dst)\n      +{\n      +\tif (dst->need_close)\n     @@ -2034,50 +2022,129 @@\n      +\tdst->need_close = 0;\n      +}\n      +\n     ++static int tr2_dst_try_path(struct tr2_dst *dst, const char *tgt_value)\n     ++{\n     ++\tint fd = open(tgt_value, O_WRONLY | O_APPEND | O_CREAT, 0666);\n     ++\tif (fd == -1) {\n     ++\t\tif (tr2_dst_want_warning())\n     ++\t\t\twarning(\"trace2: could not open '%s' for '%s' tracing: %s\",\n     ++\t\t\t\ttgt_value, dst->env_var_name, strerror(errno));\n     ++\n     ++\t\ttr2_dst_trace_disable(dst);\n     ++\t\treturn 0;\n     ++\t}\n     ++\n     ++\tdst->fd = fd;\n     ++\tdst->need_close = 1;\n     ++\tdst->initialized = 1;\n     ++\n     ++\treturn dst->fd;\n     ++}\n     ++\n     ++#ifndef NO_UNIX_SOCKETS\n     ++#define PREFIX_AF_UNIX \"af_unix:\"\n     ++#define PREFIX_AF_UNIX_LEN (8)\n     ++\n     ++static int tr2_dst_try_unix_domain_socket(struct tr2_dst *dst,\n     ++\t\t\t\t\t  const char *tgt_value)\n     ++{\n     ++\tint fd;\n     ++\tstruct sockaddr_un sa;\n     ++\tconst char *path = tgt_value + PREFIX_AF_UNIX_LEN;\n     ++\tint path_len = strlen(path);\n     ++\n     ++\tif (!is_absolute_path(path) || path_len >= sizeof(sa.sun_path)) {\n     ++\t\tif (tr2_dst_want_warning())\n     ++\t\t\twarning(\"trace2: invalid AF_UNIX path '%s' for '%s' tracing\",\n     ++\t\t\t\tpath, dst->env_var_name);\n     ++\n     ++\t\ttr2_dst_trace_disable(dst);\n     ++\t\treturn 0;\n     ++\t}\n     ++\n     ++\tsa.sun_family = AF_UNIX;\n     ++\tstrlcpy(sa.sun_path, path, sizeof(sa.sun_path));\n     ++\tif ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1 ||\n     ++\t    connect(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {\n     ++\t\tif (tr2_dst_want_warning())\n     ++\t\t\twarning(\"trace2: could not connect to socket '%s' for '%s' tracing: %s\",\n     ++\t\t\t\tpath, dst->env_var_name, strerror(errno));\n     ++\n     ++\t\ttr2_dst_trace_disable(dst);\n     ++\t\treturn 0;\n     ++\t}\n     ++\n     ++\tdst->fd = fd;\n     ++\tdst->need_close = 1;\n     ++\tdst->initialized = 1;\n     ++\n     ++\treturn dst->fd;\n     ++}\n     ++#endif\n     ++\n     ++static void tr2_dst_malformed_warning(struct tr2_dst *dst,\n     ++\t\t\t\t      const char *tgt_value)\n     ++{\n     ++\tstruct strbuf buf = STRBUF_INIT;\n     ++\n     ++\tstrbuf_addf(&buf, \"trace2: unknown trace value for '%s': '%s'\",\n     ++\t\t    dst->env_var_name, tgt_value);\n     ++\tstrbuf_addstr(\n     ++\t\t&buf,\n     ++\t\t\"\\n         If you want to trace into a file, then please set it\"\n     ++\t\t\"\\n         to an absolute pathname.\");\n     ++#ifndef NO_UNIX_SOCKETS\n     ++\tstrbuf_addstr(\n     ++\t\t&buf,\n     ++\t\t\"\\n         If you want to trace to a unix domain socket, prefix\"\n     ++\t\t\"\\n         the absolute pathname with \\\"af_unix:\\\".\");\n     ++#endif\n     ++\n     ++\twarning(\"%s\", buf.buf);\n     ++\n     ++\tstrbuf_release(&buf);\n     ++}\n     ++\n      +int tr2_dst_get_trace_fd(struct tr2_dst *dst)\n      +{\n     -+\tconst char *trace;\n     ++\tconst char *tgt_value;\n      +\n      +\t/* don't open twice */\n      +\tif (dst->initialized)\n      +\t\treturn dst->fd;\n      +\n     -+\ttrace = getenv(dst->env_var_name);\n     ++\tdst->initialized = 1;\n     ++\n     ++\ttgt_value = getenv(dst->env_var_name);\n      +\n     -+\tif (!trace || !strcmp(trace, \"\") ||\n     -+\t    !strcmp(trace, \"0\") || !strcasecmp(trace, \"false\"))\n     ++\tif (!tgt_value || !strcmp(tgt_value, \"\") || !strcmp(tgt_value, \"0\") ||\n     ++\t    !strcasecmp(tgt_value, \"false\")) {\n      +\t\tdst->fd = 0;\n     -+\telse if (!strcmp(trace, \"1\") || !strcasecmp(trace, \"true\"))\n     ++\t\treturn dst->fd;\n     ++\t}\n     ++\n     ++\tif (!strcmp(tgt_value, \"1\") || !strcasecmp(tgt_value, \"true\")) {\n      +\t\tdst->fd = STDERR_FILENO;\n     -+\telse if (strlen(trace) == 1 && isdigit(*trace))\n     -+\t\tdst->fd = atoi(trace);\n     -+\telse if (is_absolute_path(trace)) {\n     -+\t\tint fd = open(trace, O_WRONLY | O_APPEND | O_CREAT, 0666);\n     -+\t\tif (fd == -1) {\n     -+\t\t\t/*\n     -+\t\t\t * Silently eat the error and disable tracing on this\n     -+\t\t\t * destination.  This will cause us to lose events,\n     -+\t\t\t * but that is better than causing a stream of warnings\n     -+\t\t\t * for each git command they run.\n     -+\t\t\t *\n     -+\t\t\t * warning(\"could not open '%s' for tracing: %s\",\n     -+\t\t\t *         trace, strerror(errno));\n     -+\t\t\t */\n     -+\t\t\ttr2_dst_trace_disable(dst);\n     -+\t\t} else {\n     -+\t\t\tdst->fd = fd;\n     -+\t\t\tdst->need_close = 1;\n     -+\t\t}\n     -+\t} else {\n     -+\t\twarning(\"unknown trace value for '%s': %s\\n\"\n     -+\t\t\t\"         If you want to trace into a file, then please set %s\\n\"\n     -+\t\t\t\"         to an absolute pathname (starting with /)\",\n     -+\t\t\tdst->env_var_name, trace, dst->env_var_name);\n     -+\t\ttr2_dst_trace_disable(dst);\n     ++\t\treturn dst->fd;\n      +\t}\n      +\n     -+\tdst->initialized = 1;\n     -+\treturn dst->fd;\n     ++\tif (strlen(tgt_value) == 1 && isdigit(*tgt_value)) {\n     ++\t\tdst->fd = atoi(tgt_value);\n     ++\t\treturn dst->fd;\n     ++\t}\n     ++\n     ++\tif (is_absolute_path(tgt_value))\n     ++\t\treturn tr2_dst_try_path(dst, tgt_value);\n     ++\n     ++#ifndef NO_UNIX_SOCKETS\n     ++\tif (!strncmp(tgt_value, PREFIX_AF_UNIX, PREFIX_AF_UNIX_LEN))\n     ++\t\treturn tr2_dst_try_unix_domain_socket(dst, tgt_value);\n     ++#endif\n     ++\n     ++\t/* Always warn about malformed values. */\n     ++\ttr2_dst_malformed_warning(dst, tgt_value);\n     ++\ttr2_dst_trace_disable(dst);\n     ++\treturn 0;\n      +}\n      +\n      +int tr2_dst_trace_want(struct tr2_dst *dst)\n     @@ -2105,12 +2172,15 @@\n      +\t * If we get an IO error, just close the trace dst.\n      +\t */\n      +\n     -+\tif (write(tr2_dst_get_trace_fd(dst),\n     -+\t\t  buf_line->buf, buf_line->len) < 0) {\n     -+\t\twarning(\"unable to write trace for '%s': %s\",\n     -+\t\t\tdst->env_var_name, strerror(errno));\n     -+\t\ttr2_dst_trace_disable(dst);\n     -+\t}\n     ++\tint fd = tr2_dst_get_trace_fd(dst);\n     ++\n     ++\tif (write(fd, buf_line->buf, buf_line->len) >= 0)\n     ++\t\treturn;\n     ++\n     ++\tif (tr2_dst_want_warning())\n     ++\t\twarning(\"unable to write trace to '%s': %s\", dst->env_var_name,\n     ++\t\t\tstrerror(errno));\n     ++\ttr2_dst_trace_disable(dst);\n      +}\n      \n       diff --git a/trace2/tr2_dst.h b/trace2/tr2_dst.h\n     @@ -2121,11 +2191,13 @@\n      +#ifndef TR2_DST_H\n      +#define TR2_DST_H\n      +\n     ++struct strbuf;\n     ++\n      +struct tr2_dst {\n     -+\tconst char * const env_var_name;\n     ++\tconst char *const env_var_name;\n      +\tint fd;\n      +\tunsigned int initialized : 1;\n     -+\tunsigned int  need_close : 1;\n     ++\tunsigned int need_close : 1;\n      +};\n      +\n      +/*\n     @@ -2199,8 +2271,8 @@\n      +\t}\n      +\n      +\tus_now = getnanotime() / 1000;\n     -+\tstrbuf_addf(&tr2sid_buf, \"%\"PRIuMAX\"-%\"PRIdMAX,\n     -+\t\t    (uintmax_t)us_now, (intmax_t)getpid());\n     ++\tstrbuf_addf(&tr2sid_buf, \"%\" PRIuMAX \"-%\" PRIdMAX, (uintmax_t)us_now,\n     ++\t\t    (intmax_t)getpid());\n      +\n      +\tsetenv(TR2_ENVVAR_PARENT_SID, tr2sid_buf.buf, 1);\n      +}\n     @@ -2268,8 +2340,8 @@\n      +\tsecs = tv.tv_sec;\n      +\tlocaltime_r(&secs, &tm);\n      +\n     -+\txsnprintf(tb->buf, sizeof(tb->buf), \"%02d:%02d:%02d.%06ld\",\n     -+\t\t  tm.tm_hour, tm.tm_min, tm.tm_sec, (long)tv.tv_usec);\n     ++\txsnprintf(tb->buf, sizeof(tb->buf), \"%02d:%02d:%02d.%06ld\", tm.tm_hour,\n     ++\t\t  tm.tm_min, tm.tm_sec, (long)tv.tv_usec);\n      +}\n      +\n      +void tr2_tbuf_utc_time(struct tr2_tbuf *tb)\n     @@ -2282,11 +2354,11 @@\n      +\tsecs = tv.tv_sec;\n      +\tgmtime_r(&secs, &tm);\n      +\n     -+\txsnprintf(tb->buf, sizeof(tb->buf), \"%4d-%02d-%02d %02d:%02d:%02d.%06ld\",\n     -+\t\t  tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,\n     -+\t\t  tm.tm_hour, tm.tm_min, tm.tm_sec, (long)tv.tv_usec);\n     ++\txsnprintf(tb->buf, sizeof(tb->buf),\n     ++\t\t  \"%4d-%02d-%02d %02d:%02d:%02d.%06ld\", tm.tm_year + 1900,\n     ++\t\t  tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,\n     ++\t\t  (long)tv.tv_usec);\n      +}\n     -+\n      \n       diff --git a/trace2/tr2_tbuf.h b/trace2/tr2_tbuf.h\n       new file mode 100644\n     @@ -2325,94 +2397,99 @@\n      +#ifndef TR2_TGT_H\n      +#define TR2_TGT_H\n      +\n     ++struct child_process;\n     ++struct repository;\n     ++struct json_writer;\n     ++\n      +/*\n      + * Function prototypes for a TRACE2 \"target\" vtable.\n      + */\n      +\n     -+typedef int (tr2_tgt_init_t)(void);\n     -+typedef void (tr2_tgt_term_t)(void);\n     -+\n     -+typedef void (tr2_tgt_evt_version_fl_t)\n     -+\t(const char *file, int line);\n     -+\n     -+typedef void (tr2_tgt_evt_start_fl_t)\n     -+\t(const char *file, int line, const char **argv);\n     -+typedef void (tr2_tgt_evt_exit_fl_t)\n     -+\t(const char *file, int line, uint64_t us_elapsed_absolute, int code);\n     -+typedef void (tr2_tgt_evt_signal_t)\n     -+\t(uint64_t us_elapsed_absolute, int signo);\n     -+typedef void (tr2_tgt_evt_atexit_t)\n     -+\t(uint64_t us_elapsed_absolute, int code);\n     -+\n     -+typedef void (tr2_tgt_evt_error_va_fl_t)\n     -+\t(const char *file, int line, const char *fmt, va_list ap);\n     -+\n     -+typedef void (tr2_tgt_evt_command_path_fl_t)\n     -+\t(const char *file, int line, const char *command_path);\n     -+typedef void (tr2_tgt_evt_command_verb_fl_t)\n     -+\t(const char *file, int line, const char *command_verb,\n     -+\t const char *hierarchy);\n     -+typedef void (tr2_tgt_evt_command_subverb_fl_t)\n     -+\t(const char *file, int line, const char *command_subverb);\n     -+\n     -+typedef void (tr2_tgt_evt_alias_fl_t)\n     -+\t(const char *file, int line, const char *alias, const char **argv);\n     -+\n     -+typedef void (tr2_tgt_evt_child_start_fl_t)\n     -+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n     -+\t const struct child_process *cmd);\n     -+typedef void (tr2_tgt_evt_child_exit_fl_t)\n     -+\t(const char *file, int line, uint64_t us_elapsed_absolute, int cid,\n     -+\t int pid, int code, uint64_t us_elapsed_child);\n     -+\n     -+typedef void (tr2_tgt_evt_thread_start_fl_t)\n     -+\t(const char *file, int line, uint64_t us_elapsed_absolute);\n     -+typedef void (tr2_tgt_evt_thread_exit_fl_t)\n     -+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n     -+\t uint64_t us_elapsed_thread);\n     -+\n     -+typedef void (tr2_tgt_evt_exec_fl_t)\n     -+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n     -+\t int exec_id, const char *exe, const char **argv);\n     -+typedef void (tr2_tgt_evt_exec_result_fl_t)\n     -+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n     -+\t int exec_id, int code);\n     -+\n     -+typedef void (tr2_tgt_evt_param_fl_t)\n     -+\t(const char *file, int line, const char *param, const char *value);\n     -+\n     -+typedef void (tr2_tgt_evt_repo_fl_t)\n     -+\t(const char *file, int line, const struct repository *repo);\n     -+\n     -+typedef void (tr2_tgt_evt_region_enter_printf_va_fl_t)\n     -+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n     -+\t const char *category, const char *label, const struct repository *repo,\n     -+\t const char *fmt, va_list ap);\n     -+typedef void (tr2_tgt_evt_region_leave_printf_va_fl_t)\n     -+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n     -+\t uint64_t us_elapsed_region, const char *category, const char *label,\n     -+\t const struct repository *repo, const char *fmt, va_list ap);\n     -+\n     -+typedef void (tr2_tgt_evt_data_fl_t)\n     -+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n     -+\t uint64_t us_elapsed_region, const char *category,\n     -+\t const struct repository *repo, const char *key, const char *value);\n     -+typedef void (tr2_tgt_evt_data_json_fl_t)\n     -+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n     -+\t uint64_t us_elapsed_region, const char *category,\n     -+\t const struct repository *repo,\n     -+\t const char *key, const struct json_writer *value);\n     -+\n     -+typedef void (tr2_tgt_evt_printf_va_fl_t)\n     -+\t(const char *file, int line, uint64_t us_elapsed_absolute,\n     -+\t const char *fmt, va_list ap);\n     ++typedef int(tr2_tgt_init_t)(void);\n     ++typedef void(tr2_tgt_term_t)(void);\n     ++\n     ++typedef void(tr2_tgt_evt_version_fl_t)(const char *file, int line);\n     ++\n     ++typedef void(tr2_tgt_evt_start_fl_t)(const char *file, int line,\n     ++\t\t\t\t     const char **argv);\n     ++typedef void(tr2_tgt_evt_exit_fl_t)(const char *file, int line,\n     ++\t\t\t\t    uint64_t us_elapsed_absolute, int code);\n     ++typedef void(tr2_tgt_evt_signal_t)(uint64_t us_elapsed_absolute, int signo);\n     ++typedef void(tr2_tgt_evt_atexit_t)(uint64_t us_elapsed_absolute, int code);\n     ++\n     ++typedef void(tr2_tgt_evt_error_va_fl_t)(const char *file, int line,\n     ++\t\t\t\t\tconst char *fmt, va_list ap);\n     ++\n     ++typedef void(tr2_tgt_evt_command_path_fl_t)(const char *file, int line,\n     ++\t\t\t\t\t    const char *command_path);\n     ++typedef void(tr2_tgt_evt_command_verb_fl_t)(const char *file, int line,\n     ++\t\t\t\t\t    const char *command_verb,\n     ++\t\t\t\t\t    const char *hierarchy);\n     ++typedef void(tr2_tgt_evt_command_subverb_fl_t)(const char *file, int line,\n     ++\t\t\t\t\t       const char *command_subverb);\n     ++\n     ++typedef void(tr2_tgt_evt_alias_fl_t)(const char *file, int line,\n     ++\t\t\t\t     const char *alias, const char **argv);\n     ++\n     ++typedef void(tr2_tgt_evt_child_start_fl_t)(const char *file, int line,\n     ++\t\t\t\t\t   uint64_t us_elapsed_absolute,\n     ++\t\t\t\t\t   const struct child_process *cmd);\n     ++typedef void(tr2_tgt_evt_child_exit_fl_t)(const char *file, int line,\n     ++\t\t\t\t\t  uint64_t us_elapsed_absolute, int cid,\n     ++\t\t\t\t\t  int pid, int code,\n     ++\t\t\t\t\t  uint64_t us_elapsed_child);\n     ++\n     ++typedef void(tr2_tgt_evt_thread_start_fl_t)(const char *file, int line,\n     ++\t\t\t\t\t    uint64_t us_elapsed_absolute);\n     ++typedef void(tr2_tgt_evt_thread_exit_fl_t)(const char *file, int line,\n     ++\t\t\t\t\t   uint64_t us_elapsed_absolute,\n     ++\t\t\t\t\t   uint64_t us_elapsed_thread);\n     ++\n     ++typedef void(tr2_tgt_evt_exec_fl_t)(const char *file, int line,\n     ++\t\t\t\t    uint64_t us_elapsed_absolute, int exec_id,\n     ++\t\t\t\t    const char *exe, const char **argv);\n     ++typedef void(tr2_tgt_evt_exec_result_fl_t)(const char *file, int line,\n     ++\t\t\t\t\t   uint64_t us_elapsed_absolute,\n     ++\t\t\t\t\t   int exec_id, int code);\n     ++\n     ++typedef void(tr2_tgt_evt_param_fl_t)(const char *file, int line,\n     ++\t\t\t\t     const char *param, const char *value);\n     ++\n     ++typedef void(tr2_tgt_evt_repo_fl_t)(const char *file, int line,\n     ++\t\t\t\t    const struct repository *repo);\n     ++\n     ++typedef void(tr2_tgt_evt_region_enter_printf_va_fl_t)(\n     ++\tconst char *file, int line, uint64_t us_elapsed_absolute,\n     ++\tconst char *category, const char *label, const struct repository *repo,\n     ++\tconst char *fmt, va_list ap);\n     ++typedef void(tr2_tgt_evt_region_leave_printf_va_fl_t)(\n     ++\tconst char *file, int line, uint64_t us_elapsed_absolute,\n     ++\tuint64_t us_elapsed_region, const char *category, const char *label,\n     ++\tconst struct repository *repo, const char *fmt, va_list ap);\n     ++\n     ++typedef void(tr2_tgt_evt_data_fl_t)(const char *file, int line,\n     ++\t\t\t\t    uint64_t us_elapsed_absolute,\n     ++\t\t\t\t    uint64_t us_elapsed_region,\n     ++\t\t\t\t    const char *category,\n     ++\t\t\t\t    const struct repository *repo,\n     ++\t\t\t\t    const char *key, const char *value);\n     ++typedef void(tr2_tgt_evt_data_json_fl_t)(const char *file, int line,\n     ++\t\t\t\t\t uint64_t us_elapsed_absolute,\n     ++\t\t\t\t\t uint64_t us_elapsed_region,\n     ++\t\t\t\t\t const char *category,\n     ++\t\t\t\t\t const struct repository *repo,\n     ++\t\t\t\t\t const char *key,\n     ++\t\t\t\t\t const struct json_writer *value);\n     ++\n     ++typedef void(tr2_tgt_evt_printf_va_fl_t)(const char *file, int line,\n     ++\t\t\t\t\t uint64_t us_elapsed_absolute,\n     ++\t\t\t\t\t const char *fmt, va_list ap);\n      +\n      +/*\n      + * \"vtable\" for a TRACE2 target.  Use NULL if a target does not want\n      + * to emit that message.\n      + */\n     -+struct tr2_tgt\n     -+{\n     ++struct tr2_tgt {\n      +\tstruct tr2_dst                          *pdst;\n      +\n      +\ttr2_tgt_init_t                          *pfn_init;\n     @@ -2465,7 +2542,7 @@\n      +#include \"trace2/tr2_tgt.h\"\n      +#include \"trace2/tr2_tls.h\"\n      +\n     -+static struct tr2_dst tr2dst_event  = { \"GIT_TR2_EVENT\", 0, 0, 0 };\n     ++static struct tr2_dst tr2dst_event = { \"GIT_TR2_EVENT\", 0, 0, 0 };\n      +\n      +/*\n      + * The version number of the JSON data generated by the EVENT target\n     @@ -2534,9 +2611,8 @@\n      + *     \"line\":<line_number>\n      + *     \"repo\":<repo_id>\n      + */\n     -+static void event_fmt_prepare(const char *event_name,\n     -+\t\t\t      const char *file, int line,\n     -+\t\t\t      const struct repository *repo,\n     ++static void event_fmt_prepare(const char *event_name, const char *file,\n     ++\t\t\t      int line, const struct repository *repo,\n      +\t\t\t      struct json_writer *jw)\n      +{\n      +\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n     @@ -2549,8 +2625,7 @@\n      +\t/*\n      +\t * In brief mode, only emit <time> on these 2 event types.\n      +\t */\n     -+\tif (!tr2env_event_brief ||\n     -+\t    !strcmp(event_name, \"version\") ||\n     ++\tif (!tr2env_event_brief || !strcmp(event_name, \"version\") ||\n      +\t    !strcmp(event_name, \"atexit\")) {\n      +\t\ttr2_tbuf_utc_time(&tb_now);\n      +\t\tjw_object_string(jw, \"time\", tb_now.buf);\n     @@ -2596,8 +2671,8 @@\n      +\tjw_release(&jw);\n      +}\n      +\n     -+static void fn_exit_fl(const char *file, int line,\n     -+\t\t       uint64_t us_elapsed_absolute, int code)\n     ++static void fn_exit_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n     ++\t\t       int code)\n      +{\n      +\tconst char *event_name = \"exit\";\n      +\tstruct json_writer jw = JSON_WRITER_INIT;\n     @@ -2645,8 +2720,7 @@\n      +\tjw_release(&jw);\n      +}\n      +\n     -+static void maybe_add_string_va(struct json_writer *jw,\n     -+\t\t\t\tconst char *field_name,\n     ++static void maybe_add_string_va(struct json_writer *jw, const char *field_name,\n      +\t\t\t\tconst char *fmt, va_list ap)\n      +{\n      +\tif (fmt && *fmt && ap) {\n     @@ -2668,8 +2742,8 @@\n      +\t}\n      +}\n      +\n     -+static void fn_error_va_fl(const char *file, int line,\n     -+\t\t\t   const char *fmt, va_list ap)\n     ++static void fn_error_va_fl(const char *file, int line, const char *fmt,\n     ++\t\t\t   va_list ap)\n      +{\n      +\tconst char *event_name = \"error\";\n      +\tstruct json_writer jw = JSON_WRITER_INIT;\n     @@ -2691,8 +2765,7 @@\n      +\tjw_release(&jw);\n      +}\n      +\n     -+static void fn_command_path_fl(const char *file, int line,\n     -+\t\t\t       const char *pathname)\n     ++static void fn_command_path_fl(const char *file, int line, const char *pathname)\n      +{\n      +\tconst char *event_name = \"cmd_path\";\n      +\tstruct json_writer jw = JSON_WRITER_INIT;\n     @@ -2725,7 +2798,7 @@\n      +}\n      +\n      +static void fn_command_subverb_fl(const char *file, int line,\n     -+\t\t\t       const char *command_subverb)\n     ++\t\t\t\t  const char *command_subverb)\n      +{\n      +\tconst char *event_name = \"cmd_subverb\";\n      +\tstruct json_writer jw = JSON_WRITER_INIT;\n     @@ -2739,8 +2812,8 @@\n      +\tjw_release(&jw);\n      +}\n      +\n     -+static void fn_alias_fl(const char *file, int line,\n     -+\t\t\tconst char *alias, const char **argv)\n     ++static void fn_alias_fl(const char *file, int line, const char *alias,\n     ++\t\t\tconst char **argv)\n      +{\n      +\tconst char *event_name = \"alias\";\n      +\tstruct json_writer jw = JSON_WRITER_INIT;\n     @@ -2771,8 +2844,8 @@\n      +\t\tjw_object_string(&jw, \"child_class\", \"hook\");\n      +\t\tjw_object_string(&jw, \"hook_name\", cmd->trace2_hook_name);\n      +\t} else {\n     -+\t\tconst char *child_class = ((cmd->trace2_child_class) ?\n     -+\t\t\t\t\t   cmd->trace2_child_class : \"?\");\n     ++\t\tconst char *child_class =\n     ++\t\t\tcmd->trace2_child_class ? cmd->trace2_child_class : \"?\";\n      +\t\tjw_object_string(&jw, \"child_class\", child_class);\n      +\t}\n      +\tif (cmd->dir)\n     @@ -2790,9 +2863,8 @@\n      +}\n      +\n      +static void fn_child_exit_fl(const char *file, int line,\n     -+\t\t\t     uint64_t us_elapsed_absolute,\n     -+\t\t\t     int cid, int pid, int code,\n     -+\t\t\t     uint64_t us_elapsed_child)\n     ++\t\t\t     uint64_t us_elapsed_absolute, int cid, int pid,\n     ++\t\t\t     int code, uint64_t us_elapsed_child)\n      +{\n      +\tconst char *event_name = \"child_exit\";\n      +\tstruct json_writer jw = JSON_WRITER_INIT;\n     @@ -2842,8 +2914,7 @@\n      +\tjw_release(&jw);\n      +}\n      +\n     -+static void fn_exec_fl(const char *file, int line,\n     -+\t\t       uint64_t us_elapsed_absolute,\n     ++static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n      +\t\t       int exec_id, const char *exe, const char **argv)\n      +{\n      +\tconst char *event_name = \"exec\";\n     @@ -2864,8 +2935,8 @@\n      +}\n      +\n      +static void fn_exec_result_fl(const char *file, int line,\n     -+\t\t\t      uint64_t us_elapsed_absolute,\n     -+\t\t\t      int exec_id, int code)\n     ++\t\t\t      uint64_t us_elapsed_absolute, int exec_id,\n     ++\t\t\t      int code)\n      +{\n      +\tconst char *event_name = \"exec_result\";\n      +\tstruct json_writer jw = JSON_WRITER_INIT;\n     @@ -2880,8 +2951,8 @@\n      +\tjw_release(&jw);\n      +}\n      +\n     -+static void fn_param_fl(const char *file, int line,\n     -+\t\t\tconst char *param, const char *value)\n     ++static void fn_param_fl(const char *file, int line, const char *param,\n     ++\t\t\tconst char *value)\n      +{\n      +\tconst char *event_name = \"def_param\";\n      +\tstruct json_writer jw = JSON_WRITER_INIT;\n     @@ -2924,8 +2995,7 @@\n      +\t\tstruct json_writer jw = JSON_WRITER_INIT;\n      +\n      +\t\tjw_object_begin(&jw, 0);\n     -+\t\tevent_fmt_prepare(\n     -+\t\t\tevent_name, file, line, repo, &jw);\n     ++\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n      +\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n      +\t\tif (category)\n      +\t\t\tjw_object_string(&jw, \"category\", category);\n     @@ -2939,13 +3009,10 @@\n      +\t}\n      +}\n      +\n     -+static void fn_region_leave_printf_va_fl(const char *file, int line,\n     -+\t\t\t\t\t uint64_t us_elapsed_absolute,\n     -+\t\t\t\t\t uint64_t us_elapsed_region,\n     -+\t\t\t\t\t const char *category,\n     -+\t\t\t\t\t const char *label,\n     -+\t\t\t\t\t const struct repository *repo,\n     -+\t\t\t\t\t const char *fmt, va_list ap)\n     ++static void fn_region_leave_printf_va_fl(\n     ++\tconst char *file, int line, uint64_t us_elapsed_absolute,\n     ++\tuint64_t us_elapsed_region, const char *category, const char *label,\n     ++\tconst struct repository *repo, const char *fmt, va_list ap)\n      +{\n      +\tconst char *event_name = \"region_leave\";\n      +\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n     @@ -2969,12 +3036,9 @@\n      +\t}\n      +}\n      +\n     -+static void fn_data_fl(const char *file, int line,\n     -+\t\t       uint64_t us_elapsed_absolute,\n     -+\t\t       uint64_t us_elapsed_region,\n     -+\t\t       const char *category,\n     -+\t\t       const struct repository *repo,\n     -+\t\t       const char *key,\n     ++static void fn_data_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n     ++\t\t       uint64_t us_elapsed_region, const char *category,\n     ++\t\t       const struct repository *repo, const char *key,\n      +\t\t       const char *value)\n      +{\n      +\tconst char *event_name = \"data\";\n     @@ -3001,10 +3065,8 @@\n      +\n      +static void fn_data_json_fl(const char *file, int line,\n      +\t\t\t    uint64_t us_elapsed_absolute,\n     -+\t\t\t    uint64_t us_elapsed_region,\n     -+\t\t\t    const char *category,\n     -+\t\t\t    const struct repository *repo,\n     -+\t\t\t    const char *key,\n     ++\t\t\t    uint64_t us_elapsed_region, const char *category,\n     ++\t\t\t    const struct repository *repo, const char *key,\n      +\t\t\t    const struct json_writer *value)\n      +{\n      +\tconst char *event_name = \"data_json\";\n     @@ -3029,8 +3091,7 @@\n      +\t}\n      +}\n      +\n     -+struct tr2_tgt tr2_tgt_event =\n     -+{\n     ++struct tr2_tgt tr2_tgt_event = {\n      +\t&tr2dst_event,\n      +\n      +\tfn_init,\n     @@ -3087,7 +3148,7 @@\n      +#define TR2_ENVVAR_NORMAL_BRIEF \"GIT_TR2_BRIEF\"\n      +static int tr2env_normal_brief;\n      +\n     -+#define TR2FMT_NORMAL_FL_WIDTH       (50)\n     ++#define TR2FMT_NORMAL_FL_WIDTH (50)\n      +\n      +static int fn_init(void)\n      +{\n     @@ -3113,7 +3174,7 @@\n      +\n      +static void normal_fmt_prepare(const char *file, int line, struct strbuf *buf)\n      +{\n     -+\tstrbuf_setlen(buf,0);\n     ++\tstrbuf_setlen(buf, 0);\n      +\n      +\tif (!tr2env_normal_brief) {\n      +\t\tstruct tr2_tbuf tb_now;\n     @@ -3159,14 +3220,13 @@\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n     -+static void fn_exit_fl(const char *file, int line,\n     -+\t\t       uint64_t us_elapsed_absolute, int code)\n     ++static void fn_exit_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n     ++\t\t       int code)\n      +{\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n      +\tdouble elapsed = (double)us_elapsed_absolute / 1000000.0;\n      +\n     -+\tstrbuf_addf(&buf_payload, \"exit elapsed:%.6f code:%d\",\n     -+\t\t    elapsed, code);\n     ++\tstrbuf_addf(&buf_payload, \"exit elapsed:%.6f code:%d\", elapsed, code);\n      +\tnormal_io_write_fl(file, line, &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n     @@ -3176,8 +3236,8 @@\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n      +\tdouble elapsed = (double)us_elapsed_absolute / 1000000.0;\n      +\n     -+\tstrbuf_addf(&buf_payload, \"signal elapsed:%.6f code:%d\",\n     -+\t\t    elapsed, signo);\n     ++\tstrbuf_addf(&buf_payload, \"signal elapsed:%.6f code:%d\", elapsed,\n     ++\t\t    signo);\n      +\tnormal_io_write_fl(__FILE__, __LINE__, &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n     @@ -3187,14 +3247,13 @@\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n      +\tdouble elapsed = (double)us_elapsed_absolute / 1000000.0;\n      +\n     -+\tstrbuf_addf(&buf_payload, \"atexit elapsed:%.6f code:%d\",\n     -+\t\t    elapsed, code);\n     ++\tstrbuf_addf(&buf_payload, \"atexit elapsed:%.6f code:%d\", elapsed, code);\n      +\tnormal_io_write_fl(__FILE__, __LINE__, &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n     -+static void maybe_append_string_va(struct strbuf *buf,\n     -+\t\t\t\t   const char *fmt, va_list ap)\n     ++static void maybe_append_string_va(struct strbuf *buf, const char *fmt,\n     ++\t\t\t\t   va_list ap)\n      +{\n      +\tif (fmt && *fmt && ap) {\n      +\t\tva_list copy_ap;\n     @@ -3211,8 +3270,8 @@\n      +\t}\n      +}\n      +\n     -+static void fn_error_va_fl(const char *file, int line,\n     -+\t\t\t   const char *fmt, va_list ap)\n     ++static void fn_error_va_fl(const char *file, int line, const char *fmt,\n     ++\t\t\t   va_list ap)\n      +{\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n      +\n     @@ -3222,8 +3281,7 @@\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n     -+static void fn_command_path_fl(const char *file, int line,\n     -+\t\t\t       const char *pathname)\n     ++static void fn_command_path_fl(const char *file, int line, const char *pathname)\n      +{\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n      +\n     @@ -3246,7 +3304,7 @@\n      +}\n      +\n      +static void fn_command_subverb_fl(const char *file, int line,\n     -+\t\t\t       const char *command_subverb)\n     ++\t\t\t\t  const char *command_subverb)\n      +{\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n      +\n     @@ -3294,9 +3352,8 @@\n      +}\n      +\n      +static void fn_child_exit_fl(const char *file, int line,\n     -+\t\t\t     uint64_t us_elapsed_absolute,\n     -+\t\t\t     int cid, int pid, int code,\n     -+\t\t\t     uint64_t us_elapsed_child)\n     ++\t\t\t     uint64_t us_elapsed_absolute, int cid, int pid,\n     ++\t\t\t     int code, uint64_t us_elapsed_child)\n      +{\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n      +\tdouble elapsed = (double)us_elapsed_child / 1000000.0;\n     @@ -3307,8 +3364,7 @@\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n     -+static void fn_exec_fl(const char *file, int line,\n     -+\t\t       uint64_t us_elapsed_absolute,\n     ++static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n      +\t\t       int exec_id, const char *exe, const char **argv)\n      +{\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n     @@ -3322,8 +3378,8 @@\n      +}\n      +\n      +static void fn_exec_result_fl(const char *file, int line,\n     -+\t\t\t      uint64_t us_elapsed_absolute,\n     -+\t\t\t      int exec_id, int code)\n     ++\t\t\t      uint64_t us_elapsed_absolute, int exec_id,\n     ++\t\t\t      int code)\n      +{\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n      +\n     @@ -3356,8 +3412,8 @@\n      +}\n      +\n      +static void fn_printf_va_fl(const char *file, int line,\n     -+\t\t\t    uint64_t us_elapsed_absolute,\n     -+\t\t\t    const char *fmt, va_list ap)\n     ++\t\t\t    uint64_t us_elapsed_absolute, const char *fmt,\n     ++\t\t\t    va_list ap)\n      +{\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n      +\n     @@ -3366,8 +3422,7 @@\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n     -+struct tr2_tgt tr2_tgt_normal =\n     -+{\n     ++struct tr2_tgt tr2_tgt_normal = {\n      +\t&tr2dst_normal,\n      +\n      +\tfn_init,\n     @@ -3415,7 +3470,7 @@\n      +#include \"trace2/tr2_tgt.h\"\n      +#include \"trace2/tr2_tls.h\"\n      +\n     -+static struct tr2_dst tr2dst_perf   = { \"GIT_TR2_PERF\", 0, 0, 0 };\n     ++static struct tr2_dst tr2dst_perf = { \"GIT_TR2_PERF\", 0, 0, 0 };\n      +\n      +/*\n      + * Set this environment variable to true to omit the \"<time> <file>:<line>\"\n     @@ -3426,9 +3481,9 @@\n      +#define TR2_ENVVAR_PERF_BRIEF \"GIT_TR2_PERF_BRIEF\"\n      +static int tr2env_perf_brief;\n      +\n     -+#define TR2FMT_PERF_FL_WIDTH       (50)\n     ++#define TR2FMT_PERF_FL_WIDTH (50)\n      +#define TR2FMT_PERF_MAX_EVENT_NAME (12)\n     -+#define TR2FMT_PERF_REPO_WIDTH     (4)\n     ++#define TR2FMT_PERF_REPO_WIDTH (4)\n      +#define TR2FMT_PERF_CATEGORY_WIDTH (10)\n      +\n      +#define TR2_DOTS_BUFFER_SIZE (100)\n     @@ -3471,18 +3526,18 @@\n      + *         [<elapsed_absolute>] [<elapsed_relative>] <bar>\n      + *         [<category>] <bar> [<dots>] \"\n      + */\n     -+static void perf_fmt_prepare(\n     -+\tconst char *event_name, struct tr2tls_thread_ctx *ctx,\n     -+\tconst char *file, int line, const struct repository *repo,\n     -+\tuint64_t *p_us_elapsed_absolute, uint64_t *p_us_elapsed_relative,\n     -+\tconst char *category, struct strbuf *buf)\n     ++static void perf_fmt_prepare(const char *event_name,\n     ++\t\t\t     struct tr2tls_thread_ctx *ctx, const char *file,\n     ++\t\t\t     int line, const struct repository *repo,\n     ++\t\t\t     uint64_t *p_us_elapsed_absolute,\n     ++\t\t\t     uint64_t *p_us_elapsed_relative,\n     ++\t\t\t     const char *category, struct strbuf *buf)\n      +{\n      +\tint len;\n      +\n     -+\tstrbuf_setlen(buf,0);\n     ++\tstrbuf_setlen(buf, 0);\n      +\n      +\tif (!tr2env_perf_brief) {\n     -+\n      +\t\tstruct tr2_tbuf tb_now;\n      +\n      +\t\ttr2_tbuf_local_time(&tb_now);\n     @@ -3498,15 +3553,15 @@\n      +\t}\n      +\n      +\tstrbuf_addf(buf, \"d%d | \", tr2_sid_depth());\n     -+\tstrbuf_addf(buf, \"%-*s | %-*s | \",\n     -+\t\t    TR2_MAX_THREAD_NAME, ctx->thread_name.buf,\n     -+\t\t    TR2FMT_PERF_MAX_EVENT_NAME, event_name);\n     ++\tstrbuf_addf(buf, \"%-*s | %-*s | \", TR2_MAX_THREAD_NAME,\n     ++\t\t    ctx->thread_name.buf, TR2FMT_PERF_MAX_EVENT_NAME,\n     ++\t\t    event_name);\n      +\n      +\tlen = buf->len + TR2FMT_PERF_REPO_WIDTH;\n      +\tif (repo)\n      +\t\tstrbuf_addf(buf, \"r%d \", repo->trace2_repo_id);\n      +\twhile (buf->len < len)\n     -+\t\tstrbuf_addch(buf, ' ' );\n     ++\t\tstrbuf_addch(buf, ' ');\n      +\tstrbuf_addstr(buf, \"| \");\n      +\n      +\tif (p_us_elapsed_absolute)\n     @@ -3520,7 +3575,7 @@\n      +\t\t\t    ((double)(*p_us_elapsed_relative)) / 1000000.0);\n      +\telse\n      +\t\tstrbuf_addf(buf, \"%9s | \", \" \");\n     -+\t\t\n     ++\n      +\tstrbuf_addf(buf, \"%-*s | \", TR2FMT_PERF_CATEGORY_WIDTH,\n      +\t\t    (category ? category : \"\"));\n      +\n     @@ -3534,21 +3589,19 @@\n      +\t}\n      +}\n      +\n     -+static void perf_io_write_fl(\n     -+\tconst char *file, int line,\n     -+\tconst char *event_name,\n     -+\tconst struct repository *repo,\n     -+\tuint64_t *p_us_elapsed_absolute,\n     -+\tuint64_t *p_us_elapsed_relative,\n     -+\tconst char *category,\n     -+\tconst struct strbuf *buf_payload)\n     ++static void perf_io_write_fl(const char *file, int line, const char *event_name,\n     ++\t\t\t     const struct repository *repo,\n     ++\t\t\t     uint64_t *p_us_elapsed_absolute,\n     ++\t\t\t     uint64_t *p_us_elapsed_relative,\n     ++\t\t\t     const char *category,\n     ++\t\t\t     const struct strbuf *buf_payload)\n      +{\n      +\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n      +\tstruct strbuf buf_line = STRBUF_INIT;\n      +\n      +\tperf_fmt_prepare(event_name, ctx, file, line, repo,\n     -+\t\t\t p_us_elapsed_absolute, p_us_elapsed_relative,\n     -+\t\t\t category, &buf_line);\n     ++\t\t\t p_us_elapsed_absolute, p_us_elapsed_relative, category,\n     ++\t\t\t &buf_line);\n      +\tstrbuf_addbuf(&buf_line, buf_payload);\n      +\ttr2_dst_write_line(&tr2dst_perf, &buf_line);\n      +\tstrbuf_release(&buf_line);\n     @@ -3561,8 +3614,7 @@\n      +\n      +\tstrbuf_addstr(&buf_payload, git_version_string);\n      +\n     -+\tperf_io_write_fl(file, line, event_name, NULL,\n     -+\t\t\t NULL, NULL, NULL,\n     ++\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n      +\t\t\t &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n     @@ -3574,23 +3626,21 @@\n      +\n      +\tsq_quote_argv_pretty(&buf_payload, argv);\n      +\n     -+\tperf_io_write_fl(file, line, event_name, NULL,\n     -+\t\t\t NULL, NULL, NULL,\n     ++\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n      +\t\t\t &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n     -+static void fn_exit_fl(const char *file, int line,\n     -+\t\t       uint64_t us_elapsed_absolute, int code)\n     ++static void fn_exit_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n     ++\t\t       int code)\n      +{\n      +\tconst char *event_name = \"exit\";\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n      +\n      +\tstrbuf_addf(&buf_payload, \"code:%d\", code);\n      +\n     -+\tperf_io_write_fl(file, line, event_name, NULL,\n     -+\t\t\t &us_elapsed_absolute, NULL, NULL,\n     -+\t\t\t &buf_payload);\n     ++\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n     ++\t\t\t NULL, NULL, &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n     @@ -3602,8 +3652,7 @@\n      +\tstrbuf_addf(&buf_payload, \"signo:%d\", signo);\n      +\n      +\tperf_io_write_fl(__FILE__, __LINE__, event_name, NULL,\n     -+\t\t\t &us_elapsed_absolute, NULL, NULL,\n     -+\t\t\t &buf_payload);\n     ++\t\t\t &us_elapsed_absolute, NULL, NULL, &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n     @@ -3615,13 +3664,12 @@\n      +\tstrbuf_addf(&buf_payload, \"code:%d\", code);\n      +\n      +\tperf_io_write_fl(__FILE__, __LINE__, event_name, NULL,\n     -+\t\t\t &us_elapsed_absolute, NULL, NULL,\n     -+\t\t\t &buf_payload);\n     ++\t\t\t &us_elapsed_absolute, NULL, NULL, &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n     -+static void maybe_append_string_va(struct strbuf *buf,\n     -+\t\t\t\t   const char *fmt, va_list ap)\n     ++static void maybe_append_string_va(struct strbuf *buf, const char *fmt,\n     ++\t\t\t\t   va_list ap)\n      +{\n      +\tif (fmt && *fmt && ap) {\n      +\t\tva_list copy_ap;\n     @@ -3638,30 +3686,27 @@\n      +\t}\n      +}\n      +\n     -+static void fn_error_va_fl(const char *file, int line,\n     -+\t\t\t   const char *fmt, va_list ap)\n     ++static void fn_error_va_fl(const char *file, int line, const char *fmt,\n     ++\t\t\t   va_list ap)\n      +{\n      +\tconst char *event_name = \"error\";\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n      +\n      +\tmaybe_append_string_va(&buf_payload, fmt, ap);\n      +\n     -+\tperf_io_write_fl(file, line, event_name, NULL,\n     -+\t\t\t NULL, NULL, NULL,\n     ++\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n      +\t\t\t &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n     -+static void fn_command_path_fl(const char *file, int line,\n     -+\t\t\t       const char *pathname)\n     ++static void fn_command_path_fl(const char *file, int line, const char *pathname)\n      +{\n      +\tconst char *event_name = \"cmd_path\";\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n      +\n      +\tstrbuf_addstr(&buf_payload, pathname);\n      +\n     -+\tperf_io_write_fl(file, line, event_name, NULL,\n     -+\t\t\t NULL, NULL, NULL,\n     ++\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n      +\t\t\t &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n     @@ -3677,22 +3722,20 @@\n      +\tif (verb_hierarchy && *verb_hierarchy)\n      +\t\tstrbuf_addf(&buf_payload, \" (%s)\", verb_hierarchy);\n      +\n     -+\tperf_io_write_fl(file, line, event_name, NULL,\n     -+\t\t\t NULL, NULL, NULL,\n     ++\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n      +\t\t\t &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n      +static void fn_command_subverb_fl(const char *file, int line,\n     -+\t\t\t       const char *command_subverb)\n     ++\t\t\t\t  const char *command_subverb)\n      +{\n      +\tconst char *event_name = \"cmd_subverb\";\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n      +\n      +\tstrbuf_addstr(&buf_payload, command_subverb);\n      +\n     -+\tperf_io_write_fl(file, line, event_name, NULL,\n     -+\t\t\t NULL, NULL, NULL,\n     ++\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n      +\t\t\t &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n     @@ -3706,8 +3749,7 @@\n      +\tstrbuf_addf(&buf_payload, \"alias:%s argv:\", alias);\n      +\tsq_quote_argv_pretty(&buf_payload, argv);\n      +\n     -+\tperf_io_write_fl(file, line, event_name, NULL,\n     -+\t\t\t NULL, NULL, NULL,\n     ++\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n      +\t\t\t &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n     @@ -3723,8 +3765,8 @@\n      +\t\tstrbuf_addf(&buf_payload, \"[ch%d] class:hook hook:%s\",\n      +\t\t\t    cmd->trace2_child_id, cmd->trace2_hook_name);\n      +\t} else {\n     -+\t\tconst char *child_class = ((cmd->trace2_child_class) ?\n     -+\t\t\t\t\t   cmd->trace2_child_class : \"?\");\n     ++\t\tconst char *child_class =\n     ++\t\t\tcmd->trace2_child_class ? cmd->trace2_child_class : \"?\";\n      +\t\tstrbuf_addf(&buf_payload, \"[ch%d] class:%s\",\n      +\t\t\t    cmd->trace2_child_id, child_class);\n      +\t}\n     @@ -3739,37 +3781,33 @@\n      +\t\tstrbuf_addstr(&buf_payload, \" git\");\n      +\tsq_quote_argv_pretty(&buf_payload, cmd->argv);\n      +\n     -+\tperf_io_write_fl(file, line, event_name, NULL,\n     -+\t\t\t &us_elapsed_absolute, NULL, NULL,\n     -+\t\t\t &buf_payload);\n     ++\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n     ++\t\t\t NULL, NULL, &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n      +static void fn_child_exit_fl(const char *file, int line,\n     -+\t\t\t     uint64_t us_elapsed_absolute,\n     -+\t\t\t     int cid, int pid, int code,\n     -+\t\t\t     uint64_t us_elapsed_child)\n     ++\t\t\t     uint64_t us_elapsed_absolute, int cid, int pid,\n     ++\t\t\t     int code, uint64_t us_elapsed_child)\n      +{\n      +\tconst char *event_name = \"child_exit\";\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n      +\n      +\tstrbuf_addf(&buf_payload, \"[ch%d] pid:%d code:%d\", cid, pid, code);\n      +\n     -+\tperf_io_write_fl(file, line, event_name, NULL,\n     -+\t\t\t &us_elapsed_absolute, &us_elapsed_child, NULL,\n     -+\t\t\t &buf_payload);\n     ++\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n     ++\t\t\t &us_elapsed_child, NULL, &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n      +static void fn_thread_start_fl(const char *file, int line,\n     -+\t\t\t\t     uint64_t us_elapsed_absolute)\n     ++\t\t\t       uint64_t us_elapsed_absolute)\n      +{\n      +\tconst char *event_name = \"thread_start\";\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n      +\n     -+\tperf_io_write_fl(file, line, event_name, NULL,\n     -+\t\t\t &us_elapsed_absolute, NULL, NULL,\n     -+\t\t\t &buf_payload);\n     ++\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n     ++\t\t\t NULL, NULL, &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n     @@ -3780,14 +3818,12 @@\n      +\tconst char *event_name = \"thread_exit\";\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n      +\n     -+\tperf_io_write_fl(file, line, event_name, NULL,\n     -+\t\t\t &us_elapsed_absolute, &us_elapsed_thread, NULL,\n     -+\t\t\t &buf_payload);\n     ++\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n     ++\t\t\t &us_elapsed_thread, NULL, &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n     -+static void fn_exec_fl(const char *file, int line,\n     -+\t\t       uint64_t us_elapsed_absolute,\n     ++static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n      +\t\t       int exec_id, const char *exe, const char **argv)\n      +{\n      +\tconst char *event_name = \"exec\";\n     @@ -3799,15 +3835,14 @@\n      +\t\tstrbuf_addf(&buf_payload, \" %s\", exe);\n      +\tsq_quote_argv_pretty(&buf_payload, argv);\n      +\n     -+\tperf_io_write_fl(file, line, event_name, NULL,\n     -+\t\t\t &us_elapsed_absolute, NULL, NULL,\n     -+\t\t\t &buf_payload);\n     ++\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n     ++\t\t\t NULL, NULL, &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n      +static void fn_exec_result_fl(const char *file, int line,\n     -+\t\t\t      uint64_t us_elapsed_absolute,\n     -+\t\t\t      int exec_id, int code)\n     ++\t\t\t      uint64_t us_elapsed_absolute, int exec_id,\n     ++\t\t\t      int code)\n      +{\n      +\tconst char *event_name = \"exec_result\";\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n     @@ -3816,22 +3851,20 @@\n      +\tif (code > 0)\n      +\t\tstrbuf_addf(&buf_payload, \" err:%s\", strerror(code));\n      +\n     -+\tperf_io_write_fl(file, line, event_name, NULL,\n     -+\t\t\t &us_elapsed_absolute, NULL, NULL,\n     -+\t\t\t &buf_payload);\n     ++\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n     ++\t\t\t NULL, NULL, &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n     -+static void fn_param_fl(const char *file, int line,\n     -+\t\t\tconst char *param, const char *value)\n     ++static void fn_param_fl(const char *file, int line, const char *param,\n     ++\t\t\tconst char *value)\n      +{\n      +\tconst char *event_name = \"def_param\";\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n      +\n      +\tstrbuf_addf(&buf_payload, \"%s:%s\", param, value);\n      +\n     -+\tperf_io_write_fl(file, line, event_name, NULL,\n     -+\t\t\t NULL, NULL, NULL,\n     ++\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n      +\t\t\t &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n     @@ -3845,8 +3878,7 @@\n      +\tstrbuf_addstr(&buf_payload, \"worktree:\");\n      +\tsq_quote_buf_pretty(&buf_payload, repo->worktree);\n      +\n     -+\tperf_io_write_fl(file, line, event_name, repo,\n     -+\t\t\t NULL, NULL, NULL,\n     ++\tperf_io_write_fl(file, line, event_name, repo, NULL, NULL, NULL,\n      +\t\t\t &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n     @@ -3865,19 +3897,15 @@\n      +\t\tstrbuf_addf(&buf_payload, \"label:%s \", label);\n      +\tmaybe_append_string_va(&buf_payload, fmt, ap);\n      +\n     -+\tperf_io_write_fl(file, line, event_name, repo,\n     -+\t\t\t &us_elapsed_absolute, NULL, category,\n     -+\t\t\t &buf_payload);\n     ++\tperf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,\n     ++\t\t\t NULL, category, &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n     -+static void fn_region_leave_printf_va_fl(const char *file, int line,\n     -+\t\t\t\t\t uint64_t us_elapsed_absolute,\n     -+\t\t\t\t\t uint64_t us_elapsed_region,\n     -+\t\t\t\t\t const char *category,\n     -+\t\t\t\t\t const char *label,\n     -+\t\t\t\t\t const struct repository *repo,\n     -+\t\t\t\t\t const char *fmt, va_list ap)\n     ++static void fn_region_leave_printf_va_fl(\n     ++\tconst char *file, int line, uint64_t us_elapsed_absolute,\n     ++\tuint64_t us_elapsed_region, const char *category, const char *label,\n     ++\tconst struct repository *repo, const char *fmt, va_list ap)\n      +{\n      +\tconst char *event_name = \"region_leave\";\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n     @@ -3886,18 +3914,14 @@\n      +\t\tstrbuf_addf(&buf_payload, \"label:%s \", label);\n      +\tmaybe_append_string_va(&buf_payload, fmt, ap);\n      +\n     -+\tperf_io_write_fl(file, line, event_name, repo,\n     -+\t\t\t &us_elapsed_absolute, &us_elapsed_region, category,\n     -+\t\t\t &buf_payload);\n     ++\tperf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,\n     ++\t\t\t &us_elapsed_region, category, &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n     -+static void fn_data_fl(const char *file, int line,\n     -+\t\t       uint64_t us_elapsed_absolute,\n     -+\t\t       uint64_t us_elapsed_region,\n     -+\t\t       const char *category,\n     -+\t\t       const struct repository *repo,\n     -+\t\t       const char *key,\n     ++static void fn_data_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n     ++\t\t       uint64_t us_elapsed_region, const char *category,\n     ++\t\t       const struct repository *repo, const char *key,\n      +\t\t       const char *value)\n      +{\n      +\tconst char *event_name = \"data\";\n     @@ -3905,18 +3929,15 @@\n      +\n      +\tstrbuf_addf(&buf_payload, \"%s:%s\", key, value);\n      +\n     -+\tperf_io_write_fl(file, line, event_name, repo,\n     -+\t\t\t &us_elapsed_absolute, &us_elapsed_region, category,\n     -+\t\t\t &buf_payload);\n     ++\tperf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,\n     ++\t\t\t &us_elapsed_region, category, &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n      +static void fn_data_json_fl(const char *file, int line,\n      +\t\t\t    uint64_t us_elapsed_absolute,\n     -+\t\t\t    uint64_t us_elapsed_region,\n     -+\t\t\t    const char *category,\n     -+\t\t\t    const struct repository *repo,\n     -+\t\t\t    const char *key,\n     ++\t\t\t    uint64_t us_elapsed_region, const char *category,\n     ++\t\t\t    const struct repository *repo, const char *key,\n      +\t\t\t    const struct json_writer *value)\n      +{\n      +\tconst char *event_name = \"data_json\";\n     @@ -3924,29 +3945,26 @@\n      +\n      +\tstrbuf_addf(&buf_payload, \"%s:%s\", key, value->json.buf);\n      +\n     -+\tperf_io_write_fl(file, line, event_name, repo,\n     -+\t\t\t &us_elapsed_absolute, &us_elapsed_region, category,\n     -+\t\t\t &buf_payload);\n     ++\tperf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,\n     ++\t\t\t &us_elapsed_region, category, &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n      +static void fn_printf_va_fl(const char *file, int line,\n     -+\t\t\t    uint64_t us_elapsed_absolute,\n     -+\t\t\t    const char *fmt, va_list ap)\n     ++\t\t\t    uint64_t us_elapsed_absolute, const char *fmt,\n     ++\t\t\t    va_list ap)\n      +{\n      +\tconst char *event_name = \"printf\";\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n      +\n      +\tmaybe_append_string_va(&buf_payload, fmt, ap);\n      +\n     -+\tperf_io_write_fl(file, line, event_name, NULL,\n     -+\t\t\t &us_elapsed_absolute, NULL, NULL,\n     -+\t\t\t &buf_payload);\n     ++\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n     ++\t\t\t NULL, NULL, &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n     -+struct tr2_tgt tr2_tgt_perf =\n     -+{\n     ++struct tr2_tgt tr2_tgt_perf = {\n      +\t&tr2dst_perf,\n      +\n      +\tfn_init,\n     @@ -4012,11 +4030,11 @@\n      +\t * application run time.\n      +\t */\n      +\tctx->alloc = TR2_REGION_NESTING_INITIAL_SIZE;\n     -+\tctx->array_us_start = (uint64_t*)xcalloc(ctx->alloc, sizeof(uint64_t));\n     ++\tctx->array_us_start = (uint64_t *)xcalloc(ctx->alloc, sizeof(uint64_t));\n      +\tctx->array_us_start[ctx->nr_open_regions++] = us_now;\n      +\n      +\tctx->thread_id = tr2tls_locked_increment(&tr2_next_thread_id);\n     -+\t\n     ++\n      +\tstrbuf_init(&ctx->thread_name, 0);\n      +\tif (ctx->thread_id)\n      +\t\tstrbuf_addf(&ctx->thread_name, \"th%02d:\", ctx->thread_id);\n     @@ -4031,7 +4049,7 @@\n      +\n      +struct tr2tls_thread_ctx *tr2tls_get_self(void)\n      +{\n     -+\tstruct tr2tls_thread_ctx * ctx = pthread_getspecific(tr2tls_key);\n     ++\tstruct tr2tls_thread_ctx *ctx = pthread_getspecific(tr2tls_key);\n      +\n      +\t/*\n      +\t * If the thread-proc did not call trace2_thread_start(), we won't\n     @@ -4046,14 +4064,14 @@\n      +\n      +int tr2tls_is_main_thread(void)\n      +{\n     -+\tstruct tr2tls_thread_ctx * ctx = pthread_getspecific(tr2tls_key);\n     ++\tstruct tr2tls_thread_ctx *ctx = pthread_getspecific(tr2tls_key);\n      +\n      +\treturn ctx == tr2tls_thread_main;\n      +}\n      +\n      +void tr2tls_unset_self(void)\n      +{\n     -+\tstruct tr2tls_thread_ctx * ctx;\n     ++\tstruct tr2tls_thread_ctx *ctx;\n      +\n      +\tctx = tr2tls_get_self();\n      +\n     @@ -4097,7 +4115,7 @@\n      +\tctx = tr2tls_get_self();\n      +\tif (!ctx->nr_open_regions)\n      +\t\treturn 0;\n     -+\t\n     ++\n      +\tus_start = ctx->array_us_start[ctx->nr_open_regions - 1];\n      +\n      +\treturn us - us_start;\n     @@ -4155,6 +4173,8 @@\n      +#ifndef TR2_TLS_H\n      +#define TR2_TLS_H\n      +\n     ++struct strbuf;\n     ++\n      +/*\n      + * Arbitry limit for thread names for column alignment.\n      + */\n  3:  5ac061e14a !  3:  e8b8226640 trace2: collect platform-specific process information\n     @@ -80,7 +80,6 @@\n      +\tpid = pe32.th32ParentProcessID;\n      +\n      +\twhile (find_pid(pid, hSnapshot, &pe32)) {\n     -+\n      +\t\tjw_array_string(jw, pe32.szExeFile);\n      +\n      +\t\tpid = pe32.th32ParentProcessID;\n     @@ -102,8 +101,8 @@\n      +\t\tget_processes(&jw, hSnapshot);\n      +\t\tjw_end(&jw);\n      +\n     -+\t\ttrace2_data_json(\"process\", the_repository,\n     -+\t\t\t\t \"windows/ancestry\", &jw);\n     ++\t\ttrace2_data_json(\"process\", the_repository, \"windows/ancestry\",\n     ++\t\t\t\t &jw);\n      +\n      +\t\tjw_release(&jw);\n      +\t\tCloseHandle(hSnapshot);\n     @@ -122,7 +121,7 @@\n      +{\n      +\tif (IsDebuggerPresent())\n      +\t\ttrace2_data_intmax(\"process\", the_repository,\n     -+\t\t\t   \"windows/debugger_present\", 1);\n     ++\t\t\t\t   \"windows/debugger_present\", 1);\n      +}\n      +\n      +void trace2_collect_process_info(void)\n     @@ -171,7 +170,8 @@\n      +void trace2_collect_process_info(void);\n      +#else\n      +#define trace2_collect_process_info() \\\n     -+\tdo {} while (0)\n     ++\tdo {                          \\\n     ++\t} while (0)\n      +#endif\n      +\n       #endif /* TRACE2_H */\n  4:  f9d689a54b !  4:  0d59a6f2eb trace2:data: add trace2 regions to wt-status\n     @@ -44,7 +44,8 @@\n       void wt_status_print(struct wt_status *s)\n       {\n      +\ttrace2_data_intmax(\"status\", s->repo, \"count/changed\", s->change.nr);\n     -+\ttrace2_data_intmax(\"status\", s->repo, \"count/untracked\", s->untracked.nr);\n     ++\ttrace2_data_intmax(\"status\", s->repo, \"count/untracked\",\n     ++\t\t\t   s->untracked.nr);\n      +\ttrace2_data_intmax(\"status\", s->repo, \"count/ignored\", s->ignored.nr);\n      +\n      +\ttrace2_region_enter(\"status\", \"print\", s->repo);\n  5:  6be101d520 =  5:  2513091f77 trace2:data: add editor/pager child classification\n  6:  b590f19100 =  6:  785f6f866e trace2:data: add trace2 sub-process classification\n  7:  68192b8dfb =  7:  b7e6644f4b trace2:data: add trace2 transport child classification\n  8:  b373ab640b =  8:  52aace533b trace2:data: add trace2 hook classification\n  9:  548ea52742 !  9:  a2bcc6fd28 trace2:data: add trace2 instrumentation to index read/write\n     @@ -18,8 +18,10 @@\n      +\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n      +\t * that is associated with the given \"istate\".\n      +\t */\n     -+\ttrace2_data_intmax(\"index\", the_repository, \"read/version\", istate->version);\n     -+\ttrace2_data_intmax(\"index\", the_repository, \"read/cache_nr\", istate->cache_nr);\n     ++\ttrace2_data_intmax(\"index\", the_repository, \"read/version\",\n     ++\t\t\t   istate->version);\n     ++\ttrace2_data_intmax(\"index\", the_repository, \"read/cache_nr\",\n     ++\t\t\t   istate->cache_nr);\n      +\n       \treturn istate->cache_nr;\n       \n     @@ -46,11 +48,11 @@\n       \n       \tbase_oid_hex = oid_to_hex(&split_index->base_oid);\n       \tbase_path = xstrfmt(\"%s/sharedindex.%s\", gitdir, base_oid_hex);\n     -+\ttrace2_region_enter_printf(\"index\", \"shared/do_read_index\", the_repository,\n     -+\t\t\t\t   \"%s\", base_path);\n     ++\ttrace2_region_enter_printf(\"index\", \"shared/do_read_index\",\n     ++\t\t\t\t   the_repository, \"%s\", base_path);\n       \tret = do_read_index(split_index->base, base_path, 1);\n     -+\ttrace2_region_leave_printf(\"index\", \"shared/do_read_index\", the_repository,\n     -+\t\t\t\t   \"%s\", base_path);\n     ++\ttrace2_region_leave_printf(\"index\", \"shared/do_read_index\",\n     ++\t\t\t\t   the_repository, \"%s\", base_path);\n       \tif (!oideq(&split_index->base_oid, &split_index->base->oid))\n       \t\tdie(_(\"broken index, expect %s in %s, got %s\"),\n       \t\t    base_oid_hex, base_path,\n     @@ -63,8 +65,10 @@\n      +\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n      +\t * that is associated with the given \"istate\".\n      +\t */\n     -+\ttrace2_data_intmax(\"index\", the_repository, \"write/version\", istate->version);\n     -+\ttrace2_data_intmax(\"index\", the_repository, \"write/cache_nr\", istate->cache_nr);\n     ++\ttrace2_data_intmax(\"index\", the_repository, \"write/version\",\n     ++\t\t\t   istate->version);\n     ++\ttrace2_data_intmax(\"index\", the_repository, \"write/cache_nr\",\n     ++\t\t\t   istate->cache_nr);\n      +\n       \treturn 0;\n       }\n 10:  3458917811 ! 10:  d508322638 pack-objects: add trace2 regions\n     @@ -14,6 +14,7 @@\n          3. Write pack-file.\n      \n          Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n     +    Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n      \n       diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\n       --- a/builtin/pack-objects.c\n     @@ -30,7 +31,8 @@\n       \t\t}\n       \t}\n       \n     -+\ttrace2_region_enter(\"pack-objects\", \"enumerate-objects\", the_repository);\n     ++\ttrace2_region_enter(\"pack-objects\", \"enumerate-objects\",\n     ++\t\t\t    the_repository);\n       \tprepare_packing_data(the_repository, &to_pack);\n       \n       \tif (progress)\n     @@ -38,15 +40,18 @@\n       \tif (include_tag && nr_result)\n       \t\tfor_each_ref(add_ref_tag, NULL);\n       \tstop_progress(&progress_state);\n     -+\ttrace2_region_leave(\"pack-objects\", \"enumerate-objects\", the_repository);\n     ++\ttrace2_region_leave(\"pack-objects\", \"enumerate-objects\",\n     ++\t\t\t    the_repository);\n       \n       \tif (non_empty && !nr_result)\n       \t\treturn 0;\n      -\tif (nr_result)\n      +\tif (nr_result) {\n     -+\t\ttrace2_region_enter(\"pack-objects\", \"prepare-pack\", the_repository);\n     ++\t\ttrace2_region_enter(\"pack-objects\", \"prepare-pack\",\n     ++\t\t\t\t    the_repository);\n       \t\tprepare_pack(window, depth);\n     -+\t\ttrace2_region_leave(\"pack-objects\", \"prepare-pack\", the_repository);\n     ++\t\ttrace2_region_leave(\"pack-objects\", \"prepare-pack\",\n     ++\t\t\t\t    the_repository);\n      +\t}\n      +\n      +\ttrace2_region_enter(\"pack-objects\", \"write-pack-file\", the_repository);\n 11:  86feec03e2 = 11:  33e431be88 trace2:data: add subverb to checkout command\n 12:  9abbdf9ccd = 12:  e46e92bc7c trace2:data: add subverb to reset command\n 13:  06ccce9632 ! 13:  0a05e15978 trace2:data: add subverb for rebase\n     @@ -11,16 +11,14 @@\n       \t\tACTION_EDIT_TODO,\n       \t\tACTION_SHOW_CURRENT_PATCH,\n       \t} action = NO_ACTION;\n     -+\tstatic const char *action_names[] = {\n     -+\t\tN_(\"undefined\"),\n     -+\t\tN_(\"continue\"),\n     -+\t\tN_(\"skip\"),\n     -+\t\tN_(\"abort\"),\n     -+\t\tN_(\"quit\"),\n     -+\t\tN_(\"edit_todo\"),\n     -+\t\tN_(\"show_current_patch\"),\n     -+\t\tNULL\n     -+\t};\n     ++\tstatic const char *action_names[] = { N_(\"undefined\"),\n     ++\t\t\t\t\t      N_(\"continue\"),\n     ++\t\t\t\t\t      N_(\"skip\"),\n     ++\t\t\t\t\t      N_(\"abort\"),\n     ++\t\t\t\t\t      N_(\"quit\"),\n     ++\t\t\t\t\t      N_(\"edit_todo\"),\n     ++\t\t\t\t\t      N_(\"show_current_patch\"),\n     ++\t\t\t\t\t      NULL };\n       \tconst char *gpg_sign = NULL;\n       \tstruct string_list exec = STRING_LIST_INIT_NODUP;\n       \tconst char *rebase_merges = NULL;\n 14:  851aa8f34d ! 14:  aeaf990b1b trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh\n     @@ -54,16 +54,15 @@\n      +#include \"exec-cmd.h\"\n      +#include \"config.h\"\n      +\n     -+typedef int (fn_unit_test)(int argc, const char **argv);\n     ++typedef int(fn_unit_test)(int argc, const char **argv);\n      +\n     -+struct unit_test \n     -+{\n     ++struct unit_test {\n      +\tfn_unit_test *ut_fn;\n     -+\tconst char   *ut_name;\n     -+\tconst char   *ut_usage;\n     ++\tconst char *ut_name;\n     ++\tconst char *ut_usage;\n      +};\n      +\n     -+#define MyOk    0\n     ++#define MyOk 0\n      +#define MyError 1\n      +\n      +static int get_i(int *p_value, const char *data)\n     @@ -91,7 +90,8 @@\n      + * [] the \"code\" field in the \"exit\" trace2 event.\n      + * [] the \"code\" field in the \"atexit\" trace2 event.\n      + * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n     -+ * [] \"def_param\" events for all of the \"interesting\" pre-defined config settings.\n     ++ * [] \"def_param\" events for all of the \"interesting\" pre-defined\n     ++ * config settings.\n      + */\n      +static int ut_001return(int argc, const char **argv)\n      +{\n     @@ -110,7 +110,8 @@\n      + * [] the \"code\" field in the \"exit\" trace2 event.\n      + * [] the \"code\" field in the \"atexit\" trace2 event.\n      + * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n     -+ * [] \"def_param\" events for all of the \"interesting\" pre-defined config settings.\n     ++ * [] \"def_param\" events for all of the \"interesting\" pre-defined\n     ++ * config settings.\n      + */\n      +static int ut_002exit(int argc, const char **argv)\n      +{\n     @@ -213,7 +214,7 @@\n      + *\n      + * Test harness can confirm (on platforms with a real exec() function):\n      + * [] TODO talk about process replacement and how it affects SID.\n     -+ */ \n     ++ */\n      +static int ut_005exec(int argc, const char **argv)\n      +{\n      +\tint result;\n     @@ -234,8 +235,7 @@\n      +\t\tdie(\"%s\", usage_error);\n      +\n      +\twhile (argc) {\n     -+\t\tif (!argv[0] || !*argv[0] ||\n     -+\t\t    !argv[1] || !*argv[1] ||\n     ++\t\tif (!argv[0] || !*argv[0] || !argv[1] || !*argv[1] ||\n      +\t\t    !argv[2] || !*argv[2])\n      +\t\t\tdie(\"%s\", usage_error);\n      +\n     @@ -252,7 +252,7 @@\n      + *     test-tool trace2 <ut_name_1> <ut_usage_1>\n      + *     test-tool trace2 <ut_name_2> <ut_usage_2>\n      + *     ...\n     -+ */ \n     ++ */\n      +#define USAGE_PREFIX \"test-tool trace2\"\n      +\n      +static struct unit_test ut_table[] = {\n     @@ -275,9 +275,10 @@\n      +\tstruct unit_test *ut_k;\n      +\n      +\tfprintf(stderr, \"usage:\\n\");\n     -+\tfor_each_ut(k, ut_k) {\n     -+\t\tfprintf(stderr, \"\\t%s %s %s\\n\",\n     -+\t\t\tUSAGE_PREFIX, ut_k->ut_name, ut_k->ut_usage);\n     ++\tfor_each_ut(k, ut_k)\n     ++\t{\n     ++\t\tfprintf(stderr, \"\\t%s %s %s\\n\", USAGE_PREFIX, ut_k->ut_name,\n     ++\t\t\tut_k->ut_usage);\n      +\t}\n      +\n      +\treturn 129;\n     @@ -307,11 +308,12 @@\n      +\tint k;\n      +\tstruct unit_test *ut_k;\n      +\n     -+\targc--;\t/* skip over \"trace2\" arg */\n     ++\targc--; /* skip over \"trace2\" arg */\n      +\targv++;\n      +\n      +\tif (argc) {\n     -+\t\tfor_each_ut(k, ut_k) {\n     ++\t\tfor_each_ut(k, ut_k)\n     ++\t\t{\n      +\t\t\tif (!strcmp(argv[0], ut_k->ut_name))\n      +\t\t\t\treturn ut_k->ut_fn(argc - 1, argv + 1);\n      +\t\t}\n     @@ -319,7 +321,6 @@\n      +\n      +\treturn print_usage();\n      +}\n     -+\n      \n       diff --git a/t/t0210-trace2-normal.sh b/t/t0210-trace2-normal.sh\n       new file mode 100755\n     @@ -337,8 +338,6 @@\n      +TTDIR=\"$GIT_BUILD_DIR/t/helper/\" && export TTDIR\n      +PATH=\"$TTDIR:$PATH\" && export PATH\n      +\n     -+TT=\"test-tool\" && export TT\n     -+\n      +# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe\n      +# Warning: to do the actual diff/comparison, so the HEREDOCs here\n      +# Warning: only cover our actual calls to test-tool and/or git.\n     @@ -384,9 +383,7 @@\n      +\n      +test_expect_success 'normal stream, return code 0' '\n      +\ttest_when_finished \"rm trace.normal actual expect\" &&\n     -+\tGIT_TR2=\"$(pwd)/trace.normal\" && export GIT_TR2 &&\n     -+\t$TT trace2 001return 0 &&\n     -+\tunset GIT_TR2 &&\n     ++\tGIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 001return 0 &&\n      +\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n      +\tcat >expect <<-EOF &&\n      +\t\tversion $V\n     @@ -400,9 +397,7 @@\n      +\n      +test_expect_success 'normal stream, return code 1' '\n      +\ttest_when_finished \"rm trace.normal actual expect\" &&\n     -+\tGIT_TR2=\"$(pwd)/trace.normal\" && export GIT_TR2 &&\n     -+\ttest_must_fail $TT trace2 001return 1 &&\n     -+\tunset GIT_TR2 &&\n     ++\ttest_must_fail env GIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 001return 1 &&\n      +\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n      +\tcat >expect <<-EOF &&\n      +\t\tversion $V\n     @@ -420,9 +415,7 @@\n      +\n      +test_expect_success 'normal stream, exit code 0' '\n      +\ttest_when_finished \"rm trace.normal actual expect\" &&\n     -+\tGIT_TR2=\"$(pwd)/trace.normal\" && export GIT_TR2 &&\n     -+\t$TT trace2 002exit 0 &&\n     -+\tunset GIT_TR2 &&\n     ++\tGIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 002exit 0 &&\n      +\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n      +\tcat >expect <<-EOF &&\n      +\t\tversion $V\n     @@ -436,9 +429,7 @@\n      +\n      +test_expect_success 'normal stream, exit code 1' '\n      +\ttest_when_finished \"rm trace.normal actual expect\" &&\n     -+\tGIT_TR2=\"$(pwd)/trace.normal\" && export GIT_TR2 &&\n     -+\ttest_must_fail $TT trace2 002exit 1 &&\n     -+\tunset GIT_TR2 &&\n     ++\ttest_must_fail env GIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 002exit 1 &&\n      +\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n      +\tcat >expect <<-EOF &&\n      +\t\tversion $V\n     @@ -456,9 +447,7 @@\n      +\n      +test_expect_success 'normal stream, error event' '\n      +\ttest_when_finished \"rm trace.normal actual expect\" &&\n     -+\tGIT_TR2=\"$(pwd)/trace.normal\" && export GIT_TR2 &&\n     -+\t$TT trace2 003error \"hello world\" \"this is a test\" &&\n     -+\tunset GIT_TR2 &&\n     ++\tGIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 003error \"hello world\" \"this is a test\" &&\n      +\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n      +\tcat >expect <<-EOF &&\n      +\t\tversion $V\n     @@ -544,8 +533,6 @@\n      +TTDIR=\"$GIT_BUILD_DIR/t/helper/\" && export TTDIR\n      +PATH=\"$TTDIR:$PATH\" && export PATH\n      +\n     -+TT=\"test-tool\" && export TT\n     -+\n      +# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe\n      +# Warning: to do the actual diff/comparison, so the HEREDOCs here\n      +# Warning: only cover our actual calls to test-tool and/or git.\n     @@ -583,9 +570,7 @@\n      +\n      +test_expect_success 'perf stream, return code 0' '\n      +\ttest_when_finished \"rm trace.perf actual expect\" &&\n     -+\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" && export GIT_TR2_PERF &&\n     -+\t$TT trace2 001return 0 &&\n     -+\tunset GIT_TR2_PERF &&\n     ++\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" test-tool trace2 001return 0 &&\n      +\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n      +\tcat >expect <<-EOF &&\n      +\t\td0|main|version|||||$V\n     @@ -599,9 +584,7 @@\n      +\n      +test_expect_success 'perf stream, return code 1' '\n      +\ttest_when_finished \"rm trace.perf actual expect\" &&\n     -+\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" && export GIT_TR2_PERF &&\n     -+\ttest_must_fail $TT trace2 001return 1 &&\n     -+\tunset GIT_TR2_PERF &&\n     ++\ttest_must_fail env GIT_TR2_PERF=\"$(pwd)/trace.perf\" test-tool trace2 001return 1 &&\n      +\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n      +\tcat >expect <<-EOF &&\n      +\t\td0|main|version|||||$V\n     @@ -619,9 +602,7 @@\n      +\n      +test_expect_success 'perf stream, error event' '\n      +\ttest_when_finished \"rm trace.perf actual expect\" &&\n     -+\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" && export GIT_TR2_PERF &&\n     -+\t$TT trace2 003error \"hello world\" \"this is a test\" &&\n     -+\tunset GIT_TR2_PERF &&\n     ++\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" test-tool trace2 003error \"hello world\" \"this is a test\" &&\n      +\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n      +\tcat >expect <<-EOF &&\n      +\t\td0|main|version|||||$V\n     @@ -667,19 +648,17 @@\n      +\n      +test_expect_success 'perf stream, child processes' '\n      +\ttest_when_finished \"rm trace.perf actual expect\" &&\n     -+\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" && export GIT_TR2_PERF &&\n     -+\t$TT trace2 004child $TT trace2 004child $TT trace2 001return 0 &&\n     -+\tunset GIT_TR2_PERF &&\n     ++\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" test-tool trace2 004child test-tool trace2 004child test-tool trace2 001return 0 &&\n      +\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n      +\tcat >expect <<-EOF &&\n      +\t\td0|main|version|||||$V\n     -+\t\td0|main|start|||||_EXE_ trace2 004child $TT trace2 004child $TT trace2 001return 0\n     ++\t\td0|main|start|||||_EXE_ trace2 004child test-tool trace2 004child test-tool trace2 001return 0\n      +\t\td0|main|cmd_verb|||||trace2 (trace2)\n     -+\t\td0|main|child_start||_T_ABS_|||[ch0] class:? argv: $TT trace2 004child $TT trace2 001return 0\n     ++\t\td0|main|child_start||_T_ABS_|||[ch0] class:? argv: test-tool trace2 004child test-tool trace2 001return 0\n      +\t\td1|main|version|||||$V\n     -+\t\td1|main|start|||||_EXE_ trace2 004child $TT trace2 001return 0\n     ++\t\td1|main|start|||||_EXE_ trace2 004child test-tool trace2 001return 0\n      +\t\td1|main|cmd_verb|||||trace2 (trace2/trace2)\n     -+\t\td1|main|child_start||_T_ABS_|||[ch0] class:? argv: $TT trace2 001return 0\n     ++\t\td1|main|child_start||_T_ABS_|||[ch0] class:? argv: test-tool trace2 001return 0\n      +\t\td2|main|version|||||$V\n      +\t\td2|main|start|||||_EXE_ trace2 001return 0\n      +\t\td2|main|cmd_verb|||||trace2 (trace2/trace2/trace2)\n     @@ -795,8 +774,6 @@\n      +TTDIR=\"$GIT_BUILD_DIR/t/helper/\" && export TTDIR\n      +PATH=\"$TTDIR:$PATH\" && export PATH\n      +\n     -+TT=\"test-tool\" && export TT\n     -+\n      +# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe\n      +# Warning: to do the actual diff/comparison, so the HEREDOCs here\n      +# Warning: only cover our actual calls to test-tool and/or git.\n     @@ -828,9 +805,7 @@\n      +\n      +test_expect_success 'event stream, error event' '\n      +\ttest_when_finished \"rm trace.event actual expect\" &&\n     -+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" && export GIT_TR2_EVENT &&\n     -+\t$TT trace2 003error \"hello world\" \"this is a test\" &&\n     -+\tunset GIT_TR2_EVENT &&\n     ++\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 003error \"hello world\" \"this is a test\" &&\n      +\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n      +\tsed -e \"s/^|//\" >expect <<-EOF &&\n      +\t|VAR1 = {\n     @@ -866,10 +841,8 @@\n      +#    P3:      |--- TT trace2 001return 0\n      +\n      +test_expect_success 'event stream, return code 0' '\n     -+#\ttest_when_finished \"rm trace.event actual expect\" &&\n     -+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" && export GIT_TR2_EVENT &&\n     -+\t$TT trace2 004child $TT trace2 004child $TT trace2 001return 0 &&\n     -+\tunset GIT_TR2_EVENT &&\n     ++\ttest_when_finished \"rm trace.event actual expect\" &&\n     ++\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 004child test-tool trace2 004child test-tool trace2 001return 0 &&\n      +\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n      +\tsed -e \"s/^|//\" >expect <<-EOF &&\n      +\t|VAR1 = {\n     @@ -878,10 +851,10 @@\n      +\t|      \"_EXE_\",\n      +\t|      \"trace2\",\n      +\t|      \"004child\",\n     -+\t|      \"$TT\",\n     ++\t|      \"test-tool\",\n      +\t|      \"trace2\",\n      +\t|      \"004child\",\n     -+\t|      \"$TT\",\n     ++\t|      \"test-tool\",\n      +\t|      \"trace2\",\n      +\t|      \"001return\",\n      +\t|      \"0\"\n     @@ -892,7 +865,7 @@\n      +\t|          \"_EXE_\",\n      +\t|          \"trace2\",\n      +\t|          \"004child\",\n     -+\t|          \"$TT\",\n     ++\t|          \"test-tool\",\n      +\t|          \"trace2\",\n      +\t|          \"001return\",\n      +\t|          \"0\"\n     @@ -912,7 +885,7 @@\n      +\t|      \"_EXE_\",\n      +\t|      \"trace2\",\n      +\t|      \"004child\",\n     -+\t|      \"$TT\",\n     ++\t|      \"test-tool\",\n      +\t|      \"trace2\",\n      +\t|      \"001return\",\n      +\t|      \"0\"\n     @@ -958,11 +931,7 @@\n      +\ttest_when_finished \"rm trace.event actual expect\" &&\n      +\tgit config --local t0212.abc 1 &&\n      +\tgit config --local t0212.def \"hello world\" &&\n     -+\t# delete events generated by the above config commands\n     -+\trm trace.event &&\n     -+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" && export GIT_TR2_EVENT &&\n     -+\tGIT_TR2_CONFIG_PARAMS=\"t0212.*\" $TT trace2 001return 0 &&\n     -+\tunset GIT_TR2_EVENT &&\n     ++\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" GIT_TR2_CONFIG_PARAMS=\"t0212.*\" test-tool trace2 001return 0 &&\n      +\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n      +\tsed -e \"s/^|//\" >expect <<-EOF &&\n      +\t|VAR1 = {\n     @@ -995,9 +964,7 @@\n      +\n      +test_expect_success 'basic trace2_data' '\n      +\ttest_when_finished \"rm trace.event actual expect\" &&\n     -+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" && export GIT_TR2_EVENT &&\n     -+\t$TT trace2 006data test_category k1 v1 test_category k2 v2 &&\n     -+\tunset GIT_TR2_EVENT &&\n     ++\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 006data test_category k1 v1 test_category k2 v2 &&\n      +\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n      +\tsed -e \"s/^|//\" >expect <<-EOF &&\n      +\t|VAR1 = {\n\n-- \ngitgitgadget\n"},{"id":"368176","messageId":"e46e92bc7cfea388e0d15dfca7831e47b92a5854.1548877863.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 12/14] trace2:data: add subverb to reset command","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T19:51:14Z","receivedAt":"2019-01-30T19:51:23Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/reset.c | 6 ++++++\n 1 file changed, 6 insertions(+)\n\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex 59898c972e..b65b4a66db 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -340,6 +340,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \tif (patch_mode) {\n \t\tif (reset_type != NONE)\n \t\t\tdie(_(\"--patch is incompatible with --{hard,mixed,soft}\"));\n+\t\ttrace2_cmd_subverb(\"patch-interactive\");\n \t\treturn run_add_interactive(rev, \"--patch=reset\", &pathspec);\n \t}\n \n@@ -356,6 +357,11 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \tif (reset_type == NONE)\n \t\treset_type = MIXED; /* by default */\n \n+\tif (pathspec.nr)\n+\t\ttrace2_cmd_subverb(\"path\");\n+\telse\n+\t\ttrace2_cmd_subverb(reset_type_names[reset_type]);\n+\n \tif (reset_type != SOFT && (reset_type != MIXED || get_git_work_tree()))\n \t\tsetup_work_tree();\n \n-- \ngitgitgadget\n\n"},{"id":"368175","messageId":"aeaf990b1b67828293708c194f58b075aa6f2a44.1548877863.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 14/14] trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T19:51:16Z","receivedAt":"2019-01-30T19:51:24Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate unit tests for Trace2.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                  |   1 +\n t/helper/test-tool.c      |   1 +\n t/helper/test-tool.h      |   1 +\n t/helper/test-trace2.c    | 274 ++++++++++++++++++++++++++++++++++++++\n t/t0210-trace2-normal.sh  | 135 +++++++++++++++++++\n t/t0210/scrub_normal.perl |  48 +++++++\n t/t0211-trace2-perf.sh    | 153 +++++++++++++++++++++\n t/t0211/scrub_perf.perl   |  76 +++++++++++\n t/t0212-trace2-event.sh   | 234 ++++++++++++++++++++++++++++++++\n t/t0212/parse_events.perl | 251 ++++++++++++++++++++++++++++++++++\n 10 files changed, 1174 insertions(+)\n create mode 100644 t/helper/test-trace2.c\n create mode 100755 t/t0210-trace2-normal.sh\n create mode 100644 t/t0210/scrub_normal.perl\n create mode 100755 t/t0211-trace2-perf.sh\n create mode 100644 t/t0211/scrub_perf.perl\n create mode 100755 t/t0212-trace2-event.sh\n create mode 100644 t/t0212/parse_events.perl\n\ndiff --git a/Makefile b/Makefile\nindex f929640766..f455095401 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -762,6 +762,7 @@ TEST_BUILTINS_OBJS += test-string-list.o\n TEST_BUILTINS_OBJS += test-submodule-config.o\n TEST_BUILTINS_OBJS += test-submodule-nested-repo-config.o\n TEST_BUILTINS_OBJS += test-subprocess.o\n+TEST_BUILTINS_OBJS += test-trace2.o\n TEST_BUILTINS_OBJS += test-urlmatch-normalization.o\n TEST_BUILTINS_OBJS += test-wildmatch.o\n TEST_BUILTINS_OBJS += test-windows-named-pipe.o\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 0a704fe8d2..f594cb6785 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -51,6 +51,7 @@ static struct test_cmd cmds[] = {\n \t{ \"submodule-config\", cmd__submodule_config },\n \t{ \"submodule-nested-repo-config\", cmd__submodule_nested_repo_config },\n \t{ \"subprocess\", cmd__subprocess },\n+\t{ \"trace2\", cmd__trace2 },\n \t{ \"urlmatch-normalization\", cmd__urlmatch_normalization },\n \t{ \"wildmatch\", cmd__wildmatch },\n #ifdef GIT_WINDOWS_NATIVE\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex ca5c88edb2..61d2938ce5 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -46,6 +46,7 @@ int cmd__string_list(int argc, const char **argv);\n int cmd__submodule_config(int argc, const char **argv);\n int cmd__submodule_nested_repo_config(int argc, const char **argv);\n int cmd__subprocess(int argc, const char **argv);\n+int cmd__trace2(int argc, const char **argv);\n int cmd__urlmatch_normalization(int argc, const char **argv);\n int cmd__wildmatch(int argc, const char **argv);\n #ifdef GIT_WINDOWS_NATIVE\ndiff --git a/t/helper/test-trace2.c b/t/helper/test-trace2.c\nnew file mode 100644\nindex 0000000000..37750f5256\n--- /dev/null\n+++ b/t/helper/test-trace2.c\n@@ -0,0 +1,274 @@\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"argv-array.h\"\n+#include \"run-command.h\"\n+#include \"exec-cmd.h\"\n+#include \"config.h\"\n+\n+typedef int(fn_unit_test)(int argc, const char **argv);\n+\n+struct unit_test {\n+\tfn_unit_test *ut_fn;\n+\tconst char *ut_name;\n+\tconst char *ut_usage;\n+};\n+\n+#define MyOk 0\n+#define MyError 1\n+\n+static int get_i(int *p_value, const char *data)\n+{\n+\tchar *endptr;\n+\n+\tif (!data || !*data)\n+\t\treturn MyError;\n+\n+\t*p_value = strtol(data, &endptr, 10);\n+\tif (*endptr || errno == ERANGE)\n+\t\treturn MyError;\n+\n+\treturn MyOk;\n+}\n+\n+/*\n+ * Cause process to exit with the requested value via \"return\".\n+ *\n+ * Rely on test-tool.c:cmd_main() to call trace2_cmd_exit()\n+ * with our result.\n+ *\n+ * Test harness can confirm:\n+ * [] the process-exit value.\n+ * [] the \"code\" field in the \"exit\" trace2 event.\n+ * [] the \"code\" field in the \"atexit\" trace2 event.\n+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n+ * [] \"def_param\" events for all of the \"interesting\" pre-defined\n+ * config settings.\n+ */\n+static int ut_001return(int argc, const char **argv)\n+{\n+\tint rc;\n+\n+\tif (get_i(&rc, argv[0]))\n+\t\tdie(\"expect <exit_code>\");\n+\n+\treturn rc;\n+}\n+\n+/*\n+ * Cause the process to exit with the requested value via \"exit()\".\n+ *\n+ * Test harness can confirm:\n+ * [] the \"code\" field in the \"exit\" trace2 event.\n+ * [] the \"code\" field in the \"atexit\" trace2 event.\n+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n+ * [] \"def_param\" events for all of the \"interesting\" pre-defined\n+ * config settings.\n+ */\n+static int ut_002exit(int argc, const char **argv)\n+{\n+\tint rc;\n+\n+\tif (get_i(&rc, argv[0]))\n+\t\tdie(\"expect <exit_code>\");\n+\n+\texit(rc);\n+}\n+\n+/*\n+ * Send an \"error\" event with each value in argv.  Normally, git only issues\n+ * a single \"error\" event immediately before issuing an \"exit\" event (such\n+ * as in die() or BUG()), but multiple \"error\" events are allowed.\n+ *\n+ * Test harness can confirm:\n+ * [] a trace2 \"error\" event for each value in argv.\n+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n+ * [] (optional) the file:line in the \"exit\" event refers to this function.\n+ */\n+static int ut_003error(int argc, const char **argv)\n+{\n+\tint k;\n+\n+\tif (!argv[0] || !*argv[0])\n+\t\tdie(\"expect <error_message>\");\n+\n+\tfor (k = 0; k < argc; k++)\n+\t\terror(\"%s\", argv[k]);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Run a child process and wait for it to finish and exit with its return code.\n+ * test-tool trace2 004child [<child-command-line>]\n+ *\n+ * For example:\n+ * test-tool trace2 004child git version\n+ * test-tool trace2 004child test-tool trace2 001return 0\n+ * test-tool trace2 004child test-tool trace2 004child test-tool trace2 004child\n+ * test-tool trace2 004child git -c alias.xyz=version xyz\n+ *\n+ * Test harness can confirm:\n+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n+ * [] that the outer process has a single component SID (or depth \"d0\" in\n+ *    the PERF stream).\n+ * [] that \"child_start\" and \"child_exit\" events are generated for the child.\n+ * [] if the child process is an instrumented executable:\n+ *    [] that \"version\", \"start\", ..., \"exit\", and \"atexit\" events are\n+ *       generated by the child process.\n+ *    [] that the child process events have a multiple component SID (or\n+ *       depth \"dN+1\" in the PERF stream).\n+ * [] that the child exit code is propagated to the parent process \"exit\"\n+ *    and \"atexit\" events..\n+ * [] (optional) that the \"t_abs\" field in the child process \"atexit\" event\n+ *    is less than the \"t_rel\" field in the \"child_exit\" event of the parent\n+ *    process.\n+ * [] if the child process is like the alias example above,\n+ *    [] (optional) the child process attempts to run \"git-xyx\" as a dashed\n+ *       command.\n+ *    [] the child process emits an \"alias\" event with \"xyz\" => \"version\"\n+ *    [] the child process runs \"git version\" as a child process.\n+ *    [] the child process has a 3 component SID (or depth \"d2\" in the PERF\n+ *       stream).\n+ */\n+static int ut_004child(int argc, const char **argv)\n+{\n+\tint result;\n+\n+\t/*\n+\t * Allow empty <child_command_line> so we can do arbitrarily deep\n+\t * command nesting and let the last one be null.\n+\t */\n+\tif (!argc)\n+\t\treturn 0;\n+\n+\tresult = run_command_v_opt(argv, 0);\n+\texit(result);\n+}\n+\n+/*\n+ * Exec a git command.  This may either create a child process (Windows)\n+ * or replace the existing process.\n+ * test-tool trace2 005exec <git_command_args>\n+ *\n+ * For example:\n+ * test-tool trace2 005exec version\n+ *\n+ * Test harness can confirm (on Windows):\n+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n+ * [] that the outer process has a single component SID (or depth \"d0\" in\n+ *    the PERF stream).\n+ * [] that \"exec\" and \"exec_result\" events are generated for the child\n+ *    process (since the Windows compatibility layer fakes an exec() with\n+ *    a CreateProcess(), WaitForSingleObject(), and exit()).\n+ * [] that the child process has multiple component SID (or depth \"dN+1\"\n+ *    in the PERF stream).\n+ *\n+ * Test harness can confirm (on platforms with a real exec() function):\n+ * [] TODO talk about process replacement and how it affects SID.\n+ */\n+static int ut_005exec(int argc, const char **argv)\n+{\n+\tint result;\n+\n+\tif (!argc)\n+\t\treturn 0;\n+\n+\tresult = execv_git_cmd(argv);\n+\treturn result;\n+}\n+\n+static int ut_006data(int argc, const char **argv)\n+{\n+\tconst char *usage_error =\n+\t\t\"expect <cat0> <k0> <v0> [<cat1> <k1> <v1> [...]]\";\n+\n+\tif (argc % 3 != 0)\n+\t\tdie(\"%s\", usage_error);\n+\n+\twhile (argc) {\n+\t\tif (!argv[0] || !*argv[0] || !argv[1] || !*argv[1] ||\n+\t\t    !argv[2] || !*argv[2])\n+\t\t\tdie(\"%s\", usage_error);\n+\n+\t\ttrace2_data_string(argv[0], the_repository, argv[1], argv[2]);\n+\t\targv += 3;\n+\t\targc -= 3;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Usage:\n+ *     test-tool trace2 <ut_name_1> <ut_usage_1>\n+ *     test-tool trace2 <ut_name_2> <ut_usage_2>\n+ *     ...\n+ */\n+#define USAGE_PREFIX \"test-tool trace2\"\n+\n+static struct unit_test ut_table[] = {\n+\t{ ut_001return,   \"001return\", \"<exit_code>\" },\n+\t{ ut_002exit,     \"002exit\",   \"<exit_code>\" },\n+\t{ ut_003error,    \"003error\",  \"<error_message>+\" },\n+\t{ ut_004child,    \"004child\",  \"[<child_command_line>]\" },\n+\t{ ut_005exec,     \"005exec\",   \"<git_command_args>\" },\n+\t{ ut_006data,     \"006data\",   \"[<category> <key> <value>]+\" },\n+};\n+\n+#define for_each_ut(k, ut_k)\t\t\t\\\n+\tfor (k = 0, ut_k = &ut_table[k];\t\\\n+\t     k < ARRAY_SIZE(ut_table);\t\t\\\n+\t     k++, ut_k = &ut_table[k])\n+\n+static int print_usage(void)\n+{\n+\tint k;\n+\tstruct unit_test *ut_k;\n+\n+\tfprintf(stderr, \"usage:\\n\");\n+\tfor_each_ut(k, ut_k)\n+\t{\n+\t\tfprintf(stderr, \"\\t%s %s %s\\n\", USAGE_PREFIX, ut_k->ut_name,\n+\t\t\tut_k->ut_usage);\n+\t}\n+\n+\treturn 129;\n+}\n+\n+/*\n+ * Issue various trace2 events for testing.\n+ *\n+ * We assume that these trace2 routines has already been called:\n+ *    [] trace2_initialize()      [common-main.c:main()]\n+ *    [] trace2_cmd_start()       [common-main.c:main()]\n+ *    [] trace2_cmd_verb()        [test-tool.c:cmd_main()]\n+ *    [] tracd2_cmd_list_config() [test-tool.c:cmd_main()]\n+ * So that:\n+ *    [] the various trace2 streams are open.\n+ *    [] the process SID has been created.\n+ *    [] the \"version\" event has been generated.\n+ *    [] the \"start\" event has been generated.\n+ *    [] the \"verb\" event has been generated.\n+ *    [] this writes various \"def_param\" events for interesting config values.\n+ *\n+ * We further assume that if we return (rather than exit()), trace2_cmd_exit()\n+ * will be called by test-tool.c:cmd_main().\n+ */\n+int cmd__trace2(int argc, const char **argv)\n+{\n+\tint k;\n+\tstruct unit_test *ut_k;\n+\n+\targc--; /* skip over \"trace2\" arg */\n+\targv++;\n+\n+\tif (argc) {\n+\t\tfor_each_ut(k, ut_k)\n+\t\t{\n+\t\t\tif (!strcmp(argv[0], ut_k->ut_name))\n+\t\t\t\treturn ut_k->ut_fn(argc - 1, argv + 1);\n+\t\t}\n+\t}\n+\n+\treturn print_usage();\n+}\ndiff --git a/t/t0210-trace2-normal.sh b/t/t0210-trace2-normal.sh\nnew file mode 100755\nindex 0000000000..ab4636fe5a\n--- /dev/null\n+++ b/t/t0210-trace2-normal.sh\n@@ -0,0 +1,135 @@\n+#!/bin/sh\n+\n+test_description='test trace2 facility (normal target)'\n+. ./test-lib.sh\n+\n+# Add t/helper directory to PATH so that we can use a relative\n+# path to run nested instances of test-tool.exe (see 004child).\n+# This helps with HEREDOC comparisons later.\n+TTDIR=\"$GIT_BUILD_DIR/t/helper/\" && export TTDIR\n+PATH=\"$TTDIR:$PATH\" && export PATH\n+\n+# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe\n+# Warning: to do the actual diff/comparison, so the HEREDOCs here\n+# Warning: only cover our actual calls to test-tool and/or git.\n+# Warning: So you may see extra lines in artifact files when\n+# Warning: interactively debugging.\n+\n+# Turn off any inherited trace2 settings for this test.\n+unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT\n+unset GIT_TR2_BRIEF\n+unset GIT_TR2_CONFIG_PARAMS\n+\n+V=$(git version | sed -e 's/^git version //') && export V\n+\n+# There are multiple trace2 targets: normal, perf, and event.\n+# Trace2 events will/can be written to each active target (subject\n+# to whatever filtering that target decides to do).\n+# This script tests the normal target in isolation.\n+#\n+# Defer setting GIT_TR2 until the actual command line we want to test\n+# because hidden git and test-tool commands run by the test harness\n+# can contaminate our output.\n+\n+# Enable \"brief\" feature which turns off \"<clock> <file>:<line> \" prefix.\n+GIT_TR2_BRIEF=1 && export GIT_TR2_BRIEF\n+\n+# Basic tests of the trace2 normal stream.  Since this stream is used\n+# primarily with printf-style debugging/tracing, we do limited testing\n+# here.\n+#\n+# We do confirm the following API features:\n+# [] the 'version <v>' event\n+# [] the 'start <argv>' event\n+# [] the 'cmd_verb <verb>' event\n+# [] the 'exit <time> code:<code>' event\n+# [] the 'atexit <time> code:<code>' event\n+#\n+# Fields of the form _FIELD_ are tokens that have been replaced (such\n+# as the elapsed time).\n+\n+# Verb 001return\n+#\n+# Implicit return from cmd_<verb> function propagates <code>.\n+\n+test_expect_success 'normal stream, return code 0' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\tGIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 001return 0\n+\t\tcmd_verb trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:0\n+\t\tatexit elapsed:_TIME_ code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'normal stream, return code 1' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\ttest_must_fail env GIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 001return 1 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 001return 1\n+\t\tcmd_verb trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:1\n+\t\tatexit elapsed:_TIME_ code:1\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 002exit\n+#\n+# Explicit exit(code) from within cmd_<verb> propagates <code>.\n+\n+test_expect_success 'normal stream, exit code 0' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\tGIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 002exit 0 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 002exit 0\n+\t\tcmd_verb trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:0\n+\t\tatexit elapsed:_TIME_ code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'normal stream, exit code 1' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\ttest_must_fail env GIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 002exit 1 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 002exit 1\n+\t\tcmd_verb trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:1\n+\t\tatexit elapsed:_TIME_ code:1\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 003error\n+#\n+# To the above, add multiple 'error <msg>' events\n+\n+test_expect_success 'normal stream, error event' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\tGIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 003error \"hello world\" \"this is a test\" &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 003error '\\''hello world'\\'' '\\''this is a test'\\''\n+\t\tcmd_verb trace2 (trace2)\n+\t\terror hello world\n+\t\terror this is a test\n+\t\texit elapsed:_TIME_ code:0\n+\t\tatexit elapsed:_TIME_ code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t0210/scrub_normal.perl b/t/t0210/scrub_normal.perl\nnew file mode 100644\nindex 0000000000..c65d1a815e\n--- /dev/null\n+++ b/t/t0210/scrub_normal.perl\n@@ -0,0 +1,48 @@\n+#!/usr/bin/perl\n+#\n+# Scrub the variable fields from the normal trace2 output to\n+# make testing easier.\n+\n+use strict;\n+use warnings;\n+\n+my $float = '[0-9]*\\.[0-9]+([eE][-+]?[0-9]+)?';\n+\n+# This code assumes that the trace2 data was written with bare\n+# turned on (which omits the \"<clock> <file>:<line>\" prefix.\n+\n+while (<>) {\n+    # Various messages include an elapsed time in the middle\n+    # of the message.  Replace the time with a placeholder to\n+    # simplify our HEREDOC in the test script.\n+    s/elapsed:$float/elapsed:_TIME_/g;\n+\n+    my $line = $_;\n+\n+    # we expect:\n+    #    start <argv0> [<argv1> [<argv2> [...]]]\n+    #\n+    # where argv0 might be a relative or absolute path, with\n+    # or without quotes, and platform dependent.  Replace argv0\n+    # with a token for HEREDOC matching in the test script.\n+\n+    if ($line =~ m/^start/) {\n+\t$line =~ /^start\\s+(.*)/;\n+\tmy $argv = $1;\n+\t$argv =~ m/(\\'[^\\']*\\'|[^ ]+)\\s+(.*)/;\n+\tmy $argv_0 = $1;\n+\tmy $argv_rest = $2;\n+\n+\tprint \"start _EXE_ $argv_rest\\n\";\n+    }\n+    elsif ($line =~ m/^cmd_path/) {\n+\t# Likewise, the 'cmd_path' message breaks out argv[0].\n+\t#\n+\t# This line is only emitted when RUNTIME_PREFIX is defined,\n+\t# so just omit it for testing purposes.\n+\t# print \"cmd_path _EXE_\\n\";\n+    }\n+    else {\n+\tprint \"$line\";\n+    }\n+}\ndiff --git a/t/t0211-trace2-perf.sh b/t/t0211-trace2-perf.sh\nnew file mode 100755\nindex 0000000000..91ca1b6d66\n--- /dev/null\n+++ b/t/t0211-trace2-perf.sh\n@@ -0,0 +1,153 @@\n+#!/bin/sh\n+\n+test_description='test trace2 facility (perf target)'\n+. ./test-lib.sh\n+\n+# Add t/helper directory to PATH so that we can use a relative\n+# path to run nested instances of test-tool.exe (see 004child).\n+# This helps with HEREDOC comparisons later.\n+TTDIR=\"$GIT_BUILD_DIR/t/helper/\" && export TTDIR\n+PATH=\"$TTDIR:$PATH\" && export PATH\n+\n+# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe\n+# Warning: to do the actual diff/comparison, so the HEREDOCs here\n+# Warning: only cover our actual calls to test-tool and/or git.\n+# Warning: So you may see extra lines in artifact files when\n+# Warning: interactively debugging.\n+\n+# Turn off any inherited trace2 settings for this test.\n+unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT\n+unset GIT_TR2_PERF_BRIEF\n+unset GIT_TR2_CONFIG_PARAMS\n+\n+V=$(git version | sed -e 's/^git version //') && export V\n+\n+# There are multiple trace2 targets: normal, perf, and event.\n+# Trace2 events will/can be written to each active target (subject\n+# to whatever filtering that target decides to do).\n+# Test each target independently.\n+#\n+# Defer setting GIT_TR2_PERF until the actual command we want to\n+# test because hidden git and test-tool commands in the test\n+# harness can contaminate our output.\n+\n+# Enable \"brief\" feature which turns off the prefix:\n+#     \"<clock> <file>:<line> | <nr_parents> | \"\n+GIT_TR2_PERF_BRIEF=1 && export GIT_TR2_PERF_BRIEF\n+\n+# Repeat some of the t0210 tests using the perf target stream instead of\n+# the normal stream.\n+#\n+# Tokens here of the form _FIELD_ have been replaced in the observed output.\n+\n+# Verb 001return\n+#\n+# Implicit return from cmd_<verb> function propagates <code>.\n+\n+test_expect_success 'perf stream, return code 0' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 001return 0\n+\t\td0|main|cmd_verb|||||trace2 (trace2)\n+\t\td0|main|exit||_T_ABS_|||code:0\n+\t\td0|main|atexit||_T_ABS_|||code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'perf stream, return code 1' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\ttest_must_fail env GIT_TR2_PERF=\"$(pwd)/trace.perf\" test-tool trace2 001return 1 &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 001return 1\n+\t\td0|main|cmd_verb|||||trace2 (trace2)\n+\t\td0|main|exit||_T_ABS_|||code:1\n+\t\td0|main|atexit||_T_ABS_|||code:1\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 003error\n+#\n+# To the above, add multiple 'error <msg>' events\n+\n+test_expect_success 'perf stream, error event' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" test-tool trace2 003error \"hello world\" \"this is a test\" &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 003error '\\''hello world'\\'' '\\''this is a test'\\''\n+\t\td0|main|cmd_verb|||||trace2 (trace2)\n+\t\td0|main|error|||||hello world\n+\t\td0|main|error|||||this is a test\n+\t\td0|main|exit||_T_ABS_|||code:0\n+\t\td0|main|atexit||_T_ABS_|||code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 004child\n+#\n+# Test nested spawning of child processes.\n+#\n+# Conceptually, this looks like:\n+#    P1: TT trace2 004child\n+#    P2: |--- TT trace2 004child\n+#    P3:      |--- TT trace2 001return 0\n+#\n+# Which should generate events:\n+#    P1: version\n+#    P1: start\n+#    P1: cmd_verb\n+#    P1: child_start\n+#        P2: version\n+#        P2: start\n+#        P2: cmd_verb\n+#        P2: child_start\n+#            P3: version\n+#            P3: start\n+#            P3: cmd_verb\n+#            P3: exit\n+#            P3: atexit\n+#        P2: child_exit\n+#        P2: exit\n+#        P2: atexit\n+#    P1: child_exit\n+#    P1: exit\n+#    P1: atexit\n+\n+test_expect_success 'perf stream, child processes' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" test-tool trace2 004child test-tool trace2 004child test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 004child test-tool trace2 004child test-tool trace2 001return 0\n+\t\td0|main|cmd_verb|||||trace2 (trace2)\n+\t\td0|main|child_start||_T_ABS_|||[ch0] class:? argv: test-tool trace2 004child test-tool trace2 001return 0\n+\t\td1|main|version|||||$V\n+\t\td1|main|start|||||_EXE_ trace2 004child test-tool trace2 001return 0\n+\t\td1|main|cmd_verb|||||trace2 (trace2/trace2)\n+\t\td1|main|child_start||_T_ABS_|||[ch0] class:? argv: test-tool trace2 001return 0\n+\t\td2|main|version|||||$V\n+\t\td2|main|start|||||_EXE_ trace2 001return 0\n+\t\td2|main|cmd_verb|||||trace2 (trace2/trace2/trace2)\n+\t\td2|main|exit||_T_ABS_|||code:0\n+\t\td2|main|atexit||_T_ABS_|||code:0\n+\t\td1|main|child_exit||_T_ABS_|_T_REL_||[ch0] pid:_PID_ code:0\n+\t\td1|main|exit||_T_ABS_|||code:0\n+\t\td1|main|atexit||_T_ABS_|||code:0\n+\t\td0|main|child_exit||_T_ABS_|_T_REL_||[ch0] pid:_PID_ code:0\n+\t\td0|main|exit||_T_ABS_|||code:0\n+\t\td0|main|atexit||_T_ABS_|||code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t0211/scrub_perf.perl b/t/t0211/scrub_perf.perl\nnew file mode 100644\nindex 0000000000..351af7844e\n--- /dev/null\n+++ b/t/t0211/scrub_perf.perl\n@@ -0,0 +1,76 @@\n+#!/usr/bin/perl\n+#\n+# Scrub the variable fields from the perf trace2 output to\n+# make testing easier.\n+\n+use strict;\n+use warnings;\n+\n+my $qpath = '\\'[^\\']*\\'|[^ ]*';\n+\n+my $col_depth=0;\n+my $col_thread=1;\n+my $col_event=2;\n+my $col_repo=3;\n+my $col_t_abs=4;\n+my $col_t_rel=5;\n+my $col_category=6;\n+my $col_rest=7;\n+\n+# This code assumes that the trace2 data was written with bare\n+# turned on (which omits the \"<clock> <file>:<line> | <parents>\"\n+# prefix.\n+\n+while (<>) {\n+    my @tokens = split /\\|/;\n+\n+    foreach my $col (@tokens) { $col =~ s/^\\s+|\\s+$//g; }\n+\n+    if ($tokens[$col_event] =~ m/^start/) {\n+\t# The 'start' message lists the contents of argv in $col_rest.\n+\t# On some platforms (Windows), argv[0] is *sometimes* a canonical\n+\t# absolute path to the EXE rather than the value passed in the\n+\t# shell script.  Replace it with a placeholder to simplify our\n+\t# HEREDOC in the test script.\n+\tmy $argv0;\n+\tmy $argvRest;\n+\t$tokens[$col_rest] =~ s/^($qpath)\\W*(.*)/_EXE_ $2/;\n+    }\n+    elsif ($tokens[$col_event] =~ m/cmd_path/) {\n+\t# Likewise, the 'cmd_path' message breaks out argv[0].\n+\t#\n+\t# This line is only emitted when RUNTIME_PREFIX is defined,\n+\t# so just omit it for testing purposes.\n+\t# $tokens[$col_rest] = \"_EXE_\";\n+\tgoto SKIP_LINE;\n+    }\n+    elsif ($tokens[$col_event] =~ m/child_exit/) {\n+\t$tokens[$col_rest] =~ s/ pid:\\d* / pid:_PID_ /;\n+    }\n+    elsif ($tokens[$col_event] =~ m/data/) {\n+\tif ($tokens[$col_category] =~ m/process/) {\n+\t    # 'data' and 'data_json' events containing 'process'\n+\t    # category data are assumed to be platform-specific\n+\t    # and highly variable.  Just omit them.\n+\t    goto SKIP_LINE;\n+\t}\n+    }\n+\n+    # t_abs and t_rel are either blank or a float.  Replace the float\n+    # with a constant for matching the HEREDOC in the test script.\n+    if ($tokens[$col_t_abs] =~ m/\\d/) {\n+\t$tokens[$col_t_abs] = \"_T_ABS_\";\n+    }\n+    if ($tokens[$col_t_rel] =~ m/\\d/) {\n+\t$tokens[$col_t_rel] = \"_T_REL_\";\n+    }\n+\n+    my $out;\n+\n+    $out = join('|', @tokens);\n+    print \"$out\\n\";\n+\n+  SKIP_LINE:\n+}\n+\n+\ndiff --git a/t/t0212-trace2-event.sh b/t/t0212-trace2-event.sh\nnew file mode 100755\nindex 0000000000..517d5514df\n--- /dev/null\n+++ b/t/t0212-trace2-event.sh\n@@ -0,0 +1,234 @@\n+#!/bin/sh\n+\n+test_description='test trace2 facility'\n+. ./test-lib.sh\n+\n+# Add t/helper directory to PATH so that we can use a relative\n+# path to run nested instances of test-tool.exe (see 004child).\n+# This helps with HEREDOC comparisons later.\n+TTDIR=\"$GIT_BUILD_DIR/t/helper/\" && export TTDIR\n+PATH=\"$TTDIR:$PATH\" && export PATH\n+\n+# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe\n+# Warning: to do the actual diff/comparison, so the HEREDOCs here\n+# Warning: only cover our actual calls to test-tool and/or git.\n+# Warning: So you may see extra lines in artifact files when\n+# Warning: interactively debugging.\n+\n+# Turn off any inherited trace2 settings for this test.\n+unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT\n+unset GIT_TR2_BARE\n+unset GIT_TR2_CONFIG_PARAMS\n+\n+V=$(git version | sed -e 's/^git version //') && export V\n+\n+# There are multiple trace2 targets: normal, perf, and event.\n+# Trace2 events will/can be written to each active target (subject\n+# to whatever filtering that target decides to do).\n+# Test each target independently.\n+#\n+# Defer setting GIT_TR2_PERF until the actual command we want to\n+# test because hidden git and test-tool commands in the test\n+# harness can contaminate our output.\n+\n+# We don't bother repeating the 001return and 002exit tests, since they\n+# have coverage in the normal and perf targets.\n+\n+# Verb 003error\n+#\n+# To the above, add multiple 'error <msg>' events\n+\n+test_expect_success 'event stream, error event' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 003error \"hello world\" \"this is a test\" &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"003error\",\n+\t|      \"hello world\",\n+\t|      \"this is a test\"\n+\t|    ],\n+\t|    \"errors\":[\n+\t|      \"%s\",\n+\t|      \"%s\"\n+\t|    ],\n+\t|    \"exit_code\":0,\n+\t|    \"verb\":\"trace2\",\n+\t|    \"verb_hierarchy\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 004child\n+#\n+# Test nested spawning of child processes.\n+#\n+# Conceptually, this looks like:\n+#    P1: TT trace2 004child\n+#    P2: |--- TT trace2 004child\n+#    P3:      |--- TT trace2 001return 0\n+\n+test_expect_success 'event stream, return code 0' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 004child test-tool trace2 004child test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"004child\",\n+\t|      \"test-tool\",\n+\t|      \"trace2\",\n+\t|      \"004child\",\n+\t|      \"test-tool\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"child\":{\n+\t|      \"0\":{\n+\t|        \"child_argv\":[\n+\t|          \"_EXE_\",\n+\t|          \"trace2\",\n+\t|          \"004child\",\n+\t|          \"test-tool\",\n+\t|          \"trace2\",\n+\t|          \"001return\",\n+\t|          \"0\"\n+\t|        ],\n+\t|        \"child_class\":\"?\",\n+\t|        \"child_code\":0,\n+\t|        \"use_shell\":0\n+\t|      }\n+\t|    },\n+\t|    \"exit_code\":0,\n+\t|    \"verb\":\"trace2\",\n+\t|    \"verb_hierarchy\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  },\n+\t|  \"_SID0_/_SID1_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"004child\",\n+\t|      \"test-tool\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"child\":{\n+\t|      \"0\":{\n+\t|        \"child_argv\":[\n+\t|          \"_EXE_\",\n+\t|          \"trace2\",\n+\t|          \"001return\",\n+\t|          \"0\"\n+\t|        ],\n+\t|        \"child_class\":\"?\",\n+\t|        \"child_code\":0,\n+\t|        \"use_shell\":0\n+\t|      }\n+\t|    },\n+\t|    \"exit_code\":0,\n+\t|    \"verb\":\"trace2\",\n+\t|    \"verb_hierarchy\":\"trace2/trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  },\n+\t|  \"_SID0_/_SID1_/_SID2_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"exit_code\":0,\n+\t|    \"verb\":\"trace2\",\n+\t|    \"verb_hierarchy\":\"trace2/trace2/trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Test listing of all \"interesting\" config settings.\n+\n+test_expect_success 'event stream, list config' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tgit config --local t0212.abc 1 &&\n+\tgit config --local t0212.def \"hello world\" &&\n+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" GIT_TR2_CONFIG_PARAMS=\"t0212.*\" test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"exit_code\":0,\n+\t|    \"params\":[\n+\t|      {\n+\t|        \"param\":\"t0212.abc\",\n+\t|        \"value\":\"1\"\n+\t|      },\n+\t|      {\n+\t|        \"param\":\"t0212.def\",\n+\t|        \"value\":\"hello world\"\n+\t|      }\n+\t|    ],\n+\t|    \"verb\":\"trace2\",\n+\t|    \"verb_hierarchy\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'basic trace2_data' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 006data test_category k1 v1 test_category k2 v2 &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"006data\",\n+\t|      \"test_category\",\n+\t|      \"k1\",\n+\t|      \"v1\",\n+\t|      \"test_category\",\n+\t|      \"k2\",\n+\t|      \"v2\"\n+\t|    ],\n+\t|    \"data\":{\n+\t|      \"test_category\":{\n+\t|        \"k1\":\"v1\",\n+\t|        \"k2\":\"v2\"\n+\t|      }\n+\t|    },\n+\t|    \"exit_code\":0,\n+\t|    \"verb\":\"trace2\",\n+\t|    \"verb_hierarchy\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t0212/parse_events.perl b/t/t0212/parse_events.perl\nnew file mode 100644\nindex 0000000000..0e38fa6db6\n--- /dev/null\n+++ b/t/t0212/parse_events.perl\n@@ -0,0 +1,251 @@\n+#!/usr/bin/perl\n+#\n+# Parse event stream and convert individual events into a summary\n+# record for the process.\n+#\n+# Git.exe generates one or more \"event\" records for each API method,\n+# such as \"start <argv>\" and \"exit <code>\", during the life of the git\n+# process.  Additionally, the input may contain interleaved events\n+# from multiple concurrent git processes and/or multiple threads from\n+# within a git process.\n+#\n+# Accumulate events for each process (based on its unique SID) in a\n+# dictionary and emit process summary records.\n+#\n+# Convert some of the variable fields (such as elapsed time) into\n+# placeholders (or omit them) to make HEREDOC comparisons easier in\n+# the test scripts.\n+#\n+# We may also omit fields not (currently) useful for testing purposes.\n+\n+use strict;\n+use warnings;\n+use JSON::PP;\n+use Data::Dumper;\n+use Getopt::Long;\n+\n+# The version of the trace2 event target format that we understand.\n+# This is reported in the 'version' event in the 'evt' field.\n+# It comes from the GIT_TR2_EVENT_VERSION macro in trace2/tr2_tgt_event.c\n+my $evt_version = '1';\n+\n+my $show_children = 1;\n+my $show_exec     = 1;\n+my $show_threads  = 1;\n+\n+# A hack to generate test HEREDOC data for pasting into the test script.\n+# Usage:\n+#    cd \"t/trash directory.t0212-trace2-event\"\n+#    $TT trace ... >trace.event\n+#    VV=$(../../git.exe version | sed -e 's/^git version //')\n+#    perl ../t0212/parse_events.perl --HEREDOC --VERSION=$VV <trace.event >heredoc\n+# Then paste heredoc into your new test.\n+\n+my $gen_heredoc = 0;\n+my $gen_version = '';\n+\n+GetOptions(\"children!\" => \\$show_children,\n+\t   \"exec!\"     => \\$show_exec,\n+\t   \"threads!\"  => \\$show_threads,\n+\t   \"HEREDOC!\"  => \\$gen_heredoc,\n+\t   \"VERSION=s\" => \\$gen_version    )\n+    or die(\"Error in command line arguments\\n\");\n+\n+\n+# SIDs contains timestamps and PIDs of the process and its parents.\n+# This makes it difficult to match up in a HEREDOC in the test script.\n+# Build a map from actual SIDs to predictable constant values and yet\n+# keep the parent/child relationships.  For example:\n+# {..., \"sid\":\"1539706952458276-8652\", ...}\n+# {..., \"sid\":\"1539706952458276-8652/1539706952649493-15452\", ...}\n+# becomes:\n+# {..., \"sid\":\"_SID1_\", ...}\n+# {..., \"sid\":\"_SID1_/_SID2_\", ...}\n+my $sid_map;\n+my $sid_count = 0;\n+\n+my $processes;\n+\n+while (<>) {\n+    my $line = decode_json( $_ );\n+\n+    my $sid = \"\";\n+    my $sid_sep = \"\";\n+\n+    my $raw_sid = $line->{'sid'};\n+    my @raw_sid_parts = split /\\//, $raw_sid;\n+    foreach my $raw_sid_k (@raw_sid_parts) {\n+\tif (!exists $sid_map->{$raw_sid_k}) {\n+\t    $sid_map->{$raw_sid_k} = '_SID' . $sid_count . '_';\n+\t    $sid_count++;\n+\t}\n+\t$sid = $sid . $sid_sep . $sid_map->{$raw_sid_k};\n+\t$sid_sep = '/';\n+    }\n+    \n+    my $event = $line->{'event'};\n+\n+    if ($event eq 'version') {\n+\t$processes->{$sid}->{'version'} = $line->{'exe'};\n+\tif ($gen_heredoc == 1 && $gen_version eq $line->{'exe'}) {\n+\t    # If we are generating data FOR the test script, replace\n+\t    # the reported git.exe version with a reference to an\n+\t    # environment variable.  When our output is pasted into\n+\t    # the test script, it will then be expanded in future\n+\t    # test runs to the THEN current version of git.exe.\n+\t    # We assume that the test script uses env var $V.\n+\t    $processes->{$sid}->{'version'} = \"\\$V\";\n+\t}\n+    }\n+\n+    elsif ($event eq 'start') {\n+\t$processes->{$sid}->{'argv'} = $line->{'argv'};\n+\t$processes->{$sid}->{'argv'}[0] = \"_EXE_\";\n+    }\n+\n+    elsif ($event eq 'exit') {\n+\t$processes->{$sid}->{'exit_code'} = $line->{'code'};\n+    }\n+\n+    elsif ($event eq 'atexit') {\n+\t$processes->{$sid}->{'exit_code'} = $line->{'code'};\n+    }\n+\n+    elsif ($event eq 'error') {\n+\t# For HEREDOC purposes, use the error message format string if\n+\t# available, rather than the formatted message (which probably\n+\t# has an absolute pathname).\n+\tif (exists $line->{'fmt'}) {\n+\t    push( @{$processes->{$sid}->{'errors'}}, $line->{'fmt'} );\n+\t}\n+\telsif (exists $line->{'msg'}) {\n+\t    push( @{$processes->{$sid}->{'errors'}}, $line->{'msg'} );\n+\t}\n+    }\n+\n+    elsif ($event eq 'cmd_path') {\n+\t## $processes->{$sid}->{'path'} = $line->{'path'};\n+\t#\n+\t# Like in the 'start' event, we need to replace the value of\n+\t# argv[0] with a token for HEREDOC purposes.  However, the\n+\t# event is only emitted when RUNTIME_PREFIX is defined, so\n+\t# just omit it for testing purposes.\n+\t# $processes->{$sid}->{'path'} = \"_EXE_\";\n+    }\n+    \n+    elsif ($event eq 'cmd_verb') {\n+\t$processes->{$sid}->{'verb'} = $line->{'name'};\n+\t$processes->{$sid}->{'verb_hierarchy'} = $line->{'hierarchy'};\n+    }\n+\n+    elsif ($event eq 'alias') {\n+\t$processes->{$sid}->{'alias'}->{'key'} = $line->{'alias'};\n+\t$processes->{$sid}->{'alias'}->{'argv'} = $line->{'argv'};\n+    }\n+\n+    elsif ($event eq 'def_param') {\n+\tmy $kv;\n+\t$kv->{'param'} = $line->{'param'};\n+\t$kv->{'value'} = $line->{'value'};\n+\tpush( @{$processes->{$sid}->{'params'}}, $kv );\n+    }\n+\n+    elsif ($event eq 'child_start') {\n+\tif ($show_children == 1) {\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_class'} = $line->{'child_class'};\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_argv'} = $line->{'argv'};\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_argv'}[0] = \"_EXE_\";\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'use_shell'} = $line->{'use_shell'} ? 1 : 0;\n+\t}\n+    }\n+\n+    elsif ($event eq 'child_exit') {\n+\tif ($show_children == 1) {\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_code'} = $line->{'code'};\n+\t}\n+    }\n+\n+    # TODO decide what information we want to test from thread events.\n+\n+    elsif ($event eq 'thread_start') {\n+\tif ($show_threads == 1) {\n+\t}\n+    }\n+\n+    elsif ($event eq 'thread_exit') {\n+\tif ($show_threads == 1) {\n+\t}\n+    }\n+\n+    # TODO decide what information we want to test from exec events.\n+\n+    elsif ($event eq 'exec') {\n+\tif ($show_exec == 1) {\n+\t}\n+    }\n+\n+    elsif ($event eq 'exec_result') {\n+\tif ($show_exec == 1) {\n+\t}\n+    }\n+\n+    elsif ($event eq 'def_param') {\n+\t# Accumulate parameter key/value pairs by key rather than in an array\n+\t# so that we get overwrite (last one wins) effects.\n+\t$processes->{$sid}->{'params'}->{$line->{'param'}} = $line->{'value'};\n+    }\n+\n+    elsif ($event eq 'def_repo') {\n+\t# $processes->{$sid}->{'repos'}->{$line->{'repo'}} = $line->{'worktree'};\n+\t$processes->{$sid}->{'repos'}->{$line->{'repo'}} = \"_WORKTREE_\";\n+    }\n+\n+    # A series of potentially nested and threaded region and data events\n+    # is fundamentally incompatibile with the type of summary record we\n+    # are building in this script.  Since they are intended for\n+    # perf-trace-like analysis rather than a result summary, we ignore\n+    # most of them here.\n+\n+    # elsif ($event eq 'region_enter') {\n+    # }\n+    # elsif ($event eq 'region_leave') {\n+    # }\n+\n+    elsif ($event eq 'data') {\n+\tmy $cat = $line->{'category'};\n+\tif ($cat eq 'test_category') {\n+\t    \n+\t    my $key = $line->{'key'};\n+\t    my $value = $line->{'value'};\n+\t    $processes->{$sid}->{'data'}->{$cat}->{$key} = $value;\n+\t}\n+    }\n+\n+    # This trace2 target does not emit 'printf' events.\n+    #\n+    # elsif ($event eq 'printf') {\n+    # }\n+}\n+\n+# Dump the resulting hash into something that we can compare against\n+# in the test script.  These options make Dumper output look a little\n+# bit like JSON.  Also convert variable references of the form \"$VAR*\"\n+# so that the matching HEREDOC doesn't need to escape it.\n+\n+$Data::Dumper::Sortkeys = 1;\n+$Data::Dumper::Indent = 1;\n+$Data::Dumper::Purity = 1;\n+$Data::Dumper::Pair = ':';\n+\n+my $out = Dumper($processes);\n+$out =~ s/'/\"/g;\n+$out =~ s/\\$VAR/VAR/g;\n+\n+# Finally, if we're running this script to generate (manually confirmed)\n+# data to add to the test script, guard the indentation.\n+\n+if ($gen_heredoc == 1) {\n+    $out =~ s/^/\\t\\|/gms;\n+}\n+\n+print $out;\n-- \ngitgitgadget\n"},{"id":"368177","messageId":"bfe2fe0bbe671c9b8091de0f6303cf0561400132.1548877863.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 02/14] trace2: create new combined trace facility","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T19:51:06Z","receivedAt":"2019-01-30T19:51:27Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate a new unified tracing facility for git.  The eventual intent is to\nreplace the current trace_printf* and trace_performance* routines with a\nunified set of git_trace2* routines.\n\nIn addition to the usual printf-style API, trace2 provides higer-level\nevent verbs with fixed-fields allowing structured data to be written.\nThis makes post-processing and analysis easier for external tools.\n\nTrace2 defines 3 output targets.  These are set using the environment\nvariables \"GIT_TR2\", \"GIT_TR2_PERF\", and \"GIT_TR2_EVENT\".  These may be\nset to \"1\" or to an absolute pathname (just like the current GIT_TRACE).\n\n* GIT_TR2 is intended to be a replacement for GIT_TRACE and logs command\n  summary data.\n\n* GIT_TR2_PERF is intended as a replacement for GIT_TRACE_PERFORMANCE.\n  It extends the output with columns for the command process, thread,\n  repo, absolute and relative elapsed times.  It reports events for\n  child process start/stop, thread start/stop, and per-thread function\n  nesting.\n\n* GIT_TR2_EVENT is a new structured format. It writes event data as a\n  series of JSON records.\n\nCalls to trace2 functions log to any of the 3 output targets enabled\nwithout the need to call different trace_printf* or trace_performance*\nroutines.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                      |  10 +\n builtin/submodule--helper.c   |   9 +-\n cache.h                       |   1 +\n common-main.c                 |  12 +-\n compat/mingw.c                |  11 +-\n compat/mingw.h                |   3 +-\n config.c                      |   2 +\n exec-cmd.c                    |   2 +\n git-compat-util.h             |   7 +\n git.c                         |  65 +++\n remote-curl.c                 |   7 +\n repository.c                  |   2 +\n repository.h                  |   3 +\n run-command.c                 |  59 ++-\n run-command.h                 |  13 +-\n sh-i18n--envsubst.c           |   3 +\n submodule.c                   |  11 +-\n t/helper/test-parse-options.c |   3 +\n t/helper/test-tool.c          |   3 +\n t/t0001-init.sh               |   1 +\n trace2.c                      | 807 ++++++++++++++++++++++++++++++++++\n trace2.h                      | 366 +++++++++++++++\n trace2/tr2_cfg.c              |  90 ++++\n trace2/tr2_cfg.h              |  19 +\n trace2/tr2_dst.c              | 198 +++++++++\n trace2/tr2_dst.h              |  36 ++\n trace2/tr2_sid.c              |  67 +++\n trace2/tr2_sid.h              |  18 +\n trace2/tr2_tbuf.c             |  32 ++\n trace2/tr2_tbuf.h             |  23 +\n trace2/tr2_tgt.h              | 131 ++++++\n trace2/tr2_tgt_event.c        | 590 +++++++++++++++++++++++++\n trace2/tr2_tgt_normal.c       | 325 ++++++++++++++\n trace2/tr2_tgt_perf.c         | 536 ++++++++++++++++++++++\n trace2/tr2_tls.c              | 164 +++++++\n trace2/tr2_tls.h              |  97 ++++\n trace2/tr2_verb.c             |  30 ++\n trace2/tr2_verb.h             |  24 +\n usage.c                       |  31 ++\n 39 files changed, 3794 insertions(+), 17 deletions(-)\n create mode 100644 trace2.c\n create mode 100644 trace2.h\n create mode 100644 trace2/tr2_cfg.c\n create mode 100644 trace2/tr2_cfg.h\n create mode 100644 trace2/tr2_dst.c\n create mode 100644 trace2/tr2_dst.h\n create mode 100644 trace2/tr2_sid.c\n create mode 100644 trace2/tr2_sid.h\n create mode 100644 trace2/tr2_tbuf.c\n create mode 100644 trace2/tr2_tbuf.h\n create mode 100644 trace2/tr2_tgt.h\n create mode 100644 trace2/tr2_tgt_event.c\n create mode 100644 trace2/tr2_tgt_normal.c\n create mode 100644 trace2/tr2_tgt_perf.c\n create mode 100644 trace2/tr2_tls.c\n create mode 100644 trace2/tr2_tls.h\n create mode 100644 trace2/tr2_verb.c\n create mode 100644 trace2/tr2_verb.h\n\ndiff --git a/Makefile b/Makefile\nindex 6e8d017e8e..f929640766 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1005,6 +1005,16 @@ LIB_OBJS += tempfile.o\n LIB_OBJS += thread-utils.o\n LIB_OBJS += tmp-objdir.o\n LIB_OBJS += trace.o\n+LIB_OBJS += trace2.o\n+LIB_OBJS += trace2/tr2_cfg.o\n+LIB_OBJS += trace2/tr2_dst.o\n+LIB_OBJS += trace2/tr2_sid.o\n+LIB_OBJS += trace2/tr2_tbuf.o\n+LIB_OBJS += trace2/tr2_tgt_event.o\n+LIB_OBJS += trace2/tr2_tgt_normal.o\n+LIB_OBJS += trace2/tr2_tgt_perf.o\n+LIB_OBJS += trace2/tr2_tls.o\n+LIB_OBJS += trace2/tr2_verb.o\n LIB_OBJS += trailer.o\n LIB_OBJS += transport.o\n LIB_OBJS += transport-helper.o\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 0e140f176c..f10dc5134a 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -1815,11 +1815,10 @@ static int update_submodules(struct submodule_update_clone *suc)\n {\n \tint i;\n \n-\trun_processes_parallel(suc->max_jobs,\n-\t\t\t       update_clone_get_next_task,\n-\t\t\t       update_clone_start_failure,\n-\t\t\t       update_clone_task_finished,\n-\t\t\t       suc);\n+\trun_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task,\n+\t\t\t\t   update_clone_start_failure,\n+\t\t\t\t   update_clone_task_finished, suc, \"submodule\",\n+\t\t\t\t   \"parallel/update\");\n \n \t/*\n \t * We saved the output and put it out all at once now.\ndiff --git a/cache.h b/cache.h\nindex 009e8b3b15..3d12ffe01f 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -9,6 +9,7 @@\n #include \"gettext.h\"\n #include \"convert.h\"\n #include \"trace.h\"\n+#include \"trace2.h\"\n #include \"string-list.h\"\n #include \"pack-revindex.h\"\n #include \"hash.h\"\ndiff --git a/common-main.c b/common-main.c\nindex 3728f66b4c..6dbdc4adf2 100644\n--- a/common-main.c\n+++ b/common-main.c\n@@ -25,12 +25,18 @@ static void restore_sigpipe_to_default(void)\n \n int main(int argc, const char **argv)\n {\n+\tint result;\n+\n \t/*\n \t * Always open file descriptors 0/1/2 to avoid clobbering files\n \t * in die().  It also avoids messing up when the pipes are dup'ed\n \t * onto stdin/stdout/stderr in the child processes we spawn.\n \t */\n \tsanitize_stdfds();\n+\trestore_sigpipe_to_default();\n+\n+\ttrace2_initialize();\n+\ttrace2_cmd_start(argv);\n \n \tgit_resolve_executable_dir(argv[0]);\n \n@@ -40,7 +46,9 @@ int main(int argc, const char **argv)\n \n \tattr_start();\n \n-\trestore_sigpipe_to_default();\n+\tresult = cmd_main(argc, argv);\n+\n+\ttrace2_cmd_exit(result);\n \n-\treturn cmd_main(argc, argv);\n+\treturn result;\n }\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex b459e1a291..111fe68baf 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -1476,19 +1476,23 @@ static int try_shell_exec(const char *cmd, char *const *argv)\n \t\treturn 0;\n \tprog = path_lookup(interpr, 1);\n \tif (prog) {\n+\t\tint exec_id;\n \t\tint argc = 0;\n \t\tconst char **argv2;\n \t\twhile (argv[argc]) argc++;\n \t\tALLOC_ARRAY(argv2, argc + 1);\n \t\targv2[0] = (char *)cmd;\t/* full path to the script file */\n \t\tmemcpy(&argv2[1], &argv[1], sizeof(*argv) * argc);\n+\t\texec_id = trace2_exec(prog, argv2);\n \t\tpid = mingw_spawnv(prog, argv2, 1);\n \t\tif (pid >= 0) {\n \t\t\tint status;\n \t\t\tif (waitpid(pid, &status, 0) < 0)\n \t\t\t\tstatus = 255;\n+\t\t\ttrace2_exec_result(exec_id, status);\n \t\t\texit(status);\n \t\t}\n+\t\ttrace2_exec_result(exec_id, -1);\n \t\tpid = 1;\t/* indicate that we tried but failed */\n \t\tfree(prog);\n \t\tfree(argv2);\n@@ -1501,12 +1505,17 @@ int mingw_execv(const char *cmd, char *const *argv)\n \t/* check if git_command is a shell script */\n \tif (!try_shell_exec(cmd, argv)) {\n \t\tint pid, status;\n+\t\tint exec_id;\n \n+\t\texec_id = trace2_exec(cmd, (const char **)argv);\n \t\tpid = mingw_spawnv(cmd, (const char **)argv, 0);\n-\t\tif (pid < 0)\n+\t\tif (pid < 0) {\n+\t\t\ttrace2_exec_result(exec_id, -1);\n \t\t\treturn -1;\n+\t\t}\n \t\tif (waitpid(pid, &status, 0) < 0)\n \t\t\tstatus = 255;\n+\t\ttrace2_exec_result(exec_id, status);\n \t\texit(status);\n \t}\n \treturn -1;\ndiff --git a/compat/mingw.h b/compat/mingw.h\nindex 30d9fb3e36..4d73f8aa9d 100644\n--- a/compat/mingw.h\n+++ b/compat/mingw.h\n@@ -147,8 +147,7 @@ static inline int fcntl(int fd, int cmd, ...)\n \terrno = EINVAL;\n \treturn -1;\n }\n-/* bash cannot reliably detect negative return codes as failure */\n-#define exit(code) exit((code) & 0xff)\n+\n #define sigemptyset(x) (void)0\n static inline int sigaddset(sigset_t *set, int signum)\n { return 0; }\ndiff --git a/config.c b/config.c\nindex ff521eb27a..4beeb7795a 100644\n--- a/config.c\n+++ b/config.c\n@@ -2656,6 +2656,8 @@ int git_config_set_gently(const char *key, const char *value)\n void git_config_set(const char *key, const char *value)\n {\n \tgit_config_set_multivar(key, value, NULL, 0);\n+\n+\ttrace2_cmd_set_config(key, value);\n }\n \n /*\ndiff --git a/exec-cmd.c b/exec-cmd.c\nindex 4f81f44310..7deeab3039 100644\n--- a/exec-cmd.c\n+++ b/exec-cmd.c\n@@ -209,6 +209,8 @@ static int git_get_exec_path(struct strbuf *buf, const char *argv0)\n \t\treturn -1;\n \t}\n \n+\ttrace2_cmd_path(buf->buf);\n+\n \treturn 0;\n }\n \ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 29a19902aa..1d0aedc11f 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -1251,6 +1251,13 @@ static inline int is_missing_file_error(int errno_)\n \n extern int cmd_main(int, const char **);\n \n+/*\n+ * Intercept all calls to exit() and route them to trace2 to\n+ * optionally emit a message before calling the real exit().\n+ */\n+int trace2_cmd_exit_fl(const char *file, int line, int code);\n+#define exit(code) exit(trace2_cmd_exit_fl(__FILE__, __LINE__, (code)))\n+\n /*\n  * You can mark a stack variable with UNLEAK(var) to avoid it being\n  * reported as a leak by tools like LSAN or valgrind. The argument\ndiff --git a/git.c b/git.c\nindex 0ce0e13f0f..179e52c42d 100644\n--- a/git.c\n+++ b/git.c\n@@ -147,16 +147,20 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\t\tgit_set_exec_path(cmd + 1);\n \t\t\telse {\n \t\t\t\tputs(git_exec_path());\n+\t\t\t\ttrace2_cmd_verb(\"_query_\");\n \t\t\t\texit(0);\n \t\t\t}\n \t\t} else if (!strcmp(cmd, \"--html-path\")) {\n \t\t\tputs(system_path(GIT_HTML_PATH));\n+\t\t\ttrace2_cmd_verb(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"--man-path\")) {\n \t\t\tputs(system_path(GIT_MAN_PATH));\n+\t\t\ttrace2_cmd_verb(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"--info-path\")) {\n \t\t\tputs(system_path(GIT_INFO_PATH));\n+\t\t\ttrace2_cmd_verb(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"-p\") || !strcmp(cmd, \"--paginate\")) {\n \t\t\tuse_pager = 1;\n@@ -285,6 +289,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\t(*argv)++;\n \t\t\t(*argc)--;\n \t\t} else if (skip_prefix(cmd, \"--list-cmds=\", &cmd)) {\n+\t\t\ttrace2_cmd_verb(\"_query_\");\n \t\t\tif (!strcmp(cmd, \"parseopt\")) {\n \t\t\t\tstruct string_list list = STRING_LIST_INIT_DUP;\n \t\t\t\tint i;\n@@ -332,9 +337,14 @@ static int handle_alias(int *argcp, const char ***argv)\n \t\t\tcommit_pager_choice();\n \n \t\t\tchild.use_shell = 1;\n+\t\t\tchild.trace2_child_class = \"shell_alias\";\n \t\t\targv_array_push(&child.args, alias_string + 1);\n \t\t\targv_array_pushv(&child.args, (*argv) + 1);\n \n+\t\t\ttrace2_cmd_alias(alias_command, child.args.argv);\n+\t\t\ttrace2_cmd_list_config();\n+\t\t\ttrace2_cmd_verb(\"_run_shell_alias_\");\n+\n \t\t\tret = run_command(&child);\n \t\t\tif (ret >= 0)   /* normal exit */\n \t\t\t\texit(ret);\n@@ -369,6 +379,9 @@ static int handle_alias(int *argcp, const char ***argv)\n \t\t/* insert after command name */\n \t\tmemcpy(new_argv + count, *argv + 1, sizeof(char *) * *argcp);\n \n+\t\ttrace2_cmd_alias(alias_command, new_argv);\n+\t\ttrace2_cmd_list_config();\n+\n \t\t*argv = new_argv;\n \t\t*argcp += count - 1;\n \n@@ -417,6 +430,8 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)\n \t\tsetup_work_tree();\n \n \ttrace_argv_printf(argv, \"trace: built-in: git\");\n+\ttrace2_cmd_verb(p->cmd);\n+\ttrace2_cmd_list_config();\n \n \tvalidate_cache_entries(&the_index);\n \tstatus = p->fn(argc, argv, prefix);\n@@ -666,7 +681,14 @@ static void execv_dashed_external(const char **argv)\n \tcmd.clean_on_exit = 1;\n \tcmd.wait_after_clean = 1;\n \tcmd.silent_exec_failure = 1;\n+\tcmd.trace2_child_class = \"dashed\";\n \n+\ttrace2_cmd_verb(\"_run_dashed_\");\n+\n+\t/*\n+\t * The code in run_command() logs trace2 child_start/child_exit\n+\t * events, so we do not need to report exec/exec_result events here.\n+\t */\n \ttrace_argv_printf(cmd.args.argv, \"trace: exec:\");\n \n \t/*\n@@ -676,6 +698,12 @@ static void execv_dashed_external(const char **argv)\n \t * the program.\n \t */\n \tstatus = run_command(&cmd);\n+\n+\t/*\n+\t * If the child process ran and we are now going to exit, emit a\n+\t * generic string as our trace2 command verb to indicate that we\n+\t * launched a dashed command.\n+\t */\n \tif (status >= 0)\n \t\texit(status);\n \telse if (errno != ENOENT)\n@@ -701,6 +729,43 @@ static int run_argv(int *argcp, const char ***argv)\n \t\tif (!done_alias)\n \t\t\thandle_builtin(*argcp, *argv);\n \n+#if 0 // TODO In GFW, need to amend a7924b655e940b06cb547c235d6bed9767929673 to include trace2_ and _tr2 lines.\n+\t\telse if (get_builtin(**argv)) {\n+\t\t\tstruct argv_array args = ARGV_ARRAY_INIT;\n+\t\t\tint i;\n+\n+\t\t\t/*\n+\t\t\t * The current process is committed to launching a\n+\t\t\t * child process to run the command named in (**argv)\n+\t\t\t * and exiting.  Log a generic string as the trace2\n+\t\t\t * command verb to indicate this.  Note that the child\n+\t\t\t * process will log the actual verb when it runs.\n+\t\t\t */\n+\t\t\ttrace2_cmd_verb(\"_run_git_alias_\");\n+\n+\t\t\tif (get_super_prefix())\n+\t\t\t\tdie(\"%s doesn't support --super-prefix\", **argv);\n+\n+\t\t\tcommit_pager_choice();\n+\n+\t\t\targv_array_push(&args, \"git\");\n+\t\t\tfor (i = 0; i < *argcp; i++)\n+\t\t\t\targv_array_push(&args, (*argv)[i]);\n+\n+\t\t\ttrace_argv_printf(args.argv, \"trace: exec:\");\n+\n+\t\t\t/*\n+\t\t\t * if we fail because the command is not found, it is\n+\t\t\t * OK to return. Otherwise, we just pass along the status code.\n+\t\t\t */\n+\t\t\ti = run_command_v_opt_tr2(args.argv, RUN_SILENT_EXEC_FAILURE |\n+\t\t\t\t\t\t  RUN_CLEAN_ON_EXIT, \"git_alias\");\n+\t\t\tif (i >= 0 || errno != ENOENT)\n+\t\t\t\texit(i);\n+\t\t\tdie(\"could not execute builtin %s\", **argv);\n+\t\t}\n+#endif // a7924b655e940b06cb547c235d6bed9767929673\n+\n \t\t/* .. then try the external ones */\n \t\texecv_dashed_external(*argv);\n \ndiff --git a/remote-curl.c b/remote-curl.c\nindex 6ff9c66b90..a1bcd5e60c 100644\n--- a/remote-curl.c\n+++ b/remote-curl.c\n@@ -1360,6 +1360,13 @@ int cmd_main(int argc, const char **argv)\n \tstring_list_init(&options.deepen_not, 1);\n \tstring_list_init(&options.push_options, 1);\n \n+\t/*\n+\t * Just report \"remote-curl\" here (folding all the various aliases\n+\t * (\"git-remote-http\", \"git-remote-https\", and etc.) here since they\n+\t * are all just copies of the same actual executable.\n+\t */\n+\ttrace2_cmd_verb(\"remote-curl\");\n+\n \tremote = remote_get(argv[1]);\n \n \tif (argc > 2) {\ndiff --git a/repository.c b/repository.c\nindex 20c509a922..b8da4d0bbf 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -119,6 +119,8 @@ static int repo_init_gitdir(struct repository *repo, const char *gitdir)\n void repo_set_worktree(struct repository *repo, const char *path)\n {\n \trepo->worktree = real_pathdup(path, 1);\n+\n+\ttrace2_def_repo(repo);\n }\n \n static int read_and_verify_repository_format(struct repository_format *format,\ndiff --git a/repository.h b/repository.h\nindex 0e482b7d49..4e2ad6e0be 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -90,6 +90,9 @@ struct repository {\n \t/* Repository's current hash algorithm, as serialized on disk. */\n \tconst struct git_hash_algo *hash_algo;\n \n+\t/* A unique-id for tracing purposes. */\n+\tint trace2_repo_id;\n+\n \t/* Configurations */\n \n \t/* Indicate if a repository has a different 'commondir' from 'gitdir' */\ndiff --git a/run-command.c b/run-command.c\nindex 3db26b7b0e..3449db319b 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -219,9 +219,29 @@ static int exists_in_PATH(const char *file)\n \n int sane_execvp(const char *file, char * const argv[])\n {\n+#ifndef GIT_WINDOWS_NATIVE\n+\t/*\n+\t * execvp() doesn't return, so we all we can do is tell trace2\n+\t * what we are about to do and let it leave a hint in the log\n+\t * (unless of course the execvp() fails).\n+\t *\n+\t * we skip this for Windows because the compat layer already\n+\t * has to emulate the execvp() call anyway.\n+\t */\n+\tint exec_id = trace2_exec(file, (const char **)argv);\n+#endif\n+\n \tif (!execvp(file, argv))\n \t\treturn 0; /* cannot happen ;-) */\n \n+#ifndef GIT_WINDOWS_NATIVE\n+\t{\n+\t\tint ec = errno;\n+\t\ttrace2_exec_result(exec_id, ec);\n+\t\terrno = ec;\n+\t}\n+#endif\n+\n \t/*\n \t * When a command can't be found because one of the directories\n \t * listed in $PATH is unsearchable, execvp reports EACCES, but\n@@ -712,6 +732,7 @@ int start_command(struct child_process *cmd)\n \t\tcmd->err = fderr[0];\n \t}\n \n+\ttrace2_child_start(cmd);\n \ttrace_run_command(cmd);\n \n \tfflush(NULL);\n@@ -926,6 +947,8 @@ int start_command(struct child_process *cmd)\n #endif\n \n \tif (cmd->pid < 0) {\n+\t\ttrace2_child_exit(cmd, -1);\n+\n \t\tif (need_in)\n \t\t\tclose_pair(fdin);\n \t\telse if (cmd->in)\n@@ -964,13 +987,16 @@ int start_command(struct child_process *cmd)\n int finish_command(struct child_process *cmd)\n {\n \tint ret = wait_or_whine(cmd->pid, cmd->argv[0], 0);\n+\ttrace2_child_exit(cmd, ret);\n \tchild_process_clear(cmd);\n \treturn ret;\n }\n \n int finish_command_in_signal(struct child_process *cmd)\n {\n-\treturn wait_or_whine(cmd->pid, cmd->argv[0], 1);\n+\tint ret = wait_or_whine(cmd->pid, cmd->argv[0], 1);\n+\ttrace2_child_exit(cmd, ret);\n+\treturn ret;\n }\n \n \n@@ -992,7 +1018,18 @@ int run_command_v_opt(const char **argv, int opt)\n \treturn run_command_v_opt_cd_env(argv, opt, NULL, NULL);\n }\n \n+int run_command_v_opt_tr2(const char **argv, int opt, const char *tr2_class)\n+{\n+\treturn run_command_v_opt_cd_env_tr2(argv, opt, NULL, NULL, tr2_class);\n+}\n+\n int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env)\n+{\n+\treturn run_command_v_opt_cd_env_tr2(argv, opt, dir, env, NULL);\n+}\n+\n+int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,\n+\t\t\t\t const char *const *env, const char *tr2_class)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \tcmd.argv = argv;\n@@ -1004,6 +1041,7 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const\n \tcmd.clean_on_exit = opt & RUN_CLEAN_ON_EXIT ? 1 : 0;\n \tcmd.dir = dir;\n \tcmd.env = env;\n+\tcmd.trace2_child_class = tr2_class;\n \treturn run_command(&cmd);\n }\n \n@@ -1319,6 +1357,7 @@ int run_hook_ve(const char *const *env, const char *name, va_list args)\n \thook.env = env;\n \thook.no_stdin = 1;\n \thook.stdout_to_stderr = 1;\n+\thook.trace2_hook_name = name;\n \n \treturn run_command(&hook);\n }\n@@ -1807,3 +1846,21 @@ int run_processes_parallel(int n,\n \tpp_cleanup(&pp);\n \treturn 0;\n }\n+\n+int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task,\n+\t\t\t       start_failure_fn start_failure,\n+\t\t\t       task_finished_fn task_finished, void *pp_cb,\n+\t\t\t       const char *tr2_category, const char *tr2_label)\n+{\n+\tint result;\n+\n+\ttrace2_region_enter_printf(tr2_category, tr2_label, NULL, \"max:%d\",\n+\t\t\t\t   ((n < 1) ? online_cpus() : n));\n+\n+\tresult = run_processes_parallel(n, get_next_task, start_failure,\n+\t\t\t\t\ttask_finished, pp_cb);\n+\n+\ttrace2_region_leave(tr2_category, tr2_label, NULL);\n+\n+\treturn result;\n+}\ndiff --git a/run-command.h b/run-command.h\nindex 68f5369fc2..a6950691c0 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -10,6 +10,12 @@ struct child_process {\n \tstruct argv_array args;\n \tstruct argv_array env_array;\n \tpid_t pid;\n+\n+\tint trace2_child_id;\n+\tuint64_t trace2_child_us_start;\n+\tconst char *trace2_child_class;\n+\tconst char *trace2_hook_name;\n+\n \t/*\n \t * Using .in, .out, .err:\n \t * - Specify 0 for no redirections (child inherits stdin, stdout,\n@@ -73,12 +79,14 @@ extern int run_hook_ve(const char *const *env, const char *name, va_list args);\n #define RUN_USING_SHELL 16\n #define RUN_CLEAN_ON_EXIT 32\n int run_command_v_opt(const char **argv, int opt);\n-\n+int run_command_v_opt_tr2(const char **argv, int opt, const char *tr2_class);\n /*\n  * env (the environment) is to be formatted like environ: \"VAR=VALUE\".\n  * To unset an environment variable use just \"VAR\".\n  */\n int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env);\n+int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,\n+\t\t\t\t const char *const *env, const char *tr2_class);\n \n /**\n  * Execute the given command, sending \"in\" to its stdin, and capturing its\n@@ -220,5 +228,8 @@ int run_processes_parallel(int n,\n \t\t\t   start_failure_fn,\n \t\t\t   task_finished_fn,\n \t\t\t   void *pp_cb);\n+int run_processes_parallel_tr2(int n, get_next_task_fn, start_failure_fn,\n+\t\t\t       task_finished_fn, void *pp_cb,\n+\t\t\t       const char *tr2_category, const char *tr2_label);\n \n #endif\ndiff --git a/sh-i18n--envsubst.c b/sh-i18n--envsubst.c\nindex 09c6b445b9..2296ba1a2d 100644\n--- a/sh-i18n--envsubst.c\n+++ b/sh-i18n--envsubst.c\n@@ -14,6 +14,7 @@\n  */\n \n #include \"git-compat-util.h\"\n+#include \"trace2.h\"\n \n /* Substitution of environment variables in shell format strings.\n    Copyright (C) 2003-2007 Free Software Foundation, Inc.\n@@ -67,6 +68,8 @@ cmd_main (int argc, const char *argv[])\n   /* Default values for command line options.  */\n   /* unsigned short int show_variables = 0; */\n \n+  trace2_cmd_verb(\"sh-i18n--envsubst\");\n+\n   switch (argc)\n \t{\n \tcase 1:\ndiff --git a/submodule.c b/submodule.c\nindex 7b5cea8522..a01c9dccf6 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1568,11 +1568,12 @@ int fetch_populated_submodules(struct repository *r,\n \n \tcalculate_changed_submodule_paths(r, &spf.changed_submodule_names);\n \tstring_list_sort(&spf.changed_submodule_names);\n-\trun_processes_parallel(max_parallel_jobs,\n-\t\t\t       get_next_submodule,\n-\t\t\t       fetch_start_failure,\n-\t\t\t       fetch_finish,\n-\t\t\t       &spf);\n+\trun_processes_parallel_tr2(max_parallel_jobs,\n+\t\t\t\t   get_next_submodule,\n+\t\t\t\t   fetch_start_failure,\n+\t\t\t\t   fetch_finish,\n+\t\t\t\t   &spf,\n+\t\t\t\t   \"submodule\", \"parallel/fetch\");\n \n \targv_array_clear(&spf.args);\n out:\ndiff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c\nindex 47fee660b8..5318681739 100644\n--- a/t/helper/test-parse-options.c\n+++ b/t/helper/test-parse-options.c\n@@ -2,6 +2,7 @@\n #include \"cache.h\"\n #include \"parse-options.h\"\n #include \"string-list.h\"\n+#include \"trace2.h\"\n \n static int boolean = 0;\n static int integer = 0;\n@@ -153,6 +154,8 @@ int cmd__parse_options(int argc, const char **argv)\n \tint i;\n \tint ret = 0;\n \n+\ttrace2_cmd_verb(\"_parse_\");\n+\n \targc = parse_options(argc, (const char **)argv, prefix, options, usage, 0);\n \n \tif (length_cb.called) {\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 5b137874e1..0a704fe8d2 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -1,5 +1,6 @@\n #include \"git-compat-util.h\"\n #include \"test-tool.h\"\n+#include \"trace2.h\"\n \n struct test_cmd {\n \tconst char *name;\n@@ -80,6 +81,8 @@ int cmd_main(int argc, const char **argv)\n \t\tif (!strcmp(cmds[i].name, argv[1])) {\n \t\t\targv++;\n \t\t\targc--;\n+\t\t\ttrace2_cmd_verb(cmds[i].name);\n+\t\t\ttrace2_cmd_list_config();\n \t\t\treturn cmds[i].fn(argc, argv);\n \t\t}\n \t}\ndiff --git a/t/t0001-init.sh b/t/t0001-init.sh\nindex 42a263cada..5e27604b24 100755\n--- a/t/t0001-init.sh\n+++ b/t/t0001-init.sh\n@@ -93,6 +93,7 @@ test_expect_success 'No extra GIT_* on alias scripts' '\n \t\tsed -n \\\n \t\t\t-e \"/^GIT_PREFIX=/d\" \\\n \t\t\t-e \"/^GIT_TEXTDOMAINDIR=/d\" \\\n+\t\t\t-e \"/^GIT_TR2_PARENT/d\" \\\n \t\t\t-e \"/^GIT_/s/=.*//p\" |\n \t\tsort\n \tEOF\ndiff --git a/trace2.c b/trace2.c\nnew file mode 100644\nindex 0000000000..bff1da327e\n--- /dev/null\n+++ b/trace2.c\n@@ -0,0 +1,807 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"json-writer.h\"\n+#include \"quote.h\"\n+#include \"run-command.h\"\n+#include \"sigchain.h\"\n+#include \"thread-utils.h\"\n+#include \"version.h\"\n+#include \"trace2/tr2_cfg.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_sid.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+#include \"trace2/tr2_verb.h\"\n+\n+static int trace2_enabled;\n+\n+static int tr2_next_child_id; /* modify under lock */\n+static int tr2_next_exec_id; /* modify under lock */\n+static int tr2_next_repo_id = 1; /* modify under lock. zero is reserved */\n+\n+/*\n+ * A table of the builtin TRACE2 targets.  Each of these may be independently\n+ * enabled or disabled.  Each TRACE2 API method will try to write an event to\n+ * *each* of the enabled targets.\n+ */\n+static struct tr2_tgt *tr2_tgt_builtins[] =\n+{\n+\t&tr2_tgt_normal,\n+\t&tr2_tgt_perf,\n+\t&tr2_tgt_event,\n+\tNULL\n+};\n+\n+#define for_each_builtin(j, tgt_j)\t\t\t\\\n+\tfor (j = 0, tgt_j = tr2_tgt_builtins[j];\t\\\n+\t     tgt_j;\t\t\t\t\t\\\n+\t     j++, tgt_j = tr2_tgt_builtins[j])\n+\n+#define for_each_wanted_builtin(j, tgt_j) \\\n+\tfor_each_builtin(j, tgt_j) if (tr2_dst_trace_want(tgt_j->pdst))\n+\n+/*\n+ * Force (rather than lazily) initialize any of the requested\n+ * builtin TRACE2 targets at startup (and before we've seen an\n+ * actual TRACE2 event call) so we can see if we need to setup\n+ * the TR2 and TLS machinery.\n+ *\n+ * Return the number of builtin targets enabled.\n+ */\n+static int tr2_tgt_want_builtins(void)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tint sum = 0;\n+\n+\tfor_each_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_init())\n+\t\t\tsum++;\n+\t}\n+\n+\treturn sum;\n+}\n+\n+/*\n+ * Properly terminate each builtin target.  Give each target\n+ * a chance to write a summary event and/or flush if necessary\n+ * and then close the fd.\n+ */\n+static void tr2_tgt_disable_builtins(void)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tfor_each_builtin(j, tgt_j)\n+\t{\n+\t\ttgt_j->pfn_term();\n+\t}\n+}\n+\n+static int tr2main_exit_code;\n+\n+/*\n+ * Our atexit routine should run after everything has finished.\n+ *\n+ * Note that events generated here might not actually appear if\n+ * we are writing to fd 1 or 2 and our atexit routine runs after\n+ * the pager's atexit routine (since it closes them to shutdown\n+ * the pipes).\n+ */\n+static void tr2main_atexit_handler(void)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Clear any unbalanced regions so that our atexit message\n+\t * does not appear nested.  This improves the appearance of\n+\t * the trace output if someone calls die(), for example.\n+\t */\n+\ttr2tls_pop_unwind_self();\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_atexit)\n+\t\t\ttgt_j->pfn_atexit(us_elapsed_absolute,\n+\t\t\t\t\t  tr2main_exit_code);\n+\t}\n+\n+\ttr2_tgt_disable_builtins();\n+\n+\ttr2tls_release();\n+\ttr2_sid_release();\n+\ttr2_verb_release();\n+\ttr2_cfg_free_patterns();\n+\n+\ttrace2_enabled = 0;\n+}\n+\n+static void tr2main_signal_handler(int signo)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_signal)\n+\t\t\ttgt_j->pfn_signal(us_elapsed_absolute, signo);\n+\t}\n+\n+\tsigchain_pop(signo);\n+\traise(signo);\n+}\n+\n+void trace2_initialize_fl(const char *file, int line)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (trace2_enabled)\n+\t\treturn;\n+\n+\tif (!tr2_tgt_want_builtins())\n+\t\treturn;\n+\ttrace2_enabled = 1;\n+\n+\ttr2_sid_get();\n+\n+\tatexit(tr2main_atexit_handler);\n+\tsigchain_push(SIGPIPE, tr2main_signal_handler);\n+\ttr2tls_init();\n+\n+\t/*\n+\t * Emit 'version' message on each active builtin target.\n+\t */\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_version_fl)\n+\t\t\ttgt_j->pfn_version_fl(file, line);\n+\t}\n+}\n+\n+int trace2_is_enabled(void)\n+{\n+\treturn trace2_enabled;\n+}\n+\n+void trace2_cmd_start_fl(const char *file, int line, const char **argv)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_start_fl)\n+\t\t\ttgt_j->pfn_start_fl(file, line, argv);\n+\t}\n+}\n+\n+int trace2_cmd_exit_fl(const char *file, int line, int code)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tcode &= 0xff;\n+\n+\tif (!trace2_enabled)\n+\t\treturn code;\n+\n+\ttr2main_exit_code = code;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_exit_fl)\n+\t\t\ttgt_j->pfn_exit_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t   code);\n+\t}\n+\n+\treturn code;\n+}\n+\n+void trace2_cmd_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t    va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\t/*\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy (because an 'ap' can only be walked once).\n+\t */\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_error_va_fl)\n+\t\t\ttgt_j->pfn_error_va_fl(file, line, fmt, ap);\n+\t}\n+}\n+\n+void trace2_cmd_path_fl(const char *file, int line, const char *pathname)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_command_path_fl)\n+\t\t\ttgt_j->pfn_command_path_fl(file, line, pathname);\n+\t}\n+}\n+\n+void trace2_cmd_verb_fl(const char *file, int line, const char *command_verb)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tconst char *hierarchy;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\ttr2_verb_append_hierarchy(command_verb);\n+\thierarchy = tr2_verb_get_hierarchy();\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_command_verb_fl)\n+\t\t\ttgt_j->pfn_command_verb_fl(file, line, command_verb,\n+\t\t\t\t\t\t   hierarchy);\n+\t}\n+}\n+\n+void trace2_cmd_subverb_fl(const char *file, int line,\n+\t\t\t   const char *command_subverb)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_command_subverb_fl)\n+\t\t\ttgt_j->pfn_command_subverb_fl(file, line,\n+\t\t\t\t\t\t      command_subverb);\n+\t}\n+}\n+\n+void trace2_cmd_alias_fl(const char *file, int line, const char *alias,\n+\t\t\t const char **argv)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_alias_fl)\n+\t\t\ttgt_j->pfn_alias_fl(file, line, alias, argv);\n+\t}\n+}\n+\n+void trace2_cmd_list_config_fl(const char *file, int line)\n+{\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\ttr2_cfg_list_config_fl(file, line);\n+}\n+\n+void trace2_cmd_set_config_fl(const char *file, int line, const char *key,\n+\t\t\t      const char *value)\n+{\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\ttr2_cfg_set_fl(file, line, key, value);\n+}\n+\n+void trace2_child_start_fl(const char *file, int line,\n+\t\t\t   struct child_process *cmd)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tcmd->trace2_child_id = tr2tls_locked_increment(&tr2_next_child_id);\n+\tcmd->trace2_child_us_start = us_now;\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_child_start_fl)\n+\t\t\ttgt_j->pfn_child_start_fl(file, line,\n+\t\t\t\t\t\t  us_elapsed_absolute, cmd);\n+\t}\n+}\n+\n+void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd,\n+\t\t\t  int child_exit_code)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_child;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tif (cmd->trace2_child_us_start)\n+\t\tus_elapsed_child = us_now - cmd->trace2_child_us_start;\n+\telse\n+\t\tus_elapsed_child = 0;\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_child_exit_fl)\n+\t\t\ttgt_j->pfn_child_exit_fl(file, line,\n+\t\t\t\t\t\t us_elapsed_absolute,\n+\t\t\t\t\t\t cmd->trace2_child_id, cmd->pid,\n+\t\t\t\t\t\t child_exit_code,\n+\t\t\t\t\t\t us_elapsed_child);\n+\t}\n+}\n+\n+int trace2_exec_fl(const char *file, int line, const char *exe,\n+\t\t   const char **argv)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tint exec_id;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn -1;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\texec_id = tr2tls_locked_increment(&tr2_next_exec_id);\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_exec_fl)\n+\t\t\ttgt_j->pfn_exec_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t   exec_id, exe, argv);\n+\t}\n+\n+\treturn exec_id;\n+}\n+\n+void trace2_exec_result_fl(const char *file, int line, int exec_id, int code)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_exec_result_fl)\n+\t\t\ttgt_j->pfn_exec_result_fl(\n+\t\t\t\tfile, line, us_elapsed_absolute, exec_id, code);\n+\t}\n+}\n+\n+void trace2_thread_start_fl(const char *file, int line, const char *thread_name)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tif (tr2tls_is_main_thread()) {\n+\t\t/*\n+\t\t * We should only be called from the new thread's thread-proc,\n+\t\t * so this is technically a bug.  But in those cases where the\n+\t\t * main thread also runs the thread-proc function (or when we\n+\t\t * are built with threading disabled), we need to allow it.\n+\t\t *\n+\t\t * Convert this call to a region-enter so the nesting looks\n+\t\t * correct.\n+\t\t */\n+\t\ttrace2_region_enter_printf_fl(file, line, NULL, NULL, NULL,\n+\t\t\t\t\t      \"thread-proc on main: %s\",\n+\t\t\t\t\t      thread_name);\n+\t\treturn;\n+\t}\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\ttr2tls_create_self(thread_name);\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_thread_start_fl)\n+\t\t\ttgt_j->pfn_thread_start_fl(file, line,\n+\t\t\t\t\t\t   us_elapsed_absolute);\n+\t}\n+}\n+\n+void trace2_thread_exit_fl(const char *file, int line)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_thread;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tif (tr2tls_is_main_thread()) {\n+\t\t/*\n+\t\t * We should only be called from the exiting thread's\n+\t\t * thread-proc, so this is technically a bug.  But in\n+\t\t * those cases where the main thread also runs the\n+\t\t * thread-proc function (or when we are built with\n+\t\t * threading disabled), we need to allow it.\n+\t\t *\n+\t\t * Convert this call to a region-leave so the nesting\n+\t\t * looks correct.\n+\t\t */\n+\t\ttrace2_region_leave_printf_fl(file, line, NULL, NULL, NULL,\n+\t\t\t\t\t      \"thread-proc on main\");\n+\t\treturn;\n+\t}\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Clear any unbalanced regions and then get the relative time\n+\t * for the outer-most region (which we pushed when the thread\n+\t * started).  This gives us the run time of the thread.\n+\t */\n+\ttr2tls_pop_unwind_self();\n+\tus_elapsed_thread = tr2tls_region_elasped_self(us_now);\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_thread_exit_fl)\n+\t\t\ttgt_j->pfn_thread_exit_fl(file, line,\n+\t\t\t\t\t\t  us_elapsed_absolute,\n+\t\t\t\t\t\t  us_elapsed_thread);\n+\t}\n+\n+\ttr2tls_unset_self();\n+}\n+\n+void trace2_def_param_fl(const char *file, int line, const char *param,\n+\t\t\t const char *value)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_param_fl)\n+\t\t\ttgt_j->pfn_param_fl(file, line, param, value);\n+\t}\n+}\n+\n+void trace2_def_repo_fl(const char *file, int line, struct repository *repo)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tif (repo->trace2_repo_id)\n+\t\treturn;\n+\n+\trepo->trace2_repo_id = tr2tls_locked_increment(&tr2_next_repo_id);\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_repo_fl)\n+\t\t\ttgt_j->pfn_repo_fl(file, line, repo);\n+\t}\n+}\n+\n+void trace2_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category, const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Print the region-enter message at the current nesting\n+\t * (indentation) level and then push a new level.\n+\t *\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy.\n+\t */\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_region_enter_printf_va_fl)\n+\t\t\ttgt_j->pfn_region_enter_printf_va_fl(\n+\t\t\t\tfile, line, us_elapsed_absolute, category,\n+\t\t\t\tlabel, repo, fmt, ap);\n+\t}\n+\n+\ttr2tls_push_self(us_now);\n+}\n+\n+void trace2_region_enter_fl(const char *file, int line, const char *category,\n+\t\t\t    const char *label, const struct repository *repo)\n+{\n+\ttrace2_region_enter_printf_va_fl(file, line, category, label, repo,\n+\t\t\t\t\t NULL, NULL);\n+}\n+\n+void trace2_region_enter_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category, const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_enter_printf_va_fl(file, line, category, label, repo, fmt,\n+\t\t\t\t\t ap);\n+\tva_end(ap);\n+}\n+\n+#ifndef HAVE_VARIADIC_MACROS\n+void trace2_region_enter_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo, const char *fmt,\n+\t\t\t\t...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_enter_printf_va_fl(NULL, 0, category, label, repo, fmt,\n+\t\t\t\t\t ap);\n+\tva_end(ap);\n+}\n+#endif\n+\n+void trace2_region_leave_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category, const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_region;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Get the elapsed time in the current region before we\n+\t * pop it off the stack.  Pop the stack.  And then print\n+\t * the perf message at the new (shallower) level so that\n+\t * it lines up with the corresponding push/enter.\n+\t */\n+\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n+\n+\ttr2tls_pop_self();\n+\n+\t/*\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy.\n+\t */\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_region_leave_printf_va_fl)\n+\t\t\ttgt_j->pfn_region_leave_printf_va_fl(\n+\t\t\t\tfile, line, us_elapsed_absolute,\n+\t\t\t\tus_elapsed_region, category, label, repo, fmt,\n+\t\t\t\tap);\n+\t}\n+}\n+\n+void trace2_region_leave_fl(const char *file, int line, const char *category,\n+\t\t\t    const char *label, const struct repository *repo)\n+{\n+\ttrace2_region_leave_printf_va_fl(file, line, category, label, repo,\n+\t\t\t\t\t NULL, NULL);\n+}\n+\n+void trace2_region_leave_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category, const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_leave_printf_va_fl(file, line, category, label, repo, fmt,\n+\t\t\t\t\t ap);\n+\tva_end(ap);\n+}\n+\n+#ifndef HAVE_VARIADIC_MACROS\n+void trace2_region_leave_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo, const char *fmt,\n+\t\t\t\t...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_leave_printf_va_fl(NULL, 0, category, label, repo, fmt,\n+\t\t\t\t\t ap);\n+\tva_end(ap);\n+}\n+#endif\n+\n+void trace2_data_string_fl(const char *file, int line, const char *category,\n+\t\t\t   const struct repository *repo, const char *key,\n+\t\t\t   const char *value)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_region;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_data_fl)\n+\t\t\ttgt_j->pfn_data_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t   us_elapsed_region, category, repo,\n+\t\t\t\t\t   key, value);\n+\t}\n+}\n+\n+void trace2_data_intmax_fl(const char *file, int line, const char *category,\n+\t\t\t   const struct repository *repo, const char *key,\n+\t\t\t   intmax_t value)\n+{\n+\tstruct strbuf buf_string = STRBUF_INIT;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tstrbuf_addf(&buf_string, \"%\" PRIdMAX, value);\n+\ttrace2_data_string_fl(file, line, category, repo, key, buf_string.buf);\n+\tstrbuf_release(&buf_string);\n+}\n+\n+void trace2_data_json_fl(const char *file, int line, const char *category,\n+\t\t\t const struct repository *repo, const char *key,\n+\t\t\t const struct json_writer *value)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_region;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_data_fl)\n+\t\t\ttgt_j->pfn_data_json_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t\tus_elapsed_region, category,\n+\t\t\t\t\t\trepo, key, value);\n+\t}\n+}\n+\n+void trace2_printf_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy.\n+\t */\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_printf_va_fl)\n+\t\t\ttgt_j->pfn_printf_va_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t\tfmt, ap);\n+\t}\n+}\n+\n+void trace2_printf_fl(const char *file, int line, const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_printf_va_fl(file, line, fmt, ap);\n+\tva_end(ap);\n+}\n+\n+#ifndef HAVE_VARIADIC_MACROS\n+void trace2_printf(const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_printf_va_fl(NULL, 0, fmt, ap);\n+\tva_end(ap);\n+}\n+#endif\ndiff --git a/trace2.h b/trace2.h\nnew file mode 100644\nindex 0000000000..a0e99d9c26\n--- /dev/null\n+++ b/trace2.h\n@@ -0,0 +1,366 @@\n+#ifndef TRACE2_H\n+#define TRACE2_H\n+\n+struct child_process;\n+struct repository;\n+struct json_writer;\n+\n+/*\n+ * The public TRACE2 routines are grouped into the following groups:\n+ *\n+ * [] trace2_initialize -- initialization.\n+ * [] trace2_cmd_*      -- emit command/control messages.\n+ * [] trace2_child*     -- emit child start/stop messages.\n+ * [] trace2_exec*      -- emit exec start/stop messages.\n+ * [] trace2_thread*    -- emit thread start/stop messages.\n+ * [] trace2_def*       -- emit definition/parameter mesasges.\n+ * [] trace2_region*    -- emit region nesting messages.\n+ * [] trace2_data*      -- emit region/thread/repo data messages.\n+ * [] trace2_printf*    -- legacy trace[1] messages.\n+ */\n+\n+/*\n+ * Initialize TRACE2 tracing facility if any of the builtin TRACE2\n+ * targets are enabled in the environment.  Emits a 'version' event.\n+ *\n+ * Cleanup/Termination is handled automatically by a registered\n+ * atexit() routine.\n+ */\n+void trace2_initialize_fl(const char *file, int line);\n+\n+#define trace2_initialize() trace2_initialize_fl(__FILE__, __LINE__)\n+\n+/*\n+ * Return true if trace2 is enabled.\n+ */\n+int trace2_is_enabled(void);\n+\n+/*\n+ * Emit a 'start' event with the original (unmodified) argv.\n+ */\n+void trace2_cmd_start_fl(const char *file, int line, const char **argv);\n+\n+#define trace2_cmd_start(argv) trace2_cmd_start_fl(__FILE__, __LINE__, (argv))\n+\n+/*\n+ * Emit an 'exit' event.\n+ *\n+ * Write the exit-code that will be passed to exit() or returned\n+ * from main().\n+ *\n+ * Use this prior to actually calling exit().\n+ * See \"#define exit()\" in git-compat-util.h\n+ */\n+int trace2_cmd_exit_fl(const char *file, int line, int code);\n+\n+#define trace2_cmd_exit(code) (trace2_cmd_exit_fl(__FILE__, __LINE__, (code)))\n+\n+/*\n+ * Emit an 'error' event.\n+ *\n+ * Write an error message to the TRACE2 targets.\n+ */\n+void trace2_cmd_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t    va_list ap);\n+\n+#define trace2_cmd_error_va(fmt, ap) \\\n+\ttrace2_cmd_error_va_fl(__FILE__, __LINE__, (fmt), (ap))\n+\n+/*\n+ * Emit a 'pathname' event with the canonical pathname of the current process\n+ * This gives post-processors a simple field to identify the command without\n+ * having to parse the argv.  For example, to distinguish invocations from\n+ * installed versus debug executables.\n+ */\n+void trace2_cmd_path_fl(const char *file, int line, const char *pathname);\n+\n+#define trace2_cmd_path(p) trace2_cmd_path_fl(__FILE__, __LINE__, (p))\n+\n+/*\n+ * Emit a 'cmd_verb' event with the canonical name of the (usually)\n+ * builtin command.  This gives post-processors a simple field\n+ * to identify the command verb without having to parse the argv.\n+ */\n+void trace2_cmd_verb_fl(const char *file, int line, const char *command_verb);\n+\n+#define trace2_cmd_verb(v) trace2_cmd_verb_fl(__FILE__, __LINE__, (v))\n+\n+/*\n+ * Emit a 'cmd_subverb' event to further describe the command being run.\n+ * For example, \"checkout\" can checkout a single file or can checkout a\n+ * different branch.  This gives post-processors a simple field to compare\n+ * equivalent commands without having to parse the argv.\n+ */\n+void trace2_cmd_subverb_fl(const char *file, int line,\n+\t\t\t   const char *command_subverb);\n+\n+#define trace2_cmd_subverb(sv) trace2_cmd_subverb_fl(__FILE__, __LINE__, (sv))\n+\n+/*\n+ * Emit an 'alias' expansion event.\n+ */\n+void trace2_cmd_alias_fl(const char *file, int line, const char *alias,\n+\t\t\t const char **argv);\n+\n+#define trace2_cmd_alias(alias, argv) \\\n+\ttrace2_cmd_alias_fl(__FILE__, __LINE__, (alias), (argv))\n+\n+/*\n+ * Emit one or more 'def_param' events for \"interesting\" configuration\n+ * settings.\n+ *\n+ * The environment variable \"GIT_TR2_CONFIG_PARAMS\" can be set to a\n+ * list of patterns considered important.  For example:\n+ *\n+ *    GIT_TR2_CONFIG_PARAMS=\"core.*,remote.*.url\"\n+ *\n+ * Note: this routine does a read-only iteration on the config data\n+ * (using read_early_config()), so it must not be called until enough\n+ * of the process environment has been established.  This includes the\n+ * location of the git and worktree directories, expansion of any \"-c\"\n+ * and \"-C\" command line options, and etc.\n+ */\n+void trace2_cmd_list_config_fl(const char *file, int line);\n+\n+#define trace2_cmd_list_config() trace2_cmd_list_config_fl(__FILE__, __LINE__)\n+\n+/*\n+ * Emit a \"def_param\" event for the given config key/value pair IF\n+ * we consider the key to be \"interesting\".\n+ *\n+ * Use this for new/updated config settings created/updated after\n+ * trace2_cmd_list_config() is called.\n+ */\n+void trace2_cmd_set_config_fl(const char *file, int line, const char *key,\n+\t\t\t      const char *value);\n+\n+#define trace2_cmd_set_config(k, v) \\\n+\ttrace2_cmd_set_config_fl(__FILE__, __LINE__, (k), (v))\n+\n+/*\n+ * Emit a 'child_start' event prior to spawning a child process.\n+ *\n+ * Before calling optionally set \"cmd->trace2_child_class\" to a string\n+ * describing the type of the child process.  For example, \"editor\" or\n+ * \"pager\".\n+ */\n+void trace2_child_start_fl(const char *file, int line,\n+\t\t\t   struct child_process *cmd);\n+\n+#define trace2_child_start(cmd) trace2_child_start_fl(__FILE__, __LINE__, (cmd))\n+\n+/*\n+ * Emit a 'child_exit' event after the child process completes.\n+ */\n+void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd,\n+\t\t\t  int child_exit_code);\n+\n+#define trace2_child_exit(cmd, code) \\\n+\ttrace2_child_exit_fl(__FILE__, __LINE__, (cmd), (code))\n+\n+/*\n+ * Emit an 'exec' event prior to calling one of exec(), execv(),\n+ * execvp(), and etc.  On Unix-derived systems, this will be the\n+ * last event emitted for the current process, unless the exec\n+ * fails.  On Windows, exec() behaves like 'child_start' and a\n+ * waitpid(), so additional events may be emitted.\n+ *\n+ * Returns the \"exec_id\".\n+ */\n+int trace2_exec_fl(const char *file, int line, const char *exe,\n+\t\t   const char **argv);\n+\n+#define trace2_exec(exe, argv) trace2_exec_fl(__FILE__, __LINE__, (exe), (argv))\n+\n+/*\n+ * Emit an 'exec_result' when possible.  On Unix-derived systems,\n+ * this should be called after exec() returns (which only happens\n+ * when there is an error starting the new process).  On Windows,\n+ * this should be called after the waitpid().\n+ *\n+ * The \"exec_id\" should be the value returned from trace2_exec().\n+ */\n+void trace2_exec_result_fl(const char *file, int line, int exec_id, int code);\n+\n+#define trace2_exec_result(id, code) \\\n+\ttrace2_exec_result_fl(__FILE__, __LINE__, (id), (code))\n+\n+/*\n+ * Emit a 'thread_start' event.  This must be called from inside the\n+ * thread-proc to set up the trace2 TLS data for the thread.\n+ *\n+ * Thread names should be descriptive, like \"preload_index\".\n+ * Thread names will be decorated with an instance number automatically.\n+ */\n+void trace2_thread_start_fl(const char *file, int line,\n+\t\t\t    const char *thread_name);\n+\n+#define trace2_thread_start(thread_name) \\\n+\ttrace2_thread_start_fl(__FILE__, __LINE__, (thread_name))\n+\n+/*\n+ * Emit a 'thread_exit' event.  This must be called from inside the\n+ * thread-proc to report thread-specific data and cleanup TLS data\n+ * for the thread.\n+ */\n+void trace2_thread_exit_fl(const char *file, int line);\n+\n+#define trace2_thread_exit() trace2_thread_exit_fl(__FILE__, __LINE__)\n+\n+/*\n+ * Emit a 'param' event.\n+ *\n+ * Write a \"<param> = <value>\" pair describing some aspect of the\n+ * run such as an important configuration setting or command line\n+ * option that significantly changes command behavior.\n+ */\n+void trace2_def_param_fl(const char *file, int line, const char *param,\n+\t\t\t const char *value);\n+\n+#define trace2_def_param(param, value) \\\n+\ttrace2_def_param_fl(__FILE__, __LINE__, (param), (value))\n+\n+/*\n+ * Tell trace2 about a newly instantiated repo object and assign\n+ * a trace2-repo-id to be used in subsequent activity events.\n+ *\n+ * Emits a 'worktree' event for this repo instance.\n+ */\n+void trace2_def_repo_fl(const char *file, int line, struct repository *repo);\n+\n+#define trace2_def_repo(repo) trace2_def_repo_fl(__FILE__, __LINE__, repo)\n+\n+/*\n+ * Emit a 'region_enter' event for <category>.<label> with optional\n+ * repo-id and printf message.\n+ *\n+ * Enter a new nesting level on the current thread and remember the\n+ * current time.  This controls the indenting of all subsequent events\n+ * on this thread.\n+ */\n+void trace2_region_enter_fl(const char *file, int line, const char *category,\n+\t\t\t    const char *label, const struct repository *repo);\n+\n+#define trace2_region_enter(category, label, repo) \\\n+\ttrace2_region_enter_fl(__FILE__, __LINE__, (category), (label), (repo))\n+\n+void trace2_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category, const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap);\n+\n+#define trace2_region_enter_printf_va(category, label, repo, fmt, ap)    \\\n+\ttrace2_region_enter_printf_va_fl(__FILE__, __LINE__, (category), \\\n+\t\t\t\t\t (label), (repo), (fmt), (ap))\n+\n+void trace2_region_enter_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category, const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...);\n+\n+#ifdef HAVE_VARIADIC_MACROS\n+#define trace2_region_enter_printf(category, label, repo, ...)                 \\\n+\ttrace2_region_enter_printf_fl(__FILE__, __LINE__, (category), (label), \\\n+\t\t\t\t      (repo), __VA_ARGS__)\n+#else\n+__attribute__((format (region_enter_printf, 4, 5)))\n+void trace2_region_enter_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo, const char *fmt,\n+\t\t\t\t...);\n+#endif\n+\n+/*\n+ * Emit a 'region_leave' event for <category>.<label> with optional\n+ * repo-id and printf message.\n+ *\n+ * Leave current nesting level and report the elapsed time spent\n+ * in this nesting level.\n+ */\n+void trace2_region_leave_fl(const char *file, int line, const char *category,\n+\t\t\t    const char *label, const struct repository *repo);\n+\n+#define trace2_region_leave(category, label, repo) \\\n+\ttrace2_region_leave_fl(__FILE__, __LINE__, (category), (label), (repo))\n+\n+void trace2_region_leave_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category, const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap);\n+\n+#define trace2_region_leave_printf_va(category, label, repo, fmt, ap)    \\\n+\ttrace2_region_leave_printf_va_fl(__FILE__, __LINE__, (category), \\\n+\t\t\t\t\t (label), (repo), (fmt), (ap))\n+\n+void trace2_region_leave_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category, const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...);\n+\n+#ifdef HAVE_VARIADIC_MACROS\n+#define trace2_region_leave_printf(category, label, repo, ...)                 \\\n+\ttrace2_region_leave_printf_fl(__FILE__, __LINE__, (category), (label), \\\n+\t\t\t\t      (repo), __VA_ARGS__)\n+#else\n+__attribute__((format (region_leave_printf, 4, 5)))\n+void trace2_region_leave_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo, const char *fmt,\n+\t\t\t\t...);\n+#endif\n+\n+/*\n+ * Emit a key-value pair 'data' event of the form <category>.<key> = <value>.\n+ * This event implicitly contains information about thread, nesting region,\n+ * and optional repo-id.\n+ *\n+ * On event-based TRACE2 targets, this generates a 'data' event suitable\n+ * for post-processing.  On printf-based TRACE2 targets, this is converted\n+ * into a fixed-format printf message.\n+ */\n+void trace2_data_string_fl(const char *file, int line, const char *category,\n+\t\t\t   const struct repository *repo, const char *key,\n+\t\t\t   const char *value);\n+\n+#define trace2_data_string(category, repo, key, value)                       \\\n+\ttrace2_data_string_fl(__FILE__, __LINE__, (category), (repo), (key), \\\n+\t\t\t      (value))\n+\n+void trace2_data_intmax_fl(const char *file, int line, const char *category,\n+\t\t\t   const struct repository *repo, const char *key,\n+\t\t\t   intmax_t value);\n+\n+#define trace2_data_intmax(category, repo, key, value)                       \\\n+\ttrace2_data_intmax_fl(__FILE__, __LINE__, (category), (repo), (key), \\\n+\t\t\t      (value))\n+\n+void trace2_data_json_fl(const char *file, int line, const char *category,\n+\t\t\t const struct repository *repo, const char *key,\n+\t\t\t const struct json_writer *jw);\n+\n+#define trace2_data_json(category, repo, key, value)                       \\\n+\ttrace2_data_json_fl(__FILE__, __LINE__, (category), (repo), (key), \\\n+\t\t\t    (value))\n+\n+/*\n+ * Emit a 'printf' event.\n+ *\n+ * Write an arbitrary formatted message to the TRACE2 targets.  These\n+ * text messages should be considered as human-readable strings without\n+ * any formatting guidelines.  Post-processors may choose to ignore\n+ * them.\n+ */\n+void trace2_printf_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t va_list ap);\n+\n+#define trace2_printf_va(fmt, ap) \\\n+\ttrace2_printf_va_fl(__FILE__, __LINE__, (fmt), (ap))\n+\n+void trace2_printf_fl(const char *file, int line, const char *fmt, ...);\n+\n+#ifdef HAVE_VARIADIC_MACROS\n+#define trace2_printf(...) trace2_printf_fl(__FILE__, __LINE__, __VA_ARGS__)\n+#else\n+__attribute__((format (printf, 1, 2)))\n+void trace2_printf(const char *fmt, ...);\n+#endif\n+\n+#endif /* TRACE2_H */\ndiff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c\nnew file mode 100644\nindex 0000000000..b329921ac5\n--- /dev/null\n+++ b/trace2/tr2_cfg.c\n@@ -0,0 +1,90 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"tr2_cfg.h\"\n+\n+#define TR2_ENVVAR_CFG_PARAM \"GIT_TR2_CONFIG_PARAMS\"\n+\n+static struct strbuf **tr2_cfg_patterns;\n+static int tr2_cfg_count_patterns;\n+static int tr2_cfg_loaded;\n+\n+/*\n+ * Parse a string containing a comma-delimited list of config keys\n+ * or wildcard patterns into a list of strbufs.\n+ */\n+static int tr2_cfg_load_patterns(void)\n+{\n+\tstruct strbuf **s;\n+\tconst char *envvar;\n+\n+\tif (tr2_cfg_loaded)\n+\t\treturn tr2_cfg_count_patterns;\n+\ttr2_cfg_loaded = 1;\n+\n+\tenvvar = getenv(TR2_ENVVAR_CFG_PARAM);\n+\tif (!envvar || !*envvar)\n+\t\treturn tr2_cfg_count_patterns;\n+\n+\ttr2_cfg_patterns = strbuf_split_buf(envvar, strlen(envvar), ',', -1);\n+\tfor (s = tr2_cfg_patterns; *s; s++) {\n+\t\tstruct strbuf *buf = *s;\n+\n+\t\tif (buf->len && buf->buf[buf->len - 1] == ',')\n+\t\t\tstrbuf_setlen(buf, buf->len - 1);\n+\t\tstrbuf_trim_trailing_newline(*s);\n+\t\tstrbuf_trim(*s);\n+\t}\n+\n+\ttr2_cfg_count_patterns = s - tr2_cfg_patterns;\n+\treturn tr2_cfg_count_patterns;\n+}\n+\n+void tr2_cfg_free_patterns(void)\n+{\n+\tif (tr2_cfg_patterns)\n+\t\tstrbuf_list_free(tr2_cfg_patterns);\n+\ttr2_cfg_count_patterns = 0;\n+\ttr2_cfg_loaded = 0;\n+}\n+\n+struct tr2_cfg_data {\n+\tconst char *file;\n+\tint line;\n+};\n+\n+/*\n+ * See if the given config key matches any of our patterns of interest.\n+ */\n+static int tr2_cfg_cb(const char *key, const char *value, void *d)\n+{\n+\tstruct strbuf **s;\n+\tstruct tr2_cfg_data *data = (struct tr2_cfg_data *)d;\n+\n+\tfor (s = tr2_cfg_patterns; *s; s++) {\n+\t\tstruct strbuf *buf = *s;\n+\t\tint wm = wildmatch(buf->buf, key, WM_CASEFOLD);\n+\t\tif (wm == WM_MATCH) {\n+\t\t\ttrace2_def_param_fl(data->file, data->line, key, value);\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void tr2_cfg_list_config_fl(const char *file, int line)\n+{\n+\tstruct tr2_cfg_data data = { file, line };\n+\n+\tif (tr2_cfg_load_patterns() > 0)\n+\t\tread_early_config(tr2_cfg_cb, &data);\n+}\n+\n+void tr2_cfg_set_fl(const char *file, int line, const char *key,\n+\t\t    const char *value)\n+{\n+\tstruct tr2_cfg_data data = { file, line };\n+\n+\tif (tr2_cfg_load_patterns() > 0)\n+\t\ttr2_cfg_cb(key, value, &data);\n+}\ndiff --git a/trace2/tr2_cfg.h b/trace2/tr2_cfg.h\nnew file mode 100644\nindex 0000000000..d9c98f64dd\n--- /dev/null\n+++ b/trace2/tr2_cfg.h\n@@ -0,0 +1,19 @@\n+#ifndef TR2_CFG_H\n+#define TR2_CFG_H\n+\n+/*\n+ * Iterate over all config settings and emit 'def_param' events for the\n+ * \"interesting\" ones to TRACE2.\n+ */\n+void tr2_cfg_list_config_fl(const char *file, int line);\n+\n+/*\n+ * Emit a \"def_param\" event for the given key/value pair IF we consider\n+ * the key to be \"interesting\".\n+ */\n+void tr2_cfg_set_fl(const char *file, int line, const char *key,\n+\t\t    const char *value);\n+\n+void tr2_cfg_free_patterns(void);\n+\n+#endif /* TR2_CFG_H */\ndiff --git a/trace2/tr2_dst.c b/trace2/tr2_dst.c\nnew file mode 100644\nindex 0000000000..9503bf7d95\n--- /dev/null\n+++ b/trace2/tr2_dst.c\n@@ -0,0 +1,198 @@\n+#include \"cache.h\"\n+#include \"trace2/tr2_dst.h\"\n+\n+/*\n+ * If a Trace2 target cannot be opened for writing, we should issue a\n+ * warning to stderr, but this is very annoying if the target is a pipe\n+ * or socket and beyond the user's control -- especially since every\n+ * git command (and sub-command) will print the message.  So we silently\n+ * eat these warnings and just discard the trace data.\n+ *\n+ * Enable the following environment variable to see these warnings.\n+ */\n+#define TR2_ENVVAR_DST_DEBUG \"GIT_TR2_DST_DEBUG\"\n+\n+static int tr2_dst_want_warning(void)\n+{\n+\tstatic int tr2env_dst_debug = -1;\n+\n+\tif (tr2env_dst_debug == -1) {\n+\t\tconst char *env_value = getenv(TR2_ENVVAR_DST_DEBUG);\n+\t\tif (!env_value || !*env_value)\n+\t\t\ttr2env_dst_debug = 0;\n+\t\telse\n+\t\t\ttr2env_dst_debug = atoi(env_value) > 0;\n+\t}\n+\n+\treturn tr2env_dst_debug;\n+}\n+\n+void tr2_dst_trace_disable(struct tr2_dst *dst)\n+{\n+\tif (dst->need_close)\n+\t\tclose(dst->fd);\n+\tdst->fd = 0;\n+\tdst->initialized = 1;\n+\tdst->need_close = 0;\n+}\n+\n+static int tr2_dst_try_path(struct tr2_dst *dst, const char *tgt_value)\n+{\n+\tint fd = open(tgt_value, O_WRONLY | O_APPEND | O_CREAT, 0666);\n+\tif (fd == -1) {\n+\t\tif (tr2_dst_want_warning())\n+\t\t\twarning(\"trace2: could not open '%s' for '%s' tracing: %s\",\n+\t\t\t\ttgt_value, dst->env_var_name, strerror(errno));\n+\n+\t\ttr2_dst_trace_disable(dst);\n+\t\treturn 0;\n+\t}\n+\n+\tdst->fd = fd;\n+\tdst->need_close = 1;\n+\tdst->initialized = 1;\n+\n+\treturn dst->fd;\n+}\n+\n+#ifndef NO_UNIX_SOCKETS\n+#define PREFIX_AF_UNIX \"af_unix:\"\n+#define PREFIX_AF_UNIX_LEN (8)\n+\n+static int tr2_dst_try_unix_domain_socket(struct tr2_dst *dst,\n+\t\t\t\t\t  const char *tgt_value)\n+{\n+\tint fd;\n+\tstruct sockaddr_un sa;\n+\tconst char *path = tgt_value + PREFIX_AF_UNIX_LEN;\n+\tint path_len = strlen(path);\n+\n+\tif (!is_absolute_path(path) || path_len >= sizeof(sa.sun_path)) {\n+\t\tif (tr2_dst_want_warning())\n+\t\t\twarning(\"trace2: invalid AF_UNIX path '%s' for '%s' tracing\",\n+\t\t\t\tpath, dst->env_var_name);\n+\n+\t\ttr2_dst_trace_disable(dst);\n+\t\treturn 0;\n+\t}\n+\n+\tsa.sun_family = AF_UNIX;\n+\tstrlcpy(sa.sun_path, path, sizeof(sa.sun_path));\n+\tif ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1 ||\n+\t    connect(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {\n+\t\tif (tr2_dst_want_warning())\n+\t\t\twarning(\"trace2: could not connect to socket '%s' for '%s' tracing: %s\",\n+\t\t\t\tpath, dst->env_var_name, strerror(errno));\n+\n+\t\ttr2_dst_trace_disable(dst);\n+\t\treturn 0;\n+\t}\n+\n+\tdst->fd = fd;\n+\tdst->need_close = 1;\n+\tdst->initialized = 1;\n+\n+\treturn dst->fd;\n+}\n+#endif\n+\n+static void tr2_dst_malformed_warning(struct tr2_dst *dst,\n+\t\t\t\t      const char *tgt_value)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf, \"trace2: unknown trace value for '%s': '%s'\",\n+\t\t    dst->env_var_name, tgt_value);\n+\tstrbuf_addstr(\n+\t\t&buf,\n+\t\t\"\\n         If you want to trace into a file, then please set it\"\n+\t\t\"\\n         to an absolute pathname.\");\n+#ifndef NO_UNIX_SOCKETS\n+\tstrbuf_addstr(\n+\t\t&buf,\n+\t\t\"\\n         If you want to trace to a unix domain socket, prefix\"\n+\t\t\"\\n         the absolute pathname with \\\"af_unix:\\\".\");\n+#endif\n+\n+\twarning(\"%s\", buf.buf);\n+\n+\tstrbuf_release(&buf);\n+}\n+\n+int tr2_dst_get_trace_fd(struct tr2_dst *dst)\n+{\n+\tconst char *tgt_value;\n+\n+\t/* don't open twice */\n+\tif (dst->initialized)\n+\t\treturn dst->fd;\n+\n+\tdst->initialized = 1;\n+\n+\ttgt_value = getenv(dst->env_var_name);\n+\n+\tif (!tgt_value || !strcmp(tgt_value, \"\") || !strcmp(tgt_value, \"0\") ||\n+\t    !strcasecmp(tgt_value, \"false\")) {\n+\t\tdst->fd = 0;\n+\t\treturn dst->fd;\n+\t}\n+\n+\tif (!strcmp(tgt_value, \"1\") || !strcasecmp(tgt_value, \"true\")) {\n+\t\tdst->fd = STDERR_FILENO;\n+\t\treturn dst->fd;\n+\t}\n+\n+\tif (strlen(tgt_value) == 1 && isdigit(*tgt_value)) {\n+\t\tdst->fd = atoi(tgt_value);\n+\t\treturn dst->fd;\n+\t}\n+\n+\tif (is_absolute_path(tgt_value))\n+\t\treturn tr2_dst_try_path(dst, tgt_value);\n+\n+#ifndef NO_UNIX_SOCKETS\n+\tif (!strncmp(tgt_value, PREFIX_AF_UNIX, PREFIX_AF_UNIX_LEN))\n+\t\treturn tr2_dst_try_unix_domain_socket(dst, tgt_value);\n+#endif\n+\n+\t/* Always warn about malformed values. */\n+\ttr2_dst_malformed_warning(dst, tgt_value);\n+\ttr2_dst_trace_disable(dst);\n+\treturn 0;\n+}\n+\n+int tr2_dst_trace_want(struct tr2_dst *dst)\n+{\n+\treturn !!tr2_dst_get_trace_fd(dst);\n+}\n+\n+void tr2_dst_write_line(struct tr2_dst *dst, struct strbuf *buf_line)\n+{\n+\tstrbuf_complete_line(buf_line); /* ensure final NL on buffer */\n+\n+\t/*\n+\t * We do not use write_in_full() because we do not want\n+\t * a short-write to try again.  We are using O_APPEND mode\n+\t * files and the kernel handles the atomic seek+write. If\n+\t * another thread or git process is concurrently writing to\n+\t * this fd or file, our remainder-write may not be contiguous\n+\t * with our initial write of this message.  And that will\n+\t * confuse readers.  So just don't bother.\n+\t *\n+\t * It is assumed that TRACE2 messages are short enough that\n+\t * the system can write them in 1 attempt and we won't see\n+\t * a short-write.\n+\t *\n+\t * If we get an IO error, just close the trace dst.\n+\t */\n+\n+\tint fd = tr2_dst_get_trace_fd(dst);\n+\n+\tif (write(fd, buf_line->buf, buf_line->len) >= 0)\n+\t\treturn;\n+\n+\tif (tr2_dst_want_warning())\n+\t\twarning(\"unable to write trace to '%s': %s\", dst->env_var_name,\n+\t\t\tstrerror(errno));\n+\ttr2_dst_trace_disable(dst);\n+}\ndiff --git a/trace2/tr2_dst.h b/trace2/tr2_dst.h\nnew file mode 100644\nindex 0000000000..9a64f05b02\n--- /dev/null\n+++ b/trace2/tr2_dst.h\n@@ -0,0 +1,36 @@\n+#ifndef TR2_DST_H\n+#define TR2_DST_H\n+\n+struct strbuf;\n+\n+struct tr2_dst {\n+\tconst char *const env_var_name;\n+\tint fd;\n+\tunsigned int initialized : 1;\n+\tunsigned int need_close : 1;\n+};\n+\n+/*\n+ * Disable TRACE2 on the destination.  In TRACE2 a destination (DST)\n+ * wraps a file descriptor; it is associated with a TARGET which\n+ * defines the formatting.\n+ */\n+void tr2_dst_trace_disable(struct tr2_dst *dst);\n+\n+/*\n+ * Return the file descriptor for the DST.\n+ * If 0, the dst is closed or disabled.\n+ */\n+int tr2_dst_get_trace_fd(struct tr2_dst *dst);\n+\n+/*\n+ * Return true if the DST is opened for writing.\n+ */\n+int tr2_dst_trace_want(struct tr2_dst *dst);\n+\n+/*\n+ * Write a single line/message to the trace file.\n+ */\n+void tr2_dst_write_line(struct tr2_dst *dst, struct strbuf *buf_line);\n+\n+#endif /* TR2_DST_H */\ndiff --git a/trace2/tr2_sid.c b/trace2/tr2_sid.c\nnew file mode 100644\nindex 0000000000..984524a43c\n--- /dev/null\n+++ b/trace2/tr2_sid.c\n@@ -0,0 +1,67 @@\n+#include \"cache.h\"\n+#include \"trace2/tr2_sid.h\"\n+\n+#define TR2_ENVVAR_PARENT_SID \"GIT_TR2_PARENT_SID\"\n+\n+static struct strbuf tr2sid_buf = STRBUF_INIT;\n+static int tr2sid_nr_git_parents;\n+\n+/*\n+ * Compute a \"unique\" session id (SID) for the current process.  This allows\n+ * all events from this process to have a single label (much like a PID).\n+ *\n+ * Export this into our environment so that all child processes inherit it.\n+ *\n+ * If we were started by another git instance, use our parent's SID as a\n+ * prefix.  (This lets us track parent/child relationships even if there\n+ * is an intermediate shell process.)\n+ *\n+ * Additionally, count the number of nested git processes.\n+ */\n+static void tr2_sid_compute(void)\n+{\n+\tuint64_t us_now;\n+\tconst char *parent_sid;\n+\n+\tif (tr2sid_buf.len)\n+\t\treturn;\n+\n+\tparent_sid = getenv(TR2_ENVVAR_PARENT_SID);\n+\tif (parent_sid && *parent_sid) {\n+\t\tconst char *p;\n+\t\tfor (p = parent_sid; *p; p++)\n+\t\t\tif (*p == '/')\n+\t\t\t\ttr2sid_nr_git_parents++;\n+\n+\t\tstrbuf_addstr(&tr2sid_buf, parent_sid);\n+\t\tstrbuf_addch(&tr2sid_buf, '/');\n+\t\ttr2sid_nr_git_parents++;\n+\t}\n+\n+\tus_now = getnanotime() / 1000;\n+\tstrbuf_addf(&tr2sid_buf, \"%\" PRIuMAX \"-%\" PRIdMAX, (uintmax_t)us_now,\n+\t\t    (intmax_t)getpid());\n+\n+\tsetenv(TR2_ENVVAR_PARENT_SID, tr2sid_buf.buf, 1);\n+}\n+\n+const char *tr2_sid_get(void)\n+{\n+\tif (!tr2sid_buf.len)\n+\t\ttr2_sid_compute();\n+\n+\treturn tr2sid_buf.buf;\n+}\n+\n+int tr2_sid_depth(void)\n+{\n+\tif (!tr2sid_buf.len)\n+\t\ttr2_sid_compute();\n+\n+\treturn tr2sid_nr_git_parents;\n+}\n+\n+void tr2_sid_release(void)\n+{\n+\tstrbuf_release(&tr2sid_buf);\n+}\ndiff --git a/trace2/tr2_sid.h b/trace2/tr2_sid.h\nnew file mode 100644\nindex 0000000000..9bef321708\n--- /dev/null\n+++ b/trace2/tr2_sid.h\n@@ -0,0 +1,18 @@\n+#ifndef TR2_SID_H\n+#define TR2_SID_H\n+\n+/*\n+ * Get our session id. Compute if necessary.\n+ */\n+const char *tr2_sid_get(void);\n+\n+/*\n+ * Get our process depth.  A top-level git process invoked from the\n+ * command line will have depth=0.  A child git process will have\n+ * depth=1 and so on.\n+ */\n+int tr2_sid_depth(void);\n+\n+void tr2_sid_release(void);\n+\n+#endif /* TR2_SID_H */\ndiff --git a/trace2/tr2_tbuf.c b/trace2/tr2_tbuf.c\nnew file mode 100644\nindex 0000000000..0844910423\n--- /dev/null\n+++ b/trace2/tr2_tbuf.c\n@@ -0,0 +1,32 @@\n+#include \"cache.h\"\n+#include \"tr2_tbuf.h\"\n+\n+void tr2_tbuf_local_time(struct tr2_tbuf *tb)\n+{\n+\tstruct timeval tv;\n+\tstruct tm tm;\n+\ttime_t secs;\n+\n+\tgettimeofday(&tv, NULL);\n+\tsecs = tv.tv_sec;\n+\tlocaltime_r(&secs, &tm);\n+\n+\txsnprintf(tb->buf, sizeof(tb->buf), \"%02d:%02d:%02d.%06ld\", tm.tm_hour,\n+\t\t  tm.tm_min, tm.tm_sec, (long)tv.tv_usec);\n+}\n+\n+void tr2_tbuf_utc_time(struct tr2_tbuf *tb)\n+{\n+\tstruct timeval tv;\n+\tstruct tm tm;\n+\ttime_t secs;\n+\n+\tgettimeofday(&tv, NULL);\n+\tsecs = tv.tv_sec;\n+\tgmtime_r(&secs, &tm);\n+\n+\txsnprintf(tb->buf, sizeof(tb->buf),\n+\t\t  \"%4d-%02d-%02d %02d:%02d:%02d.%06ld\", tm.tm_year + 1900,\n+\t\t  tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,\n+\t\t  (long)tv.tv_usec);\n+}\ndiff --git a/trace2/tr2_tbuf.h b/trace2/tr2_tbuf.h\nnew file mode 100644\nindex 0000000000..9cdefa3957\n--- /dev/null\n+++ b/trace2/tr2_tbuf.h\n@@ -0,0 +1,23 @@\n+#ifndef TR2_TBUF_H\n+#define TR2_TBUF_H\n+\n+/*\n+ * A simple wrapper around a fixed buffer to avoid C syntax\n+ * quirks and the need to pass around an additional size_t\n+ * argument.\n+ */\n+struct tr2_tbuf {\n+\tchar buf[32];\n+};\n+\n+/*\n+ * Fill buffer with formatted local time string.\n+ */\n+void tr2_tbuf_local_time(struct tr2_tbuf *tb);\n+\n+/*\n+ * Fill buffer with formatted UTC time string.\n+ */\n+void tr2_tbuf_utc_time(struct tr2_tbuf *tb);\n+\n+#endif /* TR2_TBUF_H */\ndiff --git a/trace2/tr2_tgt.h b/trace2/tr2_tgt.h\nnew file mode 100644\nindex 0000000000..3be0a233e7\n--- /dev/null\n+++ b/trace2/tr2_tgt.h\n@@ -0,0 +1,131 @@\n+#ifndef TR2_TGT_H\n+#define TR2_TGT_H\n+\n+struct child_process;\n+struct repository;\n+struct json_writer;\n+\n+/*\n+ * Function prototypes for a TRACE2 \"target\" vtable.\n+ */\n+\n+typedef int(tr2_tgt_init_t)(void);\n+typedef void(tr2_tgt_term_t)(void);\n+\n+typedef void(tr2_tgt_evt_version_fl_t)(const char *file, int line);\n+\n+typedef void(tr2_tgt_evt_start_fl_t)(const char *file, int line,\n+\t\t\t\t     const char **argv);\n+typedef void(tr2_tgt_evt_exit_fl_t)(const char *file, int line,\n+\t\t\t\t    uint64_t us_elapsed_absolute, int code);\n+typedef void(tr2_tgt_evt_signal_t)(uint64_t us_elapsed_absolute, int signo);\n+typedef void(tr2_tgt_evt_atexit_t)(uint64_t us_elapsed_absolute, int code);\n+\n+typedef void(tr2_tgt_evt_error_va_fl_t)(const char *file, int line,\n+\t\t\t\t\tconst char *fmt, va_list ap);\n+\n+typedef void(tr2_tgt_evt_command_path_fl_t)(const char *file, int line,\n+\t\t\t\t\t    const char *command_path);\n+typedef void(tr2_tgt_evt_command_verb_fl_t)(const char *file, int line,\n+\t\t\t\t\t    const char *command_verb,\n+\t\t\t\t\t    const char *hierarchy);\n+typedef void(tr2_tgt_evt_command_subverb_fl_t)(const char *file, int line,\n+\t\t\t\t\t       const char *command_subverb);\n+\n+typedef void(tr2_tgt_evt_alias_fl_t)(const char *file, int line,\n+\t\t\t\t     const char *alias, const char **argv);\n+\n+typedef void(tr2_tgt_evt_child_start_fl_t)(const char *file, int line,\n+\t\t\t\t\t   uint64_t us_elapsed_absolute,\n+\t\t\t\t\t   const struct child_process *cmd);\n+typedef void(tr2_tgt_evt_child_exit_fl_t)(const char *file, int line,\n+\t\t\t\t\t  uint64_t us_elapsed_absolute, int cid,\n+\t\t\t\t\t  int pid, int code,\n+\t\t\t\t\t  uint64_t us_elapsed_child);\n+\n+typedef void(tr2_tgt_evt_thread_start_fl_t)(const char *file, int line,\n+\t\t\t\t\t    uint64_t us_elapsed_absolute);\n+typedef void(tr2_tgt_evt_thread_exit_fl_t)(const char *file, int line,\n+\t\t\t\t\t   uint64_t us_elapsed_absolute,\n+\t\t\t\t\t   uint64_t us_elapsed_thread);\n+\n+typedef void(tr2_tgt_evt_exec_fl_t)(const char *file, int line,\n+\t\t\t\t    uint64_t us_elapsed_absolute, int exec_id,\n+\t\t\t\t    const char *exe, const char **argv);\n+typedef void(tr2_tgt_evt_exec_result_fl_t)(const char *file, int line,\n+\t\t\t\t\t   uint64_t us_elapsed_absolute,\n+\t\t\t\t\t   int exec_id, int code);\n+\n+typedef void(tr2_tgt_evt_param_fl_t)(const char *file, int line,\n+\t\t\t\t     const char *param, const char *value);\n+\n+typedef void(tr2_tgt_evt_repo_fl_t)(const char *file, int line,\n+\t\t\t\t    const struct repository *repo);\n+\n+typedef void(tr2_tgt_evt_region_enter_printf_va_fl_t)(\n+\tconst char *file, int line, uint64_t us_elapsed_absolute,\n+\tconst char *category, const char *label, const struct repository *repo,\n+\tconst char *fmt, va_list ap);\n+typedef void(tr2_tgt_evt_region_leave_printf_va_fl_t)(\n+\tconst char *file, int line, uint64_t us_elapsed_absolute,\n+\tuint64_t us_elapsed_region, const char *category, const char *label,\n+\tconst struct repository *repo, const char *fmt, va_list ap);\n+\n+typedef void(tr2_tgt_evt_data_fl_t)(const char *file, int line,\n+\t\t\t\t    uint64_t us_elapsed_absolute,\n+\t\t\t\t    uint64_t us_elapsed_region,\n+\t\t\t\t    const char *category,\n+\t\t\t\t    const struct repository *repo,\n+\t\t\t\t    const char *key, const char *value);\n+typedef void(tr2_tgt_evt_data_json_fl_t)(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t uint64_t us_elapsed_region,\n+\t\t\t\t\t const char *category,\n+\t\t\t\t\t const struct repository *repo,\n+\t\t\t\t\t const char *key,\n+\t\t\t\t\t const struct json_writer *value);\n+\n+typedef void(tr2_tgt_evt_printf_va_fl_t)(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t const char *fmt, va_list ap);\n+\n+/*\n+ * \"vtable\" for a TRACE2 target.  Use NULL if a target does not want\n+ * to emit that message.\n+ */\n+struct tr2_tgt {\n+\tstruct tr2_dst                          *pdst;\n+\n+\ttr2_tgt_init_t                          *pfn_init;\n+\ttr2_tgt_term_t                          *pfn_term;\n+\n+\ttr2_tgt_evt_version_fl_t                *pfn_version_fl;\n+\ttr2_tgt_evt_start_fl_t                  *pfn_start_fl;\n+\ttr2_tgt_evt_exit_fl_t                   *pfn_exit_fl;\n+\ttr2_tgt_evt_signal_t                    *pfn_signal;\n+\ttr2_tgt_evt_atexit_t                    *pfn_atexit;\n+\ttr2_tgt_evt_error_va_fl_t               *pfn_error_va_fl;\n+\ttr2_tgt_evt_command_path_fl_t           *pfn_command_path_fl;\n+\ttr2_tgt_evt_command_verb_fl_t           *pfn_command_verb_fl;\n+\ttr2_tgt_evt_command_subverb_fl_t        *pfn_command_subverb_fl;\n+\ttr2_tgt_evt_alias_fl_t                  *pfn_alias_fl;\n+\ttr2_tgt_evt_child_start_fl_t            *pfn_child_start_fl;\n+\ttr2_tgt_evt_child_exit_fl_t             *pfn_child_exit_fl;\n+\ttr2_tgt_evt_thread_start_fl_t           *pfn_thread_start_fl;\n+\ttr2_tgt_evt_thread_exit_fl_t            *pfn_thread_exit_fl;\n+\ttr2_tgt_evt_exec_fl_t                   *pfn_exec_fl;\n+\ttr2_tgt_evt_exec_result_fl_t            *pfn_exec_result_fl;\n+\ttr2_tgt_evt_param_fl_t                  *pfn_param_fl;\n+\ttr2_tgt_evt_repo_fl_t                   *pfn_repo_fl;\n+\ttr2_tgt_evt_region_enter_printf_va_fl_t *pfn_region_enter_printf_va_fl;\n+\ttr2_tgt_evt_region_leave_printf_va_fl_t *pfn_region_leave_printf_va_fl;\n+\ttr2_tgt_evt_data_fl_t                   *pfn_data_fl;\n+\ttr2_tgt_evt_data_json_fl_t              *pfn_data_json_fl;\n+\ttr2_tgt_evt_printf_va_fl_t              *pfn_printf_va_fl;\n+};\n+\n+extern struct tr2_tgt tr2_tgt_event;\n+extern struct tr2_tgt tr2_tgt_normal;\n+extern struct tr2_tgt tr2_tgt_perf;\n+\n+#endif /* TR2_TGT_H */\ndiff --git a/trace2/tr2_tgt_event.c b/trace2/tr2_tgt_event.c\nnew file mode 100644\nindex 0000000000..7d4efc8a9b\n--- /dev/null\n+++ b/trace2/tr2_tgt_event.c\n@@ -0,0 +1,590 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"json-writer.h\"\n+#include \"run-command.h\"\n+#include \"version.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_tbuf.h\"\n+#include \"trace2/tr2_sid.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+static struct tr2_dst tr2dst_event = { \"GIT_TR2_EVENT\", 0, 0, 0 };\n+\n+/*\n+ * The version number of the JSON data generated by the EVENT target\n+ * in this source file.  Update this if you make a significant change\n+ * to the JSON fields or message structure.  You probably do not need\n+ * to update this if you just add another call to one of the existing\n+ * TRACE2 API methods.\n+ */\n+#define TR2_EVENT_VERSION \"1\"\n+\n+/*\n+ * Region nesting limit for messages written to the event target.\n+ *\n+ * The \"region_enter\" and \"region_leave\" messages (especially recursive\n+ * messages such as those produced while diving the worktree or index)\n+ * are primarily intended for the performance target during debugging.\n+ *\n+ * Some of the outer-most messages, however, may be of interest to the\n+ * event target.  Set this environment variable to a larger integer for\n+ * more detail in the event target.\n+ */\n+#define TR2_ENVVAR_EVENT_NESTING \"GIT_TR2_EVENT_NESTING\"\n+static int tr2env_event_nesting_wanted = 2;\n+\n+/*\n+ * Set this environment variable to true to omit the <time>, <file>, and\n+ * <line> fields from most events.\n+ */\n+#define TR2_ENVVAR_EVENT_BRIEF \"GIT_TR2_EVENT_BRIEF\"\n+static int tr2env_event_brief;\n+\n+static int fn_init(void)\n+{\n+\tint want = tr2_dst_trace_want(&tr2dst_event);\n+\tint want_nesting;\n+\tint want_brief;\n+\tchar *nesting;\n+\tchar *brief;\n+\n+\tif (!want)\n+\t\treturn want;\n+\n+\tnesting = getenv(TR2_ENVVAR_EVENT_NESTING);\n+\tif (nesting && ((want_nesting = atoi(nesting)) > 0))\n+\t\ttr2env_event_nesting_wanted = want_nesting;\n+\n+\tbrief = getenv(TR2_ENVVAR_EVENT_BRIEF);\n+\tif (brief && ((want_brief = atoi(brief)) > 0))\n+\t\ttr2env_event_brief = want_brief;\n+\n+\treturn want;\n+}\n+\n+static void fn_term(void)\n+{\n+\ttr2_dst_trace_disable(&tr2dst_event);\n+}\n+\n+/*\n+ * Append common key-value pairs to the currently open JSON object.\n+ *     \"event:\"<event_name>\"\n+ *      \"sid\":\"<sid>\"\n+ *   \"thread\":\"<thread_name>\"\n+ *     \"time\":\"<time>\"\n+ *     \"file\":\"<filename>\"\n+ *     \"line\":<line_number>\n+ *     \"repo\":<repo_id>\n+ */\n+static void event_fmt_prepare(const char *event_name, const char *file,\n+\t\t\t      int line, const struct repository *repo,\n+\t\t\t      struct json_writer *jw)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tstruct tr2_tbuf tb_now;\n+\n+\tjw_object_string(jw, \"event\", event_name);\n+\tjw_object_string(jw, \"sid\", tr2_sid_get());\n+\tjw_object_string(jw, \"thread\", ctx->thread_name.buf);\n+\n+\t/*\n+\t * In brief mode, only emit <time> on these 2 event types.\n+\t */\n+\tif (!tr2env_event_brief || !strcmp(event_name, \"version\") ||\n+\t    !strcmp(event_name, \"atexit\")) {\n+\t\ttr2_tbuf_utc_time(&tb_now);\n+\t\tjw_object_string(jw, \"time\", tb_now.buf);\n+\t}\n+\n+\tif (!tr2env_event_brief && file && *file) {\n+\t\tjw_object_string(jw, \"file\", file);\n+\t\tjw_object_intmax(jw, \"line\", line);\n+\t}\n+\n+\tif (repo)\n+\t\tjw_object_intmax(jw, \"repo\", repo->trace2_repo_id);\n+}\n+\n+static void fn_version_fl(const char *file, int line)\n+{\n+\tconst char *event_name = \"version\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"evt\", TR2_EVENT_VERSION);\n+\tjw_object_string(&jw, \"exe\", git_version_string);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_start_fl(const char *file, int line, const char **argv)\n+{\n+\tconst char *event_name = \"start\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tjw_array_argv(&jw, argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_exit_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int code)\n+{\n+\tconst char *event_name = \"exit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_signal(uint64_t us_elapsed_absolute, int signo)\n+{\n+\tconst char *event_name = \"signal\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, __FILE__, __LINE__, NULL, &jw);\n+\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\tjw_object_intmax(&jw, \"signo\", signo);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_atexit(uint64_t us_elapsed_absolute, int code)\n+{\n+\tconst char *event_name = \"atexit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, __FILE__, __LINE__, NULL, &jw);\n+\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void maybe_add_string_va(struct json_writer *jw, const char *field_name,\n+\t\t\t\tconst char *fmt, va_list ap)\n+{\n+\tif (fmt && *fmt && ap) {\n+\t\tva_list copy_ap;\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\n+\t\tva_copy(copy_ap, ap);\n+\t\tstrbuf_vaddf(&buf, fmt, copy_ap);\n+\t\tva_end(copy_ap);\n+\n+\t\tjw_object_string(jw, field_name, buf.buf);\n+\t\tstrbuf_release(&buf);\n+\t\treturn;\n+\t}\n+\n+\tif (fmt && *fmt) {\n+\t\tjw_object_string(jw, field_name, fmt);\n+\t\treturn;\n+\t}\n+}\n+\n+static void fn_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t   va_list ap)\n+{\n+\tconst char *event_name = \"error\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tmaybe_add_string_va(&jw, \"msg\", fmt, ap);\n+\t/*\n+\t * Also emit the format string as a field in case\n+\t * post-processors want to aggregate common error\n+\t * messages by type without argument fields (such\n+\t * as pathnames or branch names) cluttering it up.\n+\t */\n+\tif (fmt && *fmt)\n+\t\tjw_object_string(&jw, \"fmt\", fmt);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_command_path_fl(const char *file, int line, const char *pathname)\n+{\n+\tconst char *event_name = \"cmd_path\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"path\", pathname);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_command_verb_fl(const char *file, int line,\n+\t\t\t       const char *command_verb,\n+\t\t\t       const char *verb_hierarchy)\n+{\n+\tconst char *event_name = \"cmd_verb\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"name\", command_verb);\n+\tif (verb_hierarchy && *verb_hierarchy)\n+\t\tjw_object_string(&jw, \"hierarchy\", verb_hierarchy);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_command_subverb_fl(const char *file, int line,\n+\t\t\t\t  const char *command_subverb)\n+{\n+\tconst char *event_name = \"cmd_subverb\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"name\", command_subverb);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_alias_fl(const char *file, int line, const char *alias,\n+\t\t\tconst char **argv)\n+{\n+\tconst char *event_name = \"alias\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"alias\", alias);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tjw_array_argv(&jw, argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_child_start_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      const struct child_process *cmd)\n+{\n+\tconst char *event_name = \"child_start\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"child_id\", cmd->trace2_child_id);\n+\tif (cmd->trace2_hook_name) {\n+\t\tjw_object_string(&jw, \"child_class\", \"hook\");\n+\t\tjw_object_string(&jw, \"hook_name\", cmd->trace2_hook_name);\n+\t} else {\n+\t\tconst char *child_class =\n+\t\t\tcmd->trace2_child_class ? cmd->trace2_child_class : \"?\";\n+\t\tjw_object_string(&jw, \"child_class\", child_class);\n+\t}\n+\tif (cmd->dir)\n+\t\tjw_object_string(&jw, \"cd\", cmd->dir);\n+\tjw_object_bool(&jw, \"use_shell\", cmd->use_shell);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tif (cmd->git_cmd)\n+\t\tjw_array_string(&jw, \"git\");\n+\tjw_array_argv(&jw, cmd->argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_child_exit_fl(const char *file, int line,\n+\t\t\t     uint64_t us_elapsed_absolute, int cid, int pid,\n+\t\t\t     int code, uint64_t us_elapsed_child)\n+{\n+\tconst char *event_name = \"child_exit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_rel = (double)us_elapsed_child / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"child_id\", cid);\n+\tjw_object_intmax(&jw, \"pid\", pid);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\n+\tjw_release(&jw);\n+}\n+\n+static void fn_thread_start_fl(const char *file, int line,\n+\t\t\t       uint64_t us_elapsed_absolute)\n+{\n+\tconst char *event_name = \"thread_start\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_thread_exit_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      uint64_t us_elapsed_thread)\n+{\n+\tconst char *event_name = \"thread_exit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_rel = (double)us_elapsed_thread / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int exec_id, const char *exe, const char **argv)\n+{\n+\tconst char *event_name = \"exec\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"exec_id\", exec_id);\n+\tif (exe)\n+\t\tjw_object_string(&jw, \"exe\", exe);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tjw_array_argv(&jw, argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_exec_result_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute, int exec_id,\n+\t\t\t      int code)\n+{\n+\tconst char *event_name = \"exec_result\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"exec_id\", exec_id);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_param_fl(const char *file, int line, const char *param,\n+\t\t\tconst char *value)\n+{\n+\tconst char *event_name = \"def_param\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"param\", param);\n+\tjw_object_string(&jw, \"value\", value);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_repo_fl(const char *file, int line,\n+\t\t       const struct repository *repo)\n+{\n+\tconst char *event_name = \"def_repo\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\tjw_object_string(&jw, \"worktree\", repo->worktree);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t const char *category,\n+\t\t\t\t\t const char *label,\n+\t\t\t\t\t const struct repository *repo,\n+\t\t\t\t\t const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_enter\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tif (category)\n+\t\t\tjw_object_string(&jw, \"category\", category);\n+\t\tif (label)\n+\t\t\tjw_object_string(&jw, \"label\", label);\n+\t\tmaybe_add_string_va(&jw, \"msg\", fmt, ap);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+static void fn_region_leave_printf_va_fl(\n+\tconst char *file, int line, uint64_t us_elapsed_absolute,\n+\tuint64_t us_elapsed_region, const char *category, const char *label,\n+\tconst struct repository *repo, const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_leave\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\t\tdouble t_rel = (double)us_elapsed_region / 1000000.0;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tif (category)\n+\t\t\tjw_object_string(&jw, \"category\", category);\n+\t\tif (label)\n+\t\t\tjw_object_string(&jw, \"label\", label);\n+\t\tmaybe_add_string_va(&jw, \"msg\", fmt, ap);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+static void fn_data_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       uint64_t us_elapsed_region, const char *category,\n+\t\t       const struct repository *repo, const char *key,\n+\t\t       const char *value)\n+{\n+\tconst char *event_name = \"data\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\t\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\t\tdouble t_rel = (double)us_elapsed_region / 1000000.0;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\t\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tjw_object_string(&jw, \"category\", category);\n+\t\tjw_object_string(&jw, \"key\", key);\n+\t\tjw_object_string(&jw, \"value\", value);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+static void fn_data_json_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute,\n+\t\t\t    uint64_t us_elapsed_region, const char *category,\n+\t\t\t    const struct repository *repo, const char *key,\n+\t\t\t    const struct json_writer *value)\n+{\n+\tconst char *event_name = \"data_json\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\t\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\t\tdouble t_rel = (double)us_elapsed_region / 1000000.0;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\t\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tjw_object_string(&jw, \"category\", category);\n+\t\tjw_object_string(&jw, \"key\", key);\n+\t\tjw_object_sub_jw(&jw, \"value\", value);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+struct tr2_tgt tr2_tgt_event = {\n+\t&tr2dst_event,\n+\n+\tfn_init,\n+\tfn_term,\n+\n+\tfn_version_fl,\n+\tfn_start_fl,\n+\tfn_exit_fl,\n+\tfn_signal,\n+\tfn_atexit,\n+\tfn_error_va_fl,\n+\tfn_command_path_fl,\n+\tfn_command_verb_fl,\n+\tfn_command_subverb_fl,\n+\tfn_alias_fl,\n+\tfn_child_start_fl,\n+\tfn_child_exit_fl,\n+\tfn_thread_start_fl,\n+\tfn_thread_exit_fl,\n+\tfn_exec_fl,\n+\tfn_exec_result_fl,\n+\tfn_param_fl,\n+\tfn_repo_fl,\n+\tfn_region_enter_printf_va_fl,\n+\tfn_region_leave_printf_va_fl,\n+\tfn_data_fl,\n+\tfn_data_json_fl,\n+\tNULL, /* printf */\n+};\ndiff --git a/trace2/tr2_tgt_normal.c b/trace2/tr2_tgt_normal.c\nnew file mode 100644\nindex 0000000000..239bcb8980\n--- /dev/null\n+++ b/trace2/tr2_tgt_normal.c\n@@ -0,0 +1,325 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"run-command.h\"\n+#include \"quote.h\"\n+#include \"version.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_tbuf.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+static struct tr2_dst tr2dst_normal = { \"GIT_TR2\", 0, 0, 0 };\n+\n+/*\n+ * Set this environment variable to true to omit the \"<time> <file>:<line>\"\n+ * fields from each line written to the builtin normal target.\n+ *\n+ * Unit tests may want to use this to help with testing.\n+ */\n+#define TR2_ENVVAR_NORMAL_BRIEF \"GIT_TR2_BRIEF\"\n+static int tr2env_normal_brief;\n+\n+#define TR2FMT_NORMAL_FL_WIDTH (50)\n+\n+static int fn_init(void)\n+{\n+\tint want = tr2_dst_trace_want(&tr2dst_normal);\n+\tint want_brief;\n+\tchar *brief;\n+\n+\tif (!want)\n+\t\treturn want;\n+\n+\tbrief = getenv(TR2_ENVVAR_NORMAL_BRIEF);\n+\tif (brief && *brief &&\n+\t    ((want_brief = git_parse_maybe_bool(brief)) != -1))\n+\t\ttr2env_normal_brief = want_brief;\n+\n+\treturn want;\n+}\n+\n+static void fn_term(void)\n+{\n+\ttr2_dst_trace_disable(&tr2dst_normal);\n+}\n+\n+static void normal_fmt_prepare(const char *file, int line, struct strbuf *buf)\n+{\n+\tstrbuf_setlen(buf, 0);\n+\n+\tif (!tr2env_normal_brief) {\n+\t\tstruct tr2_tbuf tb_now;\n+\n+\t\ttr2_tbuf_local_time(&tb_now);\n+\t\tstrbuf_addstr(buf, tb_now.buf);\n+\t\tstrbuf_addch(buf, ' ');\n+\n+\t\tif (file && *file)\n+\t\t\tstrbuf_addf(buf, \"%s:%d \", file, line);\n+\t\twhile (buf->len < TR2FMT_NORMAL_FL_WIDTH)\n+\t\t\tstrbuf_addch(buf, ' ');\n+\t}\n+}\n+\n+static void normal_io_write_fl(const char *file, int line,\n+\t\t\t       const struct strbuf *buf_payload)\n+{\n+\tstruct strbuf buf_line = STRBUF_INIT;\n+\n+\tnormal_fmt_prepare(file, line, &buf_line);\n+\tstrbuf_addbuf(&buf_line, buf_payload);\n+\ttr2_dst_write_line(&tr2dst_normal, &buf_line);\n+\tstrbuf_release(&buf_line);\n+}\n+\n+static void fn_version_fl(const char *file, int line)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"version %s\", git_version_string);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_start_fl(const char *file, int line, const char **argv)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"start \");\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exit_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int code)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"exit elapsed:%.6f code:%d\", elapsed, code);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_signal(uint64_t us_elapsed_absolute, int signo)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"signal elapsed:%.6f code:%d\", elapsed,\n+\t\t    signo);\n+\tnormal_io_write_fl(__FILE__, __LINE__, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_atexit(uint64_t us_elapsed_absolute, int code)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"atexit elapsed:%.6f code:%d\", elapsed, code);\n+\tnormal_io_write_fl(__FILE__, __LINE__, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void maybe_append_string_va(struct strbuf *buf, const char *fmt,\n+\t\t\t\t   va_list ap)\n+{\n+\tif (fmt && *fmt && ap) {\n+\t\tva_list copy_ap;\n+\n+\t\tva_copy(copy_ap, ap);\n+\t\tstrbuf_vaddf(buf, fmt, copy_ap);\n+\t\tva_end(copy_ap);\n+\t\treturn;\n+\t}\n+\n+\tif (fmt && *fmt) {\n+\t\tstrbuf_addstr(buf, fmt);\n+\t\treturn;\n+\t}\n+}\n+\n+static void fn_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t   va_list ap)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"error \");\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_path_fl(const char *file, int line, const char *pathname)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"cmd_path %s\", pathname);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_verb_fl(const char *file, int line,\n+\t\t\t       const char *command_verb,\n+\t\t\t       const char *verb_hierarchy)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"cmd_verb %s\", command_verb);\n+\tif (verb_hierarchy && *verb_hierarchy)\n+\t\tstrbuf_addf(&buf_payload, \" (%s)\", verb_hierarchy);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_subverb_fl(const char *file, int line,\n+\t\t\t\t  const char *command_subverb)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"cmd_subverb %s\", command_subverb);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_alias_fl(const char *file, int line, const char *alias,\n+\t\t\tconst char **argv)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"alias %s ->\", alias);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_start_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      const struct child_process *cmd)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"child_start[%d] \", cmd->trace2_child_id);\n+\n+\tif (cmd->dir) {\n+\t\tstrbuf_addstr(&buf_payload, \" cd\");\n+\t\tsq_quote_buf_pretty(&buf_payload, cmd->dir);\n+\t\tstrbuf_addstr(&buf_payload, \"; \");\n+\t}\n+\n+\t/*\n+\t * TODO if (cmd->env) { Consider dumping changes to environment. }\n+\t * See trace_add_env() in run-command.c as used by original trace.c\n+\t */\n+\n+\tif (cmd->git_cmd)\n+\t\tstrbuf_addstr(&buf_payload, \"git\");\n+\tsq_quote_argv_pretty(&buf_payload, cmd->argv);\n+\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_exit_fl(const char *file, int line,\n+\t\t\t     uint64_t us_elapsed_absolute, int cid, int pid,\n+\t\t\t     int code, uint64_t us_elapsed_child)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_child / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"child_exit[%d] pid:%d code:%d elapsed:%.6f\",\n+\t\t    cid, pid, code, elapsed);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int exec_id, const char *exe, const char **argv)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"exec[%d] \", exec_id);\n+\tif (exe)\n+\t\tstrbuf_addstr(&buf_payload, exe);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_result_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute, int exec_id,\n+\t\t\t      int code)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"exec_result[%d] code:%d\", exec_id, code);\n+\tif (code > 0)\n+\t\tstrbuf_addf(&buf_payload, \" err:%s\", strerror(code));\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_param_fl(const char *file, int line, const char *param,\n+\t\t\tconst char *value)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"def_param %s=%s\", param, value);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_repo_fl(const char *file, int line,\n+\t\t       const struct repository *repo)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"worktree \");\n+\tsq_quote_buf_pretty(&buf_payload, repo->worktree);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_printf_va_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute, const char *fmt,\n+\t\t\t    va_list ap)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+struct tr2_tgt tr2_tgt_normal = {\n+\t&tr2dst_normal,\n+\n+\tfn_init,\n+\tfn_term,\n+\n+\tfn_version_fl,\n+\tfn_start_fl,\n+\tfn_exit_fl,\n+\tfn_signal,\n+\tfn_atexit,\n+\tfn_error_va_fl,\n+\tfn_command_path_fl,\n+\tfn_command_verb_fl,\n+\tfn_command_subverb_fl,\n+\tfn_alias_fl,\n+\tfn_child_start_fl,\n+\tfn_child_exit_fl,\n+\tNULL, /* thread_start */\n+\tNULL, /* thread_exit */\n+\tfn_exec_fl,\n+\tfn_exec_result_fl,\n+\tfn_param_fl,\n+\tfn_repo_fl,\n+\tNULL, /* region_enter */\n+\tNULL, /* region_leave */\n+\tNULL, /* data */\n+\tNULL, /* data_json */\n+\tfn_printf_va_fl,\n+};\ndiff --git a/trace2/tr2_tgt_perf.c b/trace2/tr2_tgt_perf.c\nnew file mode 100644\nindex 0000000000..dbdb08b4f3\n--- /dev/null\n+++ b/trace2/tr2_tgt_perf.c\n@@ -0,0 +1,536 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"run-command.h\"\n+#include \"quote.h\"\n+#include \"version.h\"\n+#include \"json-writer.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_sid.h\"\n+#include \"trace2/tr2_tbuf.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+static struct tr2_dst tr2dst_perf = { \"GIT_TR2_PERF\", 0, 0, 0 };\n+\n+/*\n+ * Set this environment variable to true to omit the \"<time> <file>:<line>\"\n+ * fields from each line written to the builtin performance target.\n+ *\n+ * Unit tests may want to use this to help with testing.\n+ */\n+#define TR2_ENVVAR_PERF_BRIEF \"GIT_TR2_PERF_BRIEF\"\n+static int tr2env_perf_brief;\n+\n+#define TR2FMT_PERF_FL_WIDTH (50)\n+#define TR2FMT_PERF_MAX_EVENT_NAME (12)\n+#define TR2FMT_PERF_REPO_WIDTH (4)\n+#define TR2FMT_PERF_CATEGORY_WIDTH (10)\n+\n+#define TR2_DOTS_BUFFER_SIZE (100)\n+#define TR2_INDENT (2)\n+#define TR2_INDENT_LENGTH(ctx) (((ctx)->nr_open_regions - 1) * TR2_INDENT)\n+\n+static struct strbuf dots = STRBUF_INIT;\n+\n+static int fn_init(void)\n+{\n+\tint want = tr2_dst_trace_want(&tr2dst_perf);\n+\tint want_brief;\n+\tchar *brief;\n+\n+\tif (!want)\n+\t\treturn want;\n+\n+\tstrbuf_addchars(&dots, '.', TR2_DOTS_BUFFER_SIZE);\n+\n+\tbrief = getenv(TR2_ENVVAR_PERF_BRIEF);\n+\tif (brief && *brief &&\n+\t    ((want_brief = git_parse_maybe_bool(brief)) != -1))\n+\t\ttr2env_perf_brief = want_brief;\n+\n+\treturn want;\n+}\n+\n+static void fn_term(void)\n+{\n+\ttr2_dst_trace_disable(&tr2dst_perf);\n+\n+\tstrbuf_release(&dots);\n+}\n+\n+/*\n+ * Format trace line prefix in human-readable classic format for\n+ * the performance target:\n+ *     \"[<time> [<file>:<line>] <bar>] <nr_parents> <bar>\n+ *         <thread_name> <bar> <event_name> <bar> [<repo>] <bar>\n+ *         [<elapsed_absolute>] [<elapsed_relative>] <bar>\n+ *         [<category>] <bar> [<dots>] \"\n+ */\n+static void perf_fmt_prepare(const char *event_name,\n+\t\t\t     struct tr2tls_thread_ctx *ctx, const char *file,\n+\t\t\t     int line, const struct repository *repo,\n+\t\t\t     uint64_t *p_us_elapsed_absolute,\n+\t\t\t     uint64_t *p_us_elapsed_relative,\n+\t\t\t     const char *category, struct strbuf *buf)\n+{\n+\tint len;\n+\n+\tstrbuf_setlen(buf, 0);\n+\n+\tif (!tr2env_perf_brief) {\n+\t\tstruct tr2_tbuf tb_now;\n+\n+\t\ttr2_tbuf_local_time(&tb_now);\n+\t\tstrbuf_addstr(buf, tb_now.buf);\n+\t\tstrbuf_addch(buf, ' ');\n+\n+\t\tif (file && *file)\n+\t\t\tstrbuf_addf(buf, \"%s:%d \", file, line);\n+\t\twhile (buf->len < TR2FMT_PERF_FL_WIDTH)\n+\t\t\tstrbuf_addch(buf, ' ');\n+\n+\t\tstrbuf_addstr(buf, \"| \");\n+\t}\n+\n+\tstrbuf_addf(buf, \"d%d | \", tr2_sid_depth());\n+\tstrbuf_addf(buf, \"%-*s | %-*s | \", TR2_MAX_THREAD_NAME,\n+\t\t    ctx->thread_name.buf, TR2FMT_PERF_MAX_EVENT_NAME,\n+\t\t    event_name);\n+\n+\tlen = buf->len + TR2FMT_PERF_REPO_WIDTH;\n+\tif (repo)\n+\t\tstrbuf_addf(buf, \"r%d \", repo->trace2_repo_id);\n+\twhile (buf->len < len)\n+\t\tstrbuf_addch(buf, ' ');\n+\tstrbuf_addstr(buf, \"| \");\n+\n+\tif (p_us_elapsed_absolute)\n+\t\tstrbuf_addf(buf, \"%9.6f | \",\n+\t\t\t    ((double)(*p_us_elapsed_absolute)) / 1000000.0);\n+\telse\n+\t\tstrbuf_addf(buf, \"%9s | \", \" \");\n+\n+\tif (p_us_elapsed_relative)\n+\t\tstrbuf_addf(buf, \"%9.6f | \",\n+\t\t\t    ((double)(*p_us_elapsed_relative)) / 1000000.0);\n+\telse\n+\t\tstrbuf_addf(buf, \"%9s | \", \" \");\n+\n+\tstrbuf_addf(buf, \"%-*s | \", TR2FMT_PERF_CATEGORY_WIDTH,\n+\t\t    (category ? category : \"\"));\n+\n+\tif (ctx->nr_open_regions > 0) {\n+\t\tint len_indent = TR2_INDENT_LENGTH(ctx);\n+\t\twhile (len_indent > dots.len) {\n+\t\t\tstrbuf_addbuf(buf, &dots);\n+\t\t\tlen_indent -= dots.len;\n+\t\t}\n+\t\tstrbuf_addf(buf, \"%.*s\", len_indent, dots.buf);\n+\t}\n+}\n+\n+static void perf_io_write_fl(const char *file, int line, const char *event_name,\n+\t\t\t     const struct repository *repo,\n+\t\t\t     uint64_t *p_us_elapsed_absolute,\n+\t\t\t     uint64_t *p_us_elapsed_relative,\n+\t\t\t     const char *category,\n+\t\t\t     const struct strbuf *buf_payload)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tstruct strbuf buf_line = STRBUF_INIT;\n+\n+\tperf_fmt_prepare(event_name, ctx, file, line, repo,\n+\t\t\t p_us_elapsed_absolute, p_us_elapsed_relative, category,\n+\t\t\t &buf_line);\n+\tstrbuf_addbuf(&buf_line, buf_payload);\n+\ttr2_dst_write_line(&tr2dst_perf, &buf_line);\n+\tstrbuf_release(&buf_line);\n+}\n+\n+static void fn_version_fl(const char *file, int line)\n+{\n+\tconst char *event_name = \"version\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, git_version_string);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_start_fl(const char *file, int line, const char **argv)\n+{\n+\tconst char *event_name = \"start\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exit_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int code)\n+{\n+\tconst char *event_name = \"exit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"code:%d\", code);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_signal(uint64_t us_elapsed_absolute, int signo)\n+{\n+\tconst char *event_name = \"signal\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"signo:%d\", signo);\n+\n+\tperf_io_write_fl(__FILE__, __LINE__, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_atexit(uint64_t us_elapsed_absolute, int code)\n+{\n+\tconst char *event_name = \"atexit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"code:%d\", code);\n+\n+\tperf_io_write_fl(__FILE__, __LINE__, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void maybe_append_string_va(struct strbuf *buf, const char *fmt,\n+\t\t\t\t   va_list ap)\n+{\n+\tif (fmt && *fmt && ap) {\n+\t\tva_list copy_ap;\n+\n+\t\tva_copy(copy_ap, ap);\n+\t\tstrbuf_vaddf(buf, fmt, copy_ap);\n+\t\tva_end(copy_ap);\n+\t\treturn;\n+\t}\n+\n+\tif (fmt && *fmt) {\n+\t\tstrbuf_addstr(buf, fmt);\n+\t\treturn;\n+\t}\n+}\n+\n+static void fn_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t   va_list ap)\n+{\n+\tconst char *event_name = \"error\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_path_fl(const char *file, int line, const char *pathname)\n+{\n+\tconst char *event_name = \"cmd_path\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, pathname);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_verb_fl(const char *file, int line,\n+\t\t\t       const char *command_verb,\n+\t\t\t       const char *verb_hierarchy)\n+{\n+\tconst char *event_name = \"cmd_verb\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, command_verb);\n+\tif (verb_hierarchy && *verb_hierarchy)\n+\t\tstrbuf_addf(&buf_payload, \" (%s)\", verb_hierarchy);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_subverb_fl(const char *file, int line,\n+\t\t\t\t  const char *command_subverb)\n+{\n+\tconst char *event_name = \"cmd_subverb\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, command_subverb);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_alias_fl(const char *file, int line, const char *alias,\n+\t\t\tconst char **argv)\n+{\n+\tconst char *event_name = \"alias\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"alias:%s argv:\", alias);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_start_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      const struct child_process *cmd)\n+{\n+\tconst char *event_name = \"child_start\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tif (cmd->trace2_hook_name) {\n+\t\tstrbuf_addf(&buf_payload, \"[ch%d] class:hook hook:%s\",\n+\t\t\t    cmd->trace2_child_id, cmd->trace2_hook_name);\n+\t} else {\n+\t\tconst char *child_class =\n+\t\t\tcmd->trace2_child_class ? cmd->trace2_child_class : \"?\";\n+\t\tstrbuf_addf(&buf_payload, \"[ch%d] class:%s\",\n+\t\t\t    cmd->trace2_child_id, child_class);\n+\t}\n+\n+\tif (cmd->dir) {\n+\t\tstrbuf_addstr(&buf_payload, \" cd:\");\n+\t\tsq_quote_buf_pretty(&buf_payload, cmd->dir);\n+\t}\n+\n+\tstrbuf_addstr(&buf_payload, \" argv:\");\n+\tif (cmd->git_cmd)\n+\t\tstrbuf_addstr(&buf_payload, \" git\");\n+\tsq_quote_argv_pretty(&buf_payload, cmd->argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_exit_fl(const char *file, int line,\n+\t\t\t     uint64_t us_elapsed_absolute, int cid, int pid,\n+\t\t\t     int code, uint64_t us_elapsed_child)\n+{\n+\tconst char *event_name = \"child_exit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"[ch%d] pid:%d code:%d\", cid, pid, code);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_child, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_thread_start_fl(const char *file, int line,\n+\t\t\t       uint64_t us_elapsed_absolute)\n+{\n+\tconst char *event_name = \"thread_start\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_thread_exit_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      uint64_t us_elapsed_thread)\n+{\n+\tconst char *event_name = \"thread_exit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_thread, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int exec_id, const char *exe, const char **argv)\n+{\n+\tconst char *event_name = \"exec\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"id:%d \", exec_id);\n+\tstrbuf_addstr(&buf_payload, \"argv:\");\n+\tif (exe)\n+\t\tstrbuf_addf(&buf_payload, \" %s\", exe);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_result_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute, int exec_id,\n+\t\t\t      int code)\n+{\n+\tconst char *event_name = \"exec_result\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"id:%d code:%d\", exec_id, code);\n+\tif (code > 0)\n+\t\tstrbuf_addf(&buf_payload, \" err:%s\", strerror(code));\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_param_fl(const char *file, int line, const char *param,\n+\t\t\tconst char *value)\n+{\n+\tconst char *event_name = \"def_param\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"%s:%s\", param, value);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_repo_fl(const char *file, int line,\n+\t\t       const struct repository *repo)\n+{\n+\tconst char *event_name = \"def_repo\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"worktree:\");\n+\tsq_quote_buf_pretty(&buf_payload, repo->worktree);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t const char *category,\n+\t\t\t\t\t const char *label,\n+\t\t\t\t\t const struct repository *repo,\n+\t\t\t\t\t const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_enter\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tif (label)\n+\t\tstrbuf_addf(&buf_payload, \"label:%s \", label);\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,\n+\t\t\t NULL, category, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_region_leave_printf_va_fl(\n+\tconst char *file, int line, uint64_t us_elapsed_absolute,\n+\tuint64_t us_elapsed_region, const char *category, const char *label,\n+\tconst struct repository *repo, const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_leave\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tif (label)\n+\t\tstrbuf_addf(&buf_payload, \"label:%s \", label);\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_region, category, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_data_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       uint64_t us_elapsed_region, const char *category,\n+\t\t       const struct repository *repo, const char *key,\n+\t\t       const char *value)\n+{\n+\tconst char *event_name = \"data\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"%s:%s\", key, value);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_region, category, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_data_json_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute,\n+\t\t\t    uint64_t us_elapsed_region, const char *category,\n+\t\t\t    const struct repository *repo, const char *key,\n+\t\t\t    const struct json_writer *value)\n+{\n+\tconst char *event_name = \"data_json\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"%s:%s\", key, value->json.buf);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_region, category, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_printf_va_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute, const char *fmt,\n+\t\t\t    va_list ap)\n+{\n+\tconst char *event_name = \"printf\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+struct tr2_tgt tr2_tgt_perf = {\n+\t&tr2dst_perf,\n+\n+\tfn_init,\n+\tfn_term,\n+\n+\tfn_version_fl,\n+\tfn_start_fl,\n+\tfn_exit_fl,\n+\tfn_signal,\n+\tfn_atexit,\n+\tfn_error_va_fl,\n+\tfn_command_path_fl,\n+\tfn_command_verb_fl,\n+\tfn_command_subverb_fl,\n+\tfn_alias_fl,\n+\tfn_child_start_fl,\n+\tfn_child_exit_fl,\n+\tfn_thread_start_fl,\n+\tfn_thread_exit_fl,\n+\tfn_exec_fl,\n+\tfn_exec_result_fl,\n+\tfn_param_fl,\n+\tfn_repo_fl,\n+\tfn_region_enter_printf_va_fl,\n+\tfn_region_leave_printf_va_fl,\n+\tfn_data_fl,\n+\tfn_data_json_fl,\n+\tfn_printf_va_fl,\n+};\ndiff --git a/trace2/tr2_tls.c b/trace2/tr2_tls.c\nnew file mode 100644\nindex 0000000000..8e65b0361d\n--- /dev/null\n+++ b/trace2/tr2_tls.c\n@@ -0,0 +1,164 @@\n+#include \"cache.h\"\n+#include \"thread-utils.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+/*\n+ * Initialize size of the thread stack for nested regions.\n+ * This is used to store nested region start times.  Note that\n+ * this stack is per-thread and not per-trace-key.\n+ */\n+#define TR2_REGION_NESTING_INITIAL_SIZE (100)\n+\n+static struct tr2tls_thread_ctx *tr2tls_thread_main;\n+static uint64_t tr2tls_us_start_main;\n+\n+static pthread_mutex_t tr2tls_mutex;\n+static pthread_key_t tr2tls_key;\n+\n+static int tr2_next_thread_id; /* modify under lock */\n+\n+struct tr2tls_thread_ctx *tr2tls_create_self(const char *thread_name)\n+{\n+\tuint64_t us_now = getnanotime() / 1000;\n+\tstruct tr2tls_thread_ctx *ctx = xcalloc(1, sizeof(*ctx));\n+\n+\t/*\n+\t * Implicitly \"tr2tls_push_self()\" to capture the thread's start\n+\t * time in array_us_start[0].  For the main thread this gives us the\n+\t * application run time.\n+\t */\n+\tctx->alloc = TR2_REGION_NESTING_INITIAL_SIZE;\n+\tctx->array_us_start = (uint64_t *)xcalloc(ctx->alloc, sizeof(uint64_t));\n+\tctx->array_us_start[ctx->nr_open_regions++] = us_now;\n+\n+\tctx->thread_id = tr2tls_locked_increment(&tr2_next_thread_id);\n+\n+\tstrbuf_init(&ctx->thread_name, 0);\n+\tif (ctx->thread_id)\n+\t\tstrbuf_addf(&ctx->thread_name, \"th%02d:\", ctx->thread_id);\n+\tstrbuf_addstr(&ctx->thread_name, thread_name);\n+\tif (ctx->thread_name.len > TR2_MAX_THREAD_NAME)\n+\t\tstrbuf_setlen(&ctx->thread_name, TR2_MAX_THREAD_NAME);\n+\n+\tpthread_setspecific(tr2tls_key, ctx);\n+\n+\treturn ctx;\n+}\n+\n+struct tr2tls_thread_ctx *tr2tls_get_self(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = pthread_getspecific(tr2tls_key);\n+\n+\t/*\n+\t * If the thread-proc did not call trace2_thread_start(), we won't\n+\t * have any TLS data associated with the current thread.  Fix it\n+\t * here and silently continue.\n+\t */\n+\tif (!ctx)\n+\t\tctx = tr2tls_create_self(\"unknown\");\n+\n+\treturn ctx;\n+}\n+\n+int tr2tls_is_main_thread(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = pthread_getspecific(tr2tls_key);\n+\n+\treturn ctx == tr2tls_thread_main;\n+}\n+\n+void tr2tls_unset_self(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx;\n+\n+\tctx = tr2tls_get_self();\n+\n+\tpthread_setspecific(tr2tls_key, NULL);\n+\n+\tfree(ctx->array_us_start);\n+\tfree(ctx);\n+}\n+\n+void tr2tls_push_self(uint64_t us_now)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\n+\tALLOC_GROW(ctx->array_us_start, ctx->nr_open_regions + 1, ctx->alloc);\n+\tctx->array_us_start[ctx->nr_open_regions++] = us_now;\n+}\n+\n+void tr2tls_pop_self(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\n+\tif (!ctx->nr_open_regions)\n+\t\tBUG(\"no open regions in thread '%s'\", ctx->thread_name.buf);\n+\n+\tctx->nr_open_regions--;\n+}\n+\n+void tr2tls_pop_unwind_self(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\n+\twhile (ctx->nr_open_regions > 1)\n+\t\ttr2tls_pop_self();\n+}\n+\n+uint64_t tr2tls_region_elasped_self(uint64_t us)\n+{\n+\tstruct tr2tls_thread_ctx *ctx;\n+\tuint64_t us_start;\n+\n+\tctx = tr2tls_get_self();\n+\tif (!ctx->nr_open_regions)\n+\t\treturn 0;\n+\n+\tus_start = ctx->array_us_start[ctx->nr_open_regions - 1];\n+\n+\treturn us - us_start;\n+}\n+\n+uint64_t tr2tls_absolute_elapsed(uint64_t us)\n+{\n+\tif (!tr2tls_thread_main)\n+\t\treturn 0;\n+\n+\treturn us - tr2tls_us_start_main;\n+}\n+\n+void tr2tls_init(void)\n+{\n+\tpthread_key_create(&tr2tls_key, NULL);\n+\tinit_recursive_mutex(&tr2tls_mutex);\n+\n+\ttr2tls_thread_main = tr2tls_create_self(\"main\");\n+\t/*\n+\t * Keep a copy of the absolute start time of the main thread\n+\t * in a fixed variable since other threads need to access it.\n+\t * This also eliminates the need to lock accesses to the main\n+\t * thread's array (because of reallocs).\n+\t */\n+\ttr2tls_us_start_main = tr2tls_thread_main->array_us_start[0];\n+}\n+\n+void tr2tls_release(void)\n+{\n+\ttr2tls_unset_self();\n+\ttr2tls_thread_main = NULL;\n+\n+\tpthread_mutex_destroy(&tr2tls_mutex);\n+\tpthread_key_delete(tr2tls_key);\n+}\n+\n+int tr2tls_locked_increment(int *p)\n+{\n+\tint current_value;\n+\n+\tpthread_mutex_lock(&tr2tls_mutex);\n+\tcurrent_value = *p;\n+\t*p = current_value + 1;\n+\tpthread_mutex_unlock(&tr2tls_mutex);\n+\n+\treturn current_value;\n+}\ndiff --git a/trace2/tr2_tls.h b/trace2/tr2_tls.h\nnew file mode 100644\nindex 0000000000..b271ccd8dd\n--- /dev/null\n+++ b/trace2/tr2_tls.h\n@@ -0,0 +1,97 @@\n+#ifndef TR2_TLS_H\n+#define TR2_TLS_H\n+\n+struct strbuf;\n+\n+/*\n+ * Arbitry limit for thread names for column alignment.\n+ */\n+#define TR2_MAX_THREAD_NAME (24)\n+\n+struct tr2tls_thread_ctx {\n+\tstruct strbuf thread_name;\n+\tuint64_t *array_us_start;\n+\tint alloc;\n+\tint nr_open_regions; /* plays role of \"nr\" in ALLOC_GROW */\n+\tint thread_id;\n+};\n+\n+/*\n+ * Create TLS data for the current thread.  This gives us a place to\n+ * put per-thread data, such as thread start time, function nesting\n+ * and a per-thread label for our messages.\n+ *\n+ * We assume the first thread is \"main\".  Other threads are given\n+ * non-zero thread-ids to help distinguish messages from concurrent\n+ * threads.\n+ *\n+ * Truncate the thread name if necessary to help with column alignment\n+ * in printf-style messages.\n+ *\n+ * In this and all following functions the term \"self\" refers to the\n+ * current thread.\n+ */\n+struct tr2tls_thread_ctx *tr2tls_create_self(const char *thread_name);\n+\n+/*\n+ * Get our TLS data.\n+ */\n+struct tr2tls_thread_ctx *tr2tls_get_self(void);\n+\n+/*\n+ * return true if the current thread is the main thread.\n+ */\n+int tr2tls_is_main_thread(void);\n+\n+/*\n+ * Free our TLS data.\n+ */\n+void tr2tls_unset_self(void);\n+\n+/*\n+ * Begin a new nested region and remember the start time.\n+ */\n+void tr2tls_push_self(uint64_t us_now);\n+\n+/*\n+ * End the innermost nested region.\n+ */\n+void tr2tls_pop_self(void);\n+\n+/*\n+ * Pop any extra (above the first) open regions on the current\n+ * thread and discard.  During a thread-exit, we should only\n+ * have region[0] that was pushed in trace2_thread_start() if\n+ * the thread exits normally.\n+ */\n+void tr2tls_pop_unwind_self(void);\n+\n+/*\n+ * Compute the elapsed time since the innermost region in the\n+ * current thread started and the given time (usually now).\n+ */\n+uint64_t tr2tls_region_elasped_self(uint64_t us);\n+\n+/*\n+ * Compute the elapsed time since the main thread started\n+ * and the given time (usually now).  This is assumed to\n+ * be the absolute run time of the process.\n+ */\n+uint64_t tr2tls_absolute_elapsed(uint64_t us);\n+\n+/*\n+ * Initialize the tr2 TLS system.\n+ */\n+void tr2tls_init(void);\n+\n+/*\n+ * Free all tr2 TLS resources.\n+ */\n+void tr2tls_release(void);\n+\n+/*\n+ * Protected increment of an integer.\n+ */\n+int tr2tls_locked_increment(int *p);\n+\n+#endif /* TR2_TLS_H */\ndiff --git a/trace2/tr2_verb.c b/trace2/tr2_verb.c\nnew file mode 100644\nindex 0000000000..52be27c29f\n--- /dev/null\n+++ b/trace2/tr2_verb.c\n@@ -0,0 +1,30 @@\n+#include \"cache.h\"\n+#include \"trace2/tr2_verb.h\"\n+\n+#define TR2_ENVVAR_PARENT_VERB \"GIT_TR2_PARENT_VERB\"\n+\n+static struct strbuf tr2verb_hierarchy = STRBUF_INIT;\n+\n+void tr2_verb_append_hierarchy(const char *verb)\n+{\n+\tconst char *parent_verb = getenv(TR2_ENVVAR_PARENT_VERB);\n+\n+\tstrbuf_reset(&tr2verb_hierarchy);\n+\tif (parent_verb && *parent_verb) {\n+\t\tstrbuf_addstr(&tr2verb_hierarchy, parent_verb);\n+\t\tstrbuf_addch(&tr2verb_hierarchy, '/');\n+\t}\n+\tstrbuf_addstr(&tr2verb_hierarchy, verb);\n+\n+\tsetenv(TR2_ENVVAR_PARENT_VERB, tr2verb_hierarchy.buf, 1);\n+}\n+\n+const char *tr2_verb_get_hierarchy(void)\n+{\n+\treturn tr2verb_hierarchy.buf;\n+}\n+\n+void tr2_verb_release(void)\n+{\n+\tstrbuf_release(&tr2verb_hierarchy);\n+}\ndiff --git a/trace2/tr2_verb.h b/trace2/tr2_verb.h\nnew file mode 100644\nindex 0000000000..f84f873463\n--- /dev/null\n+++ b/trace2/tr2_verb.h\n@@ -0,0 +1,24 @@\n+#ifndef TR2_VERB_H\n+#define TR2_VERB_H\n+\n+/*\n+ * Append the current git command's \"verb\" to the list being maintained\n+ * in the environment.\n+ *\n+ * The hierarchy for a top-level git command is just the current verb.\n+ * For a child git process, the hierarchy lists the verbs of the parent\n+ * git processes (much like the SID).\n+ *\n+ * The hierarchy for the current process will be exported to the environment\n+ * and inherited by child processes.\n+ */\n+void tr2_verb_append_hierarchy(const char *verb);\n+\n+/*\n+ * Get the verb hierarchy for the current process.\n+ */\n+const char *tr2_verb_get_hierarchy(void);\n+\n+void tr2_verb_release(void);\n+\n+#endif /* TR2_VERB_H */\ndiff --git a/usage.c b/usage.c\nindex cc803336bd..5ec7511e11 100644\n--- a/usage.c\n+++ b/usage.c\n@@ -22,17 +22,48 @@ void vreportf(const char *prefix, const char *err, va_list params)\n static NORETURN void usage_builtin(const char *err, va_list params)\n {\n \tvreportf(\"usage: \", err, params);\n+\n+\t/*\n+\t * When we detect a usage error *before* the command dispatch in\n+\t * cmd_main(), we don't know what verb to report.  Force it to this\n+\t * to facilitate post-processing.\n+\t */\n+\ttrace2_cmd_verb(\"_usage_\");\n+\n+\t/*\n+\t * Currently, the (err, params) are usually just the static usage\n+\t * string which isn't very useful here.  Usually, the call site\n+\t * manually calls fprintf(stderr,...) with the actual detailed\n+\t * syntax error before calling usage().\n+\t *\n+\t * TODO It would be nice to update the call sites to pass both\n+\t * the static usage string and the detailed error message.\n+\t */\n+\n \texit(129);\n }\n \n static NORETURN void die_builtin(const char *err, va_list params)\n {\n+\t/*\n+\t * We call this trace2 function first and expect it to va_copy 'params'\n+\t * before using it (because an 'ap' can only be walked once).\n+\t */\n+\ttrace2_cmd_error_va(err, params);\n+\n \tvreportf(\"fatal: \", err, params);\n+\n \texit(128);\n }\n \n static void error_builtin(const char *err, va_list params)\n {\n+\t/*\n+\t * We call this trace2 function first and expect it to va_copy 'params'\n+\t * before using it (because an 'ap' can only be walked once).\n+\t */\n+\ttrace2_cmd_error_va(err, params);\n+\n \tvreportf(\"error: \", err, params);\n }\n \n-- \ngitgitgadget\n\n"},{"id":"368178","messageId":"0a05e15978509cd6d2394d7eab1ced1ee3614605.1548877863.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 13/14] trace2:data: add subverb for rebase","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T19:51:15Z","receivedAt":"2019-01-30T19:51:31Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/rebase.c | 17 +++++++++++++++++\n 1 file changed, 17 insertions(+)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 774264bae8..49cef31184 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -850,6 +850,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tACTION_EDIT_TODO,\n \t\tACTION_SHOW_CURRENT_PATCH,\n \t} action = NO_ACTION;\n+\tstatic const char *action_names[] = { N_(\"undefined\"),\n+\t\t\t\t\t      N_(\"continue\"),\n+\t\t\t\t\t      N_(\"skip\"),\n+\t\t\t\t\t      N_(\"abort\"),\n+\t\t\t\t\t      N_(\"quit\"),\n+\t\t\t\t\t      N_(\"edit_todo\"),\n+\t\t\t\t\t      N_(\"show_current_patch\"),\n+\t\t\t\t\t      NULL };\n \tconst char *gpg_sign = NULL;\n \tstruct string_list exec = STRING_LIST_INIT_NODUP;\n \tconst char *rebase_merges = NULL;\n@@ -1039,6 +1047,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tdie(_(\"The --edit-todo action can only be used during \"\n \t\t      \"interactive rebase.\"));\n \n+\tif (trace2_is_enabled()) {\n+\t\tif (is_interactive(&options))\n+\t\t\ttrace2_cmd_subverb(\"interactive\");\n+\t\telse if (exec.nr)\n+\t\t\ttrace2_cmd_subverb(\"interactive-exec\");\n+\t\telse\n+\t\t\ttrace2_cmd_subverb(action_names[action]);\n+\t}\n+\n \tswitch (action) {\n \tcase ACTION_CONTINUE: {\n \t\tstruct object_id head;\n-- \ngitgitgadget\n\n"},{"id":"368179","messageId":"b7e6644f4bc365e3bca02294c87a88c99d18f371.1548877863.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v3 07/14] trace2:data: add trace2 transport child classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T19:51:10Z","receivedAt":"2019-01-30T19:51:33Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 child classification for transport processes.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n connect.c          | 3 +++\n transport-helper.c | 2 ++\n 2 files changed, 5 insertions(+)\n\ndiff --git a/connect.c b/connect.c\nindex 24281b6082..3c6f829a05 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -1251,6 +1251,7 @@ struct child_process *git_connect(int fd[2], const char *url,\n \t\tconn = NULL;\n \t} else if (protocol == PROTO_GIT) {\n \t\tconn = git_connect_git(fd, hostandport, path, prog, version, flags);\n+\t\tconn->trace2_child_class = \"transport/git\";\n \t} else {\n \t\tstruct strbuf cmd = STRBUF_INIT;\n \t\tconst char *const *var;\n@@ -1293,9 +1294,11 @@ struct child_process *git_connect(int fd[2], const char *url,\n \t\t\t\tstrbuf_release(&cmd);\n \t\t\t\treturn NULL;\n \t\t\t}\n+\t\t\tconn->trace2_child_class = \"transport/ssh\";\n \t\t\tfill_ssh_args(conn, ssh_host, port, version, flags);\n \t\t} else {\n \t\t\ttransport_check_allowed(\"file\");\n+\t\t\tconn->trace2_child_class = \"transport/file\";\n \t\t\tif (version > 0) {\n \t\t\t\targv_array_pushf(&conn->env_array, GIT_PROTOCOL_ENVIRONMENT \"=version=%d\",\n \t\t\t\t\t\t version);\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 6cf3bb324e..a01cc0093f 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -127,6 +127,8 @@ static struct child_process *get_helper(struct transport *transport)\n \t\targv_array_pushf(&helper->env_array, \"%s=%s\",\n \t\t\t\t GIT_DIR_ENVIRONMENT, get_git_dir());\n \n+\thelper->trace2_child_class = helper->args.argv[0]; /* \"remote-<name>\" */\n+\n \tcode = start_command(helper);\n \tif (code < 0 && errno == ENOENT)\n \t\tdie(_(\"unable to find remote helper for '%s'\"), data->name);\n-- \ngitgitgadget\n\n"},{"id":"368181","messageId":"pull.108.v4.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v3.git.gitgitgadget@gmail.com","subject":"[PATCH v4 00/14] Trace2 tracing facility","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T20:56:20Z","receivedAt":"2019-01-30T20:56:27Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"Sorry to spam the list, but here is V4. After building V3 on 3 platforms\nwithout error and submitting, the compilers on platforms 4 and 5 complained\nabout a variable declaration. (sigh) [] fix declaration after first\nstatement [] add -DNO_UNIX_SOCKETS to BASIC_CFLAGS when NO_UNIX_SOCKETS is\ndefined in the Makefile.\n\n\n----------------------------------------------------------------------------\n\nV3 addresses: [] re-fix the trace2 tests using an inline environment\nvariable rather than exporting and unsetting. [] overhaul the design\ndocument to include prototype declarations and more file format information.\n[] incorporate most of the suggestions from clang-format. [] add ability to\ntrace to a unix domain socket. [] added forward declarations suggested by\nRamsay. [] rebased onto current master to fixup conflict with\nsb/submodule-recursive-fetch-gets-the-tip that was merged yesterday.\n\n\n----------------------------------------------------------------------------\n\nV2 addresses: [] \"jh/trace2\" bad interaction with \"js/vsts-ci\" in \"pu\". []\ncoccinelle warnings in trace2/tr2_tgt_perf.c reported during CI testing.\n\n\n----------------------------------------------------------------------------\n\nThis patch series contains a greatly refactored version of my original\nTrace2 series [1] from August 2018.\n\nA new design doc in Documentation/technical/api-trace2.txt (in the first\ncommit) explains the relationship of Trace2 to the current tracing facility.\nCalls to the current tracing facility have not been changed, rather new\ntrace2 calls have been added so that both continue to work in parallel for\nthe time being.\n\n[1] https://public-inbox.org/git/pull.29.git.gitgitgadget@gmail.com/\n\nCc: gitster@pobox.comCc: peff@peff.netCc: jrnieder@gmail.com\n\nDerrick Stolee (1):\n  pack-objects: add trace2 regions\n\nJeff Hostetler (13):\n  trace2: Documentation/technical/api-trace2.txt\n  trace2: create new combined trace facility\n  trace2: collect platform-specific process information\n  trace2:data: add trace2 regions to wt-status\n  trace2:data: add editor/pager child classification\n  trace2:data: add trace2 sub-process classification\n  trace2:data: add trace2 transport child classification\n  trace2:data: add trace2 hook classification\n  trace2:data: add trace2 instrumentation to index read/write\n  trace2:data: add subverb to checkout command\n  trace2:data: add subverb to reset command\n  trace2:data: add subverb for rebase\n  trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh\n\n Documentation/technical/api-trace2.txt | 1347 ++++++++++++++++++++++++\n Makefile                               |   15 +-\n builtin/am.c                           |    1 +\n builtin/checkout.c                     |    7 +\n builtin/pack-objects.c                 |   16 +-\n builtin/rebase.c                       |   17 +\n builtin/receive-pack.c                 |    4 +\n builtin/reset.c                        |    6 +\n builtin/submodule--helper.c            |    9 +-\n builtin/worktree.c                     |    1 +\n cache.h                                |    1 +\n common-main.c                          |   13 +-\n compat/mingw.c                         |   11 +-\n compat/mingw.h                         |    3 +-\n compat/win32/ancestry.c                |  101 ++\n config.c                               |    2 +\n config.mak.uname                       |    2 +\n connect.c                              |    3 +\n editor.c                               |    1 +\n exec-cmd.c                             |    2 +\n git-compat-util.h                      |    7 +\n git.c                                  |   65 ++\n pager.c                                |    1 +\n read-cache.c                           |   51 +-\n remote-curl.c                          |    7 +\n repository.c                           |    2 +\n repository.h                           |    3 +\n run-command.c                          |   59 +-\n run-command.h                          |   13 +-\n sequencer.c                            |    2 +\n sh-i18n--envsubst.c                    |    3 +\n sub-process.c                          |    1 +\n submodule.c                            |   11 +-\n t/helper/test-parse-options.c          |    3 +\n t/helper/test-tool.c                   |    4 +\n t/helper/test-tool.h                   |    1 +\n t/helper/test-trace2.c                 |  274 +++++\n t/t0001-init.sh                        |    1 +\n t/t0210-trace2-normal.sh               |  135 +++\n t/t0210/scrub_normal.perl              |   48 +\n t/t0211-trace2-perf.sh                 |  153 +++\n t/t0211/scrub_perf.perl                |   76 ++\n t/t0212-trace2-event.sh                |  234 ++++\n t/t0212/parse_events.perl              |  251 +++++\n trace2.c                               |  807 ++++++++++++++\n trace2.h                               |  380 +++++++\n trace2/tr2_cfg.c                       |   90 ++\n trace2/tr2_cfg.h                       |   19 +\n trace2/tr2_dst.c                       |  197 ++++\n trace2/tr2_dst.h                       |   36 +\n trace2/tr2_sid.c                       |   67 ++\n trace2/tr2_sid.h                       |   18 +\n trace2/tr2_tbuf.c                      |   32 +\n trace2/tr2_tbuf.h                      |   23 +\n trace2/tr2_tgt.h                       |  131 +++\n trace2/tr2_tgt_event.c                 |  590 +++++++++++\n trace2/tr2_tgt_normal.c                |  325 ++++++\n trace2/tr2_tgt_perf.c                  |  536 ++++++++++\n trace2/tr2_tls.c                       |  164 +++\n trace2/tr2_tls.h                       |   97 ++\n trace2/tr2_verb.c                      |   30 +\n trace2/tr2_verb.h                      |   24 +\n transport-helper.c                     |    2 +\n transport.c                            |    1 +\n usage.c                                |   31 +\n wt-status.c                            |   24 +-\n 66 files changed, 6569 insertions(+), 22 deletions(-)\n create mode 100644 Documentation/technical/api-trace2.txt\n create mode 100644 compat/win32/ancestry.c\n create mode 100644 t/helper/test-trace2.c\n create mode 100755 t/t0210-trace2-normal.sh\n create mode 100644 t/t0210/scrub_normal.perl\n create mode 100755 t/t0211-trace2-perf.sh\n create mode 100644 t/t0211/scrub_perf.perl\n create mode 100755 t/t0212-trace2-event.sh\n create mode 100644 t/t0212/parse_events.perl\n create mode 100644 trace2.c\n create mode 100644 trace2.h\n create mode 100644 trace2/tr2_cfg.c\n create mode 100644 trace2/tr2_cfg.h\n create mode 100644 trace2/tr2_dst.c\n create mode 100644 trace2/tr2_dst.h\n create mode 100644 trace2/tr2_sid.c\n create mode 100644 trace2/tr2_sid.h\n create mode 100644 trace2/tr2_tbuf.c\n create mode 100644 trace2/tr2_tbuf.h\n create mode 100644 trace2/tr2_tgt.h\n create mode 100644 trace2/tr2_tgt_event.c\n create mode 100644 trace2/tr2_tgt_normal.c\n create mode 100644 trace2/tr2_tgt_perf.c\n create mode 100644 trace2/tr2_tls.c\n create mode 100644 trace2/tr2_tls.h\n create mode 100644 trace2/tr2_verb.c\n create mode 100644 trace2/tr2_verb.h\n\n\nbase-commit: b5101f929789889c2e536d915698f58d5c5c6b7a\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-108%2Fjeffhostetler%2Fcore-trace2-2019-v0-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-108/jeffhostetler/core-trace2-2019-v0-v4\nPull-Request: https://github.com/gitgitgadget/git/pull/108\n\nRange-diff vs v3:\n\n  1:  60b56d6a8f =  1:  60b56d6a8f trace2: Documentation/technical/api-trace2.txt\n  2:  bfe2fe0bbe !  2:  8c9687830e trace2: create new combined trace facility\n     @@ -52,6 +52,17 @@\n       LIB_OBJS += trailer.o\n       LIB_OBJS += transport.o\n       LIB_OBJS += transport-helper.o\n     +@@\n     + \tLIB_OBJS += compat/inet_pton.o\n     + \tBASIC_CFLAGS += -DNO_INET_PTON\n     + endif\n     +-ifndef NO_UNIX_SOCKETS\n     ++ifdef NO_UNIX_SOCKETS\n     ++\tBASIC_CFLAGS += -DNO_UNIX_SOCKETS\n     ++else\n     + \tLIB_OBJS += unix-socket.o\n     + \tPROGRAM_OBJS += credential-cache.o\n     + \tPROGRAM_OBJS += credential-cache--daemon.o\n      \n       diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\n       --- a/builtin/submodule--helper.c\n     @@ -2154,6 +2165,8 @@\n      +\n      +void tr2_dst_write_line(struct tr2_dst *dst, struct strbuf *buf_line)\n      +{\n     ++\tint fd = tr2_dst_get_trace_fd(dst);\n     ++\n      +\tstrbuf_complete_line(buf_line); /* ensure final NL on buffer */\n      +\n      +\t/*\n     @@ -2171,9 +2184,6 @@\n      +\t *\n      +\t * If we get an IO error, just close the trace dst.\n      +\t */\n     -+\n     -+\tint fd = tr2_dst_get_trace_fd(dst);\n     -+\n      +\tif (write(fd, buf_line->buf, buf_line->len) >= 0)\n      +\t\treturn;\n      +\n  3:  e8b8226640 =  3:  6167aba76c trace2: collect platform-specific process information\n  4:  0d59a6f2eb =  4:  3624c6cbd9 trace2:data: add trace2 regions to wt-status\n  5:  2513091f77 =  5:  638fdb294f trace2:data: add editor/pager child classification\n  6:  785f6f866e =  6:  f4f0453e6b trace2:data: add trace2 sub-process classification\n  7:  b7e6644f4b =  7:  660b83c453 trace2:data: add trace2 transport child classification\n  8:  52aace533b =  8:  ab26888126 trace2:data: add trace2 hook classification\n  9:  a2bcc6fd28 =  9:  87a73af338 trace2:data: add trace2 instrumentation to index read/write\n 10:  d508322638 = 10:  be707ea960 pack-objects: add trace2 regions\n 11:  33e431be88 = 11:  b4c8ff53dd trace2:data: add subverb to checkout command\n 12:  e46e92bc7c = 12:  ed1d612006 trace2:data: add subverb to reset command\n 13:  0a05e15978 = 13:  4f55f33624 trace2:data: add subverb for rebase\n 14:  aeaf990b1b = 14:  8d47cd71ed trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh\n\n-- \ngitgitgadget\n"},{"id":"368182","messageId":"4f55f336245c395fa3386b3394c7bd41148ae928.1548881779.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 13/14] trace2:data: add subverb for rebase","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T20:56:33Z","receivedAt":"2019-01-30T20:56:41Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/rebase.c | 17 +++++++++++++++++\n 1 file changed, 17 insertions(+)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 774264bae8..49cef31184 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -850,6 +850,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tACTION_EDIT_TODO,\n \t\tACTION_SHOW_CURRENT_PATCH,\n \t} action = NO_ACTION;\n+\tstatic const char *action_names[] = { N_(\"undefined\"),\n+\t\t\t\t\t      N_(\"continue\"),\n+\t\t\t\t\t      N_(\"skip\"),\n+\t\t\t\t\t      N_(\"abort\"),\n+\t\t\t\t\t      N_(\"quit\"),\n+\t\t\t\t\t      N_(\"edit_todo\"),\n+\t\t\t\t\t      N_(\"show_current_patch\"),\n+\t\t\t\t\t      NULL };\n \tconst char *gpg_sign = NULL;\n \tstruct string_list exec = STRING_LIST_INIT_NODUP;\n \tconst char *rebase_merges = NULL;\n@@ -1039,6 +1047,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tdie(_(\"The --edit-todo action can only be used during \"\n \t\t      \"interactive rebase.\"));\n \n+\tif (trace2_is_enabled()) {\n+\t\tif (is_interactive(&options))\n+\t\t\ttrace2_cmd_subverb(\"interactive\");\n+\t\telse if (exec.nr)\n+\t\t\ttrace2_cmd_subverb(\"interactive-exec\");\n+\t\telse\n+\t\t\ttrace2_cmd_subverb(action_names[action]);\n+\t}\n+\n \tswitch (action) {\n \tcase ACTION_CONTINUE: {\n \t\tstruct object_id head;\n-- \ngitgitgadget\n\n"},{"id":"368183","messageId":"8d47cd71ede3fd4e932e26756ef8f3f82b41d659.1548881779.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 14/14] trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T20:56:34Z","receivedAt":"2019-01-30T20:56:47Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate unit tests for Trace2.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                  |   1 +\n t/helper/test-tool.c      |   1 +\n t/helper/test-tool.h      |   1 +\n t/helper/test-trace2.c    | 274 ++++++++++++++++++++++++++++++++++++++\n t/t0210-trace2-normal.sh  | 135 +++++++++++++++++++\n t/t0210/scrub_normal.perl |  48 +++++++\n t/t0211-trace2-perf.sh    | 153 +++++++++++++++++++++\n t/t0211/scrub_perf.perl   |  76 +++++++++++\n t/t0212-trace2-event.sh   | 234 ++++++++++++++++++++++++++++++++\n t/t0212/parse_events.perl | 251 ++++++++++++++++++++++++++++++++++\n 10 files changed, 1174 insertions(+)\n create mode 100644 t/helper/test-trace2.c\n create mode 100755 t/t0210-trace2-normal.sh\n create mode 100644 t/t0210/scrub_normal.perl\n create mode 100755 t/t0211-trace2-perf.sh\n create mode 100644 t/t0211/scrub_perf.perl\n create mode 100755 t/t0212-trace2-event.sh\n create mode 100644 t/t0212/parse_events.perl\n\ndiff --git a/Makefile b/Makefile\nindex 6b49729c3c..6160c65317 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -762,6 +762,7 @@ TEST_BUILTINS_OBJS += test-string-list.o\n TEST_BUILTINS_OBJS += test-submodule-config.o\n TEST_BUILTINS_OBJS += test-submodule-nested-repo-config.o\n TEST_BUILTINS_OBJS += test-subprocess.o\n+TEST_BUILTINS_OBJS += test-trace2.o\n TEST_BUILTINS_OBJS += test-urlmatch-normalization.o\n TEST_BUILTINS_OBJS += test-wildmatch.o\n TEST_BUILTINS_OBJS += test-windows-named-pipe.o\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 0a704fe8d2..f594cb6785 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -51,6 +51,7 @@ static struct test_cmd cmds[] = {\n \t{ \"submodule-config\", cmd__submodule_config },\n \t{ \"submodule-nested-repo-config\", cmd__submodule_nested_repo_config },\n \t{ \"subprocess\", cmd__subprocess },\n+\t{ \"trace2\", cmd__trace2 },\n \t{ \"urlmatch-normalization\", cmd__urlmatch_normalization },\n \t{ \"wildmatch\", cmd__wildmatch },\n #ifdef GIT_WINDOWS_NATIVE\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex ca5c88edb2..61d2938ce5 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -46,6 +46,7 @@ int cmd__string_list(int argc, const char **argv);\n int cmd__submodule_config(int argc, const char **argv);\n int cmd__submodule_nested_repo_config(int argc, const char **argv);\n int cmd__subprocess(int argc, const char **argv);\n+int cmd__trace2(int argc, const char **argv);\n int cmd__urlmatch_normalization(int argc, const char **argv);\n int cmd__wildmatch(int argc, const char **argv);\n #ifdef GIT_WINDOWS_NATIVE\ndiff --git a/t/helper/test-trace2.c b/t/helper/test-trace2.c\nnew file mode 100644\nindex 0000000000..37750f5256\n--- /dev/null\n+++ b/t/helper/test-trace2.c\n@@ -0,0 +1,274 @@\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"argv-array.h\"\n+#include \"run-command.h\"\n+#include \"exec-cmd.h\"\n+#include \"config.h\"\n+\n+typedef int(fn_unit_test)(int argc, const char **argv);\n+\n+struct unit_test {\n+\tfn_unit_test *ut_fn;\n+\tconst char *ut_name;\n+\tconst char *ut_usage;\n+};\n+\n+#define MyOk 0\n+#define MyError 1\n+\n+static int get_i(int *p_value, const char *data)\n+{\n+\tchar *endptr;\n+\n+\tif (!data || !*data)\n+\t\treturn MyError;\n+\n+\t*p_value = strtol(data, &endptr, 10);\n+\tif (*endptr || errno == ERANGE)\n+\t\treturn MyError;\n+\n+\treturn MyOk;\n+}\n+\n+/*\n+ * Cause process to exit with the requested value via \"return\".\n+ *\n+ * Rely on test-tool.c:cmd_main() to call trace2_cmd_exit()\n+ * with our result.\n+ *\n+ * Test harness can confirm:\n+ * [] the process-exit value.\n+ * [] the \"code\" field in the \"exit\" trace2 event.\n+ * [] the \"code\" field in the \"atexit\" trace2 event.\n+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n+ * [] \"def_param\" events for all of the \"interesting\" pre-defined\n+ * config settings.\n+ */\n+static int ut_001return(int argc, const char **argv)\n+{\n+\tint rc;\n+\n+\tif (get_i(&rc, argv[0]))\n+\t\tdie(\"expect <exit_code>\");\n+\n+\treturn rc;\n+}\n+\n+/*\n+ * Cause the process to exit with the requested value via \"exit()\".\n+ *\n+ * Test harness can confirm:\n+ * [] the \"code\" field in the \"exit\" trace2 event.\n+ * [] the \"code\" field in the \"atexit\" trace2 event.\n+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n+ * [] \"def_param\" events for all of the \"interesting\" pre-defined\n+ * config settings.\n+ */\n+static int ut_002exit(int argc, const char **argv)\n+{\n+\tint rc;\n+\n+\tif (get_i(&rc, argv[0]))\n+\t\tdie(\"expect <exit_code>\");\n+\n+\texit(rc);\n+}\n+\n+/*\n+ * Send an \"error\" event with each value in argv.  Normally, git only issues\n+ * a single \"error\" event immediately before issuing an \"exit\" event (such\n+ * as in die() or BUG()), but multiple \"error\" events are allowed.\n+ *\n+ * Test harness can confirm:\n+ * [] a trace2 \"error\" event for each value in argv.\n+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n+ * [] (optional) the file:line in the \"exit\" event refers to this function.\n+ */\n+static int ut_003error(int argc, const char **argv)\n+{\n+\tint k;\n+\n+\tif (!argv[0] || !*argv[0])\n+\t\tdie(\"expect <error_message>\");\n+\n+\tfor (k = 0; k < argc; k++)\n+\t\terror(\"%s\", argv[k]);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Run a child process and wait for it to finish and exit with its return code.\n+ * test-tool trace2 004child [<child-command-line>]\n+ *\n+ * For example:\n+ * test-tool trace2 004child git version\n+ * test-tool trace2 004child test-tool trace2 001return 0\n+ * test-tool trace2 004child test-tool trace2 004child test-tool trace2 004child\n+ * test-tool trace2 004child git -c alias.xyz=version xyz\n+ *\n+ * Test harness can confirm:\n+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n+ * [] that the outer process has a single component SID (or depth \"d0\" in\n+ *    the PERF stream).\n+ * [] that \"child_start\" and \"child_exit\" events are generated for the child.\n+ * [] if the child process is an instrumented executable:\n+ *    [] that \"version\", \"start\", ..., \"exit\", and \"atexit\" events are\n+ *       generated by the child process.\n+ *    [] that the child process events have a multiple component SID (or\n+ *       depth \"dN+1\" in the PERF stream).\n+ * [] that the child exit code is propagated to the parent process \"exit\"\n+ *    and \"atexit\" events..\n+ * [] (optional) that the \"t_abs\" field in the child process \"atexit\" event\n+ *    is less than the \"t_rel\" field in the \"child_exit\" event of the parent\n+ *    process.\n+ * [] if the child process is like the alias example above,\n+ *    [] (optional) the child process attempts to run \"git-xyx\" as a dashed\n+ *       command.\n+ *    [] the child process emits an \"alias\" event with \"xyz\" => \"version\"\n+ *    [] the child process runs \"git version\" as a child process.\n+ *    [] the child process has a 3 component SID (or depth \"d2\" in the PERF\n+ *       stream).\n+ */\n+static int ut_004child(int argc, const char **argv)\n+{\n+\tint result;\n+\n+\t/*\n+\t * Allow empty <child_command_line> so we can do arbitrarily deep\n+\t * command nesting and let the last one be null.\n+\t */\n+\tif (!argc)\n+\t\treturn 0;\n+\n+\tresult = run_command_v_opt(argv, 0);\n+\texit(result);\n+}\n+\n+/*\n+ * Exec a git command.  This may either create a child process (Windows)\n+ * or replace the existing process.\n+ * test-tool trace2 005exec <git_command_args>\n+ *\n+ * For example:\n+ * test-tool trace2 005exec version\n+ *\n+ * Test harness can confirm (on Windows):\n+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n+ * [] that the outer process has a single component SID (or depth \"d0\" in\n+ *    the PERF stream).\n+ * [] that \"exec\" and \"exec_result\" events are generated for the child\n+ *    process (since the Windows compatibility layer fakes an exec() with\n+ *    a CreateProcess(), WaitForSingleObject(), and exit()).\n+ * [] that the child process has multiple component SID (or depth \"dN+1\"\n+ *    in the PERF stream).\n+ *\n+ * Test harness can confirm (on platforms with a real exec() function):\n+ * [] TODO talk about process replacement and how it affects SID.\n+ */\n+static int ut_005exec(int argc, const char **argv)\n+{\n+\tint result;\n+\n+\tif (!argc)\n+\t\treturn 0;\n+\n+\tresult = execv_git_cmd(argv);\n+\treturn result;\n+}\n+\n+static int ut_006data(int argc, const char **argv)\n+{\n+\tconst char *usage_error =\n+\t\t\"expect <cat0> <k0> <v0> [<cat1> <k1> <v1> [...]]\";\n+\n+\tif (argc % 3 != 0)\n+\t\tdie(\"%s\", usage_error);\n+\n+\twhile (argc) {\n+\t\tif (!argv[0] || !*argv[0] || !argv[1] || !*argv[1] ||\n+\t\t    !argv[2] || !*argv[2])\n+\t\t\tdie(\"%s\", usage_error);\n+\n+\t\ttrace2_data_string(argv[0], the_repository, argv[1], argv[2]);\n+\t\targv += 3;\n+\t\targc -= 3;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Usage:\n+ *     test-tool trace2 <ut_name_1> <ut_usage_1>\n+ *     test-tool trace2 <ut_name_2> <ut_usage_2>\n+ *     ...\n+ */\n+#define USAGE_PREFIX \"test-tool trace2\"\n+\n+static struct unit_test ut_table[] = {\n+\t{ ut_001return,   \"001return\", \"<exit_code>\" },\n+\t{ ut_002exit,     \"002exit\",   \"<exit_code>\" },\n+\t{ ut_003error,    \"003error\",  \"<error_message>+\" },\n+\t{ ut_004child,    \"004child\",  \"[<child_command_line>]\" },\n+\t{ ut_005exec,     \"005exec\",   \"<git_command_args>\" },\n+\t{ ut_006data,     \"006data\",   \"[<category> <key> <value>]+\" },\n+};\n+\n+#define for_each_ut(k, ut_k)\t\t\t\\\n+\tfor (k = 0, ut_k = &ut_table[k];\t\\\n+\t     k < ARRAY_SIZE(ut_table);\t\t\\\n+\t     k++, ut_k = &ut_table[k])\n+\n+static int print_usage(void)\n+{\n+\tint k;\n+\tstruct unit_test *ut_k;\n+\n+\tfprintf(stderr, \"usage:\\n\");\n+\tfor_each_ut(k, ut_k)\n+\t{\n+\t\tfprintf(stderr, \"\\t%s %s %s\\n\", USAGE_PREFIX, ut_k->ut_name,\n+\t\t\tut_k->ut_usage);\n+\t}\n+\n+\treturn 129;\n+}\n+\n+/*\n+ * Issue various trace2 events for testing.\n+ *\n+ * We assume that these trace2 routines has already been called:\n+ *    [] trace2_initialize()      [common-main.c:main()]\n+ *    [] trace2_cmd_start()       [common-main.c:main()]\n+ *    [] trace2_cmd_verb()        [test-tool.c:cmd_main()]\n+ *    [] tracd2_cmd_list_config() [test-tool.c:cmd_main()]\n+ * So that:\n+ *    [] the various trace2 streams are open.\n+ *    [] the process SID has been created.\n+ *    [] the \"version\" event has been generated.\n+ *    [] the \"start\" event has been generated.\n+ *    [] the \"verb\" event has been generated.\n+ *    [] this writes various \"def_param\" events for interesting config values.\n+ *\n+ * We further assume that if we return (rather than exit()), trace2_cmd_exit()\n+ * will be called by test-tool.c:cmd_main().\n+ */\n+int cmd__trace2(int argc, const char **argv)\n+{\n+\tint k;\n+\tstruct unit_test *ut_k;\n+\n+\targc--; /* skip over \"trace2\" arg */\n+\targv++;\n+\n+\tif (argc) {\n+\t\tfor_each_ut(k, ut_k)\n+\t\t{\n+\t\t\tif (!strcmp(argv[0], ut_k->ut_name))\n+\t\t\t\treturn ut_k->ut_fn(argc - 1, argv + 1);\n+\t\t}\n+\t}\n+\n+\treturn print_usage();\n+}\ndiff --git a/t/t0210-trace2-normal.sh b/t/t0210-trace2-normal.sh\nnew file mode 100755\nindex 0000000000..ab4636fe5a\n--- /dev/null\n+++ b/t/t0210-trace2-normal.sh\n@@ -0,0 +1,135 @@\n+#!/bin/sh\n+\n+test_description='test trace2 facility (normal target)'\n+. ./test-lib.sh\n+\n+# Add t/helper directory to PATH so that we can use a relative\n+# path to run nested instances of test-tool.exe (see 004child).\n+# This helps with HEREDOC comparisons later.\n+TTDIR=\"$GIT_BUILD_DIR/t/helper/\" && export TTDIR\n+PATH=\"$TTDIR:$PATH\" && export PATH\n+\n+# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe\n+# Warning: to do the actual diff/comparison, so the HEREDOCs here\n+# Warning: only cover our actual calls to test-tool and/or git.\n+# Warning: So you may see extra lines in artifact files when\n+# Warning: interactively debugging.\n+\n+# Turn off any inherited trace2 settings for this test.\n+unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT\n+unset GIT_TR2_BRIEF\n+unset GIT_TR2_CONFIG_PARAMS\n+\n+V=$(git version | sed -e 's/^git version //') && export V\n+\n+# There are multiple trace2 targets: normal, perf, and event.\n+# Trace2 events will/can be written to each active target (subject\n+# to whatever filtering that target decides to do).\n+# This script tests the normal target in isolation.\n+#\n+# Defer setting GIT_TR2 until the actual command line we want to test\n+# because hidden git and test-tool commands run by the test harness\n+# can contaminate our output.\n+\n+# Enable \"brief\" feature which turns off \"<clock> <file>:<line> \" prefix.\n+GIT_TR2_BRIEF=1 && export GIT_TR2_BRIEF\n+\n+# Basic tests of the trace2 normal stream.  Since this stream is used\n+# primarily with printf-style debugging/tracing, we do limited testing\n+# here.\n+#\n+# We do confirm the following API features:\n+# [] the 'version <v>' event\n+# [] the 'start <argv>' event\n+# [] the 'cmd_verb <verb>' event\n+# [] the 'exit <time> code:<code>' event\n+# [] the 'atexit <time> code:<code>' event\n+#\n+# Fields of the form _FIELD_ are tokens that have been replaced (such\n+# as the elapsed time).\n+\n+# Verb 001return\n+#\n+# Implicit return from cmd_<verb> function propagates <code>.\n+\n+test_expect_success 'normal stream, return code 0' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\tGIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 001return 0\n+\t\tcmd_verb trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:0\n+\t\tatexit elapsed:_TIME_ code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'normal stream, return code 1' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\ttest_must_fail env GIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 001return 1 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 001return 1\n+\t\tcmd_verb trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:1\n+\t\tatexit elapsed:_TIME_ code:1\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 002exit\n+#\n+# Explicit exit(code) from within cmd_<verb> propagates <code>.\n+\n+test_expect_success 'normal stream, exit code 0' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\tGIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 002exit 0 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 002exit 0\n+\t\tcmd_verb trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:0\n+\t\tatexit elapsed:_TIME_ code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'normal stream, exit code 1' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\ttest_must_fail env GIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 002exit 1 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 002exit 1\n+\t\tcmd_verb trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:1\n+\t\tatexit elapsed:_TIME_ code:1\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 003error\n+#\n+# To the above, add multiple 'error <msg>' events\n+\n+test_expect_success 'normal stream, error event' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\tGIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 003error \"hello world\" \"this is a test\" &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 003error '\\''hello world'\\'' '\\''this is a test'\\''\n+\t\tcmd_verb trace2 (trace2)\n+\t\terror hello world\n+\t\terror this is a test\n+\t\texit elapsed:_TIME_ code:0\n+\t\tatexit elapsed:_TIME_ code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t0210/scrub_normal.perl b/t/t0210/scrub_normal.perl\nnew file mode 100644\nindex 0000000000..c65d1a815e\n--- /dev/null\n+++ b/t/t0210/scrub_normal.perl\n@@ -0,0 +1,48 @@\n+#!/usr/bin/perl\n+#\n+# Scrub the variable fields from the normal trace2 output to\n+# make testing easier.\n+\n+use strict;\n+use warnings;\n+\n+my $float = '[0-9]*\\.[0-9]+([eE][-+]?[0-9]+)?';\n+\n+# This code assumes that the trace2 data was written with bare\n+# turned on (which omits the \"<clock> <file>:<line>\" prefix.\n+\n+while (<>) {\n+    # Various messages include an elapsed time in the middle\n+    # of the message.  Replace the time with a placeholder to\n+    # simplify our HEREDOC in the test script.\n+    s/elapsed:$float/elapsed:_TIME_/g;\n+\n+    my $line = $_;\n+\n+    # we expect:\n+    #    start <argv0> [<argv1> [<argv2> [...]]]\n+    #\n+    # where argv0 might be a relative or absolute path, with\n+    # or without quotes, and platform dependent.  Replace argv0\n+    # with a token for HEREDOC matching in the test script.\n+\n+    if ($line =~ m/^start/) {\n+\t$line =~ /^start\\s+(.*)/;\n+\tmy $argv = $1;\n+\t$argv =~ m/(\\'[^\\']*\\'|[^ ]+)\\s+(.*)/;\n+\tmy $argv_0 = $1;\n+\tmy $argv_rest = $2;\n+\n+\tprint \"start _EXE_ $argv_rest\\n\";\n+    }\n+    elsif ($line =~ m/^cmd_path/) {\n+\t# Likewise, the 'cmd_path' message breaks out argv[0].\n+\t#\n+\t# This line is only emitted when RUNTIME_PREFIX is defined,\n+\t# so just omit it for testing purposes.\n+\t# print \"cmd_path _EXE_\\n\";\n+    }\n+    else {\n+\tprint \"$line\";\n+    }\n+}\ndiff --git a/t/t0211-trace2-perf.sh b/t/t0211-trace2-perf.sh\nnew file mode 100755\nindex 0000000000..91ca1b6d66\n--- /dev/null\n+++ b/t/t0211-trace2-perf.sh\n@@ -0,0 +1,153 @@\n+#!/bin/sh\n+\n+test_description='test trace2 facility (perf target)'\n+. ./test-lib.sh\n+\n+# Add t/helper directory to PATH so that we can use a relative\n+# path to run nested instances of test-tool.exe (see 004child).\n+# This helps with HEREDOC comparisons later.\n+TTDIR=\"$GIT_BUILD_DIR/t/helper/\" && export TTDIR\n+PATH=\"$TTDIR:$PATH\" && export PATH\n+\n+# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe\n+# Warning: to do the actual diff/comparison, so the HEREDOCs here\n+# Warning: only cover our actual calls to test-tool and/or git.\n+# Warning: So you may see extra lines in artifact files when\n+# Warning: interactively debugging.\n+\n+# Turn off any inherited trace2 settings for this test.\n+unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT\n+unset GIT_TR2_PERF_BRIEF\n+unset GIT_TR2_CONFIG_PARAMS\n+\n+V=$(git version | sed -e 's/^git version //') && export V\n+\n+# There are multiple trace2 targets: normal, perf, and event.\n+# Trace2 events will/can be written to each active target (subject\n+# to whatever filtering that target decides to do).\n+# Test each target independently.\n+#\n+# Defer setting GIT_TR2_PERF until the actual command we want to\n+# test because hidden git and test-tool commands in the test\n+# harness can contaminate our output.\n+\n+# Enable \"brief\" feature which turns off the prefix:\n+#     \"<clock> <file>:<line> | <nr_parents> | \"\n+GIT_TR2_PERF_BRIEF=1 && export GIT_TR2_PERF_BRIEF\n+\n+# Repeat some of the t0210 tests using the perf target stream instead of\n+# the normal stream.\n+#\n+# Tokens here of the form _FIELD_ have been replaced in the observed output.\n+\n+# Verb 001return\n+#\n+# Implicit return from cmd_<verb> function propagates <code>.\n+\n+test_expect_success 'perf stream, return code 0' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 001return 0\n+\t\td0|main|cmd_verb|||||trace2 (trace2)\n+\t\td0|main|exit||_T_ABS_|||code:0\n+\t\td0|main|atexit||_T_ABS_|||code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'perf stream, return code 1' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\ttest_must_fail env GIT_TR2_PERF=\"$(pwd)/trace.perf\" test-tool trace2 001return 1 &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 001return 1\n+\t\td0|main|cmd_verb|||||trace2 (trace2)\n+\t\td0|main|exit||_T_ABS_|||code:1\n+\t\td0|main|atexit||_T_ABS_|||code:1\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 003error\n+#\n+# To the above, add multiple 'error <msg>' events\n+\n+test_expect_success 'perf stream, error event' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" test-tool trace2 003error \"hello world\" \"this is a test\" &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 003error '\\''hello world'\\'' '\\''this is a test'\\''\n+\t\td0|main|cmd_verb|||||trace2 (trace2)\n+\t\td0|main|error|||||hello world\n+\t\td0|main|error|||||this is a test\n+\t\td0|main|exit||_T_ABS_|||code:0\n+\t\td0|main|atexit||_T_ABS_|||code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 004child\n+#\n+# Test nested spawning of child processes.\n+#\n+# Conceptually, this looks like:\n+#    P1: TT trace2 004child\n+#    P2: |--- TT trace2 004child\n+#    P3:      |--- TT trace2 001return 0\n+#\n+# Which should generate events:\n+#    P1: version\n+#    P1: start\n+#    P1: cmd_verb\n+#    P1: child_start\n+#        P2: version\n+#        P2: start\n+#        P2: cmd_verb\n+#        P2: child_start\n+#            P3: version\n+#            P3: start\n+#            P3: cmd_verb\n+#            P3: exit\n+#            P3: atexit\n+#        P2: child_exit\n+#        P2: exit\n+#        P2: atexit\n+#    P1: child_exit\n+#    P1: exit\n+#    P1: atexit\n+\n+test_expect_success 'perf stream, child processes' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" test-tool trace2 004child test-tool trace2 004child test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 004child test-tool trace2 004child test-tool trace2 001return 0\n+\t\td0|main|cmd_verb|||||trace2 (trace2)\n+\t\td0|main|child_start||_T_ABS_|||[ch0] class:? argv: test-tool trace2 004child test-tool trace2 001return 0\n+\t\td1|main|version|||||$V\n+\t\td1|main|start|||||_EXE_ trace2 004child test-tool trace2 001return 0\n+\t\td1|main|cmd_verb|||||trace2 (trace2/trace2)\n+\t\td1|main|child_start||_T_ABS_|||[ch0] class:? argv: test-tool trace2 001return 0\n+\t\td2|main|version|||||$V\n+\t\td2|main|start|||||_EXE_ trace2 001return 0\n+\t\td2|main|cmd_verb|||||trace2 (trace2/trace2/trace2)\n+\t\td2|main|exit||_T_ABS_|||code:0\n+\t\td2|main|atexit||_T_ABS_|||code:0\n+\t\td1|main|child_exit||_T_ABS_|_T_REL_||[ch0] pid:_PID_ code:0\n+\t\td1|main|exit||_T_ABS_|||code:0\n+\t\td1|main|atexit||_T_ABS_|||code:0\n+\t\td0|main|child_exit||_T_ABS_|_T_REL_||[ch0] pid:_PID_ code:0\n+\t\td0|main|exit||_T_ABS_|||code:0\n+\t\td0|main|atexit||_T_ABS_|||code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t0211/scrub_perf.perl b/t/t0211/scrub_perf.perl\nnew file mode 100644\nindex 0000000000..351af7844e\n--- /dev/null\n+++ b/t/t0211/scrub_perf.perl\n@@ -0,0 +1,76 @@\n+#!/usr/bin/perl\n+#\n+# Scrub the variable fields from the perf trace2 output to\n+# make testing easier.\n+\n+use strict;\n+use warnings;\n+\n+my $qpath = '\\'[^\\']*\\'|[^ ]*';\n+\n+my $col_depth=0;\n+my $col_thread=1;\n+my $col_event=2;\n+my $col_repo=3;\n+my $col_t_abs=4;\n+my $col_t_rel=5;\n+my $col_category=6;\n+my $col_rest=7;\n+\n+# This code assumes that the trace2 data was written with bare\n+# turned on (which omits the \"<clock> <file>:<line> | <parents>\"\n+# prefix.\n+\n+while (<>) {\n+    my @tokens = split /\\|/;\n+\n+    foreach my $col (@tokens) { $col =~ s/^\\s+|\\s+$//g; }\n+\n+    if ($tokens[$col_event] =~ m/^start/) {\n+\t# The 'start' message lists the contents of argv in $col_rest.\n+\t# On some platforms (Windows), argv[0] is *sometimes* a canonical\n+\t# absolute path to the EXE rather than the value passed in the\n+\t# shell script.  Replace it with a placeholder to simplify our\n+\t# HEREDOC in the test script.\n+\tmy $argv0;\n+\tmy $argvRest;\n+\t$tokens[$col_rest] =~ s/^($qpath)\\W*(.*)/_EXE_ $2/;\n+    }\n+    elsif ($tokens[$col_event] =~ m/cmd_path/) {\n+\t# Likewise, the 'cmd_path' message breaks out argv[0].\n+\t#\n+\t# This line is only emitted when RUNTIME_PREFIX is defined,\n+\t# so just omit it for testing purposes.\n+\t# $tokens[$col_rest] = \"_EXE_\";\n+\tgoto SKIP_LINE;\n+    }\n+    elsif ($tokens[$col_event] =~ m/child_exit/) {\n+\t$tokens[$col_rest] =~ s/ pid:\\d* / pid:_PID_ /;\n+    }\n+    elsif ($tokens[$col_event] =~ m/data/) {\n+\tif ($tokens[$col_category] =~ m/process/) {\n+\t    # 'data' and 'data_json' events containing 'process'\n+\t    # category data are assumed to be platform-specific\n+\t    # and highly variable.  Just omit them.\n+\t    goto SKIP_LINE;\n+\t}\n+    }\n+\n+    # t_abs and t_rel are either blank or a float.  Replace the float\n+    # with a constant for matching the HEREDOC in the test script.\n+    if ($tokens[$col_t_abs] =~ m/\\d/) {\n+\t$tokens[$col_t_abs] = \"_T_ABS_\";\n+    }\n+    if ($tokens[$col_t_rel] =~ m/\\d/) {\n+\t$tokens[$col_t_rel] = \"_T_REL_\";\n+    }\n+\n+    my $out;\n+\n+    $out = join('|', @tokens);\n+    print \"$out\\n\";\n+\n+  SKIP_LINE:\n+}\n+\n+\ndiff --git a/t/t0212-trace2-event.sh b/t/t0212-trace2-event.sh\nnew file mode 100755\nindex 0000000000..517d5514df\n--- /dev/null\n+++ b/t/t0212-trace2-event.sh\n@@ -0,0 +1,234 @@\n+#!/bin/sh\n+\n+test_description='test trace2 facility'\n+. ./test-lib.sh\n+\n+# Add t/helper directory to PATH so that we can use a relative\n+# path to run nested instances of test-tool.exe (see 004child).\n+# This helps with HEREDOC comparisons later.\n+TTDIR=\"$GIT_BUILD_DIR/t/helper/\" && export TTDIR\n+PATH=\"$TTDIR:$PATH\" && export PATH\n+\n+# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe\n+# Warning: to do the actual diff/comparison, so the HEREDOCs here\n+# Warning: only cover our actual calls to test-tool and/or git.\n+# Warning: So you may see extra lines in artifact files when\n+# Warning: interactively debugging.\n+\n+# Turn off any inherited trace2 settings for this test.\n+unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT\n+unset GIT_TR2_BARE\n+unset GIT_TR2_CONFIG_PARAMS\n+\n+V=$(git version | sed -e 's/^git version //') && export V\n+\n+# There are multiple trace2 targets: normal, perf, and event.\n+# Trace2 events will/can be written to each active target (subject\n+# to whatever filtering that target decides to do).\n+# Test each target independently.\n+#\n+# Defer setting GIT_TR2_PERF until the actual command we want to\n+# test because hidden git and test-tool commands in the test\n+# harness can contaminate our output.\n+\n+# We don't bother repeating the 001return and 002exit tests, since they\n+# have coverage in the normal and perf targets.\n+\n+# Verb 003error\n+#\n+# To the above, add multiple 'error <msg>' events\n+\n+test_expect_success 'event stream, error event' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 003error \"hello world\" \"this is a test\" &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"003error\",\n+\t|      \"hello world\",\n+\t|      \"this is a test\"\n+\t|    ],\n+\t|    \"errors\":[\n+\t|      \"%s\",\n+\t|      \"%s\"\n+\t|    ],\n+\t|    \"exit_code\":0,\n+\t|    \"verb\":\"trace2\",\n+\t|    \"verb_hierarchy\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 004child\n+#\n+# Test nested spawning of child processes.\n+#\n+# Conceptually, this looks like:\n+#    P1: TT trace2 004child\n+#    P2: |--- TT trace2 004child\n+#    P3:      |--- TT trace2 001return 0\n+\n+test_expect_success 'event stream, return code 0' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 004child test-tool trace2 004child test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"004child\",\n+\t|      \"test-tool\",\n+\t|      \"trace2\",\n+\t|      \"004child\",\n+\t|      \"test-tool\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"child\":{\n+\t|      \"0\":{\n+\t|        \"child_argv\":[\n+\t|          \"_EXE_\",\n+\t|          \"trace2\",\n+\t|          \"004child\",\n+\t|          \"test-tool\",\n+\t|          \"trace2\",\n+\t|          \"001return\",\n+\t|          \"0\"\n+\t|        ],\n+\t|        \"child_class\":\"?\",\n+\t|        \"child_code\":0,\n+\t|        \"use_shell\":0\n+\t|      }\n+\t|    },\n+\t|    \"exit_code\":0,\n+\t|    \"verb\":\"trace2\",\n+\t|    \"verb_hierarchy\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  },\n+\t|  \"_SID0_/_SID1_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"004child\",\n+\t|      \"test-tool\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"child\":{\n+\t|      \"0\":{\n+\t|        \"child_argv\":[\n+\t|          \"_EXE_\",\n+\t|          \"trace2\",\n+\t|          \"001return\",\n+\t|          \"0\"\n+\t|        ],\n+\t|        \"child_class\":\"?\",\n+\t|        \"child_code\":0,\n+\t|        \"use_shell\":0\n+\t|      }\n+\t|    },\n+\t|    \"exit_code\":0,\n+\t|    \"verb\":\"trace2\",\n+\t|    \"verb_hierarchy\":\"trace2/trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  },\n+\t|  \"_SID0_/_SID1_/_SID2_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"exit_code\":0,\n+\t|    \"verb\":\"trace2\",\n+\t|    \"verb_hierarchy\":\"trace2/trace2/trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Test listing of all \"interesting\" config settings.\n+\n+test_expect_success 'event stream, list config' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tgit config --local t0212.abc 1 &&\n+\tgit config --local t0212.def \"hello world\" &&\n+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" GIT_TR2_CONFIG_PARAMS=\"t0212.*\" test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"exit_code\":0,\n+\t|    \"params\":[\n+\t|      {\n+\t|        \"param\":\"t0212.abc\",\n+\t|        \"value\":\"1\"\n+\t|      },\n+\t|      {\n+\t|        \"param\":\"t0212.def\",\n+\t|        \"value\":\"hello world\"\n+\t|      }\n+\t|    ],\n+\t|    \"verb\":\"trace2\",\n+\t|    \"verb_hierarchy\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'basic trace2_data' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 006data test_category k1 v1 test_category k2 v2 &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"006data\",\n+\t|      \"test_category\",\n+\t|      \"k1\",\n+\t|      \"v1\",\n+\t|      \"test_category\",\n+\t|      \"k2\",\n+\t|      \"v2\"\n+\t|    ],\n+\t|    \"data\":{\n+\t|      \"test_category\":{\n+\t|        \"k1\":\"v1\",\n+\t|        \"k2\":\"v2\"\n+\t|      }\n+\t|    },\n+\t|    \"exit_code\":0,\n+\t|    \"verb\":\"trace2\",\n+\t|    \"verb_hierarchy\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t0212/parse_events.perl b/t/t0212/parse_events.perl\nnew file mode 100644\nindex 0000000000..0e38fa6db6\n--- /dev/null\n+++ b/t/t0212/parse_events.perl\n@@ -0,0 +1,251 @@\n+#!/usr/bin/perl\n+#\n+# Parse event stream and convert individual events into a summary\n+# record for the process.\n+#\n+# Git.exe generates one or more \"event\" records for each API method,\n+# such as \"start <argv>\" and \"exit <code>\", during the life of the git\n+# process.  Additionally, the input may contain interleaved events\n+# from multiple concurrent git processes and/or multiple threads from\n+# within a git process.\n+#\n+# Accumulate events for each process (based on its unique SID) in a\n+# dictionary and emit process summary records.\n+#\n+# Convert some of the variable fields (such as elapsed time) into\n+# placeholders (or omit them) to make HEREDOC comparisons easier in\n+# the test scripts.\n+#\n+# We may also omit fields not (currently) useful for testing purposes.\n+\n+use strict;\n+use warnings;\n+use JSON::PP;\n+use Data::Dumper;\n+use Getopt::Long;\n+\n+# The version of the trace2 event target format that we understand.\n+# This is reported in the 'version' event in the 'evt' field.\n+# It comes from the GIT_TR2_EVENT_VERSION macro in trace2/tr2_tgt_event.c\n+my $evt_version = '1';\n+\n+my $show_children = 1;\n+my $show_exec     = 1;\n+my $show_threads  = 1;\n+\n+# A hack to generate test HEREDOC data for pasting into the test script.\n+# Usage:\n+#    cd \"t/trash directory.t0212-trace2-event\"\n+#    $TT trace ... >trace.event\n+#    VV=$(../../git.exe version | sed -e 's/^git version //')\n+#    perl ../t0212/parse_events.perl --HEREDOC --VERSION=$VV <trace.event >heredoc\n+# Then paste heredoc into your new test.\n+\n+my $gen_heredoc = 0;\n+my $gen_version = '';\n+\n+GetOptions(\"children!\" => \\$show_children,\n+\t   \"exec!\"     => \\$show_exec,\n+\t   \"threads!\"  => \\$show_threads,\n+\t   \"HEREDOC!\"  => \\$gen_heredoc,\n+\t   \"VERSION=s\" => \\$gen_version    )\n+    or die(\"Error in command line arguments\\n\");\n+\n+\n+# SIDs contains timestamps and PIDs of the process and its parents.\n+# This makes it difficult to match up in a HEREDOC in the test script.\n+# Build a map from actual SIDs to predictable constant values and yet\n+# keep the parent/child relationships.  For example:\n+# {..., \"sid\":\"1539706952458276-8652\", ...}\n+# {..., \"sid\":\"1539706952458276-8652/1539706952649493-15452\", ...}\n+# becomes:\n+# {..., \"sid\":\"_SID1_\", ...}\n+# {..., \"sid\":\"_SID1_/_SID2_\", ...}\n+my $sid_map;\n+my $sid_count = 0;\n+\n+my $processes;\n+\n+while (<>) {\n+    my $line = decode_json( $_ );\n+\n+    my $sid = \"\";\n+    my $sid_sep = \"\";\n+\n+    my $raw_sid = $line->{'sid'};\n+    my @raw_sid_parts = split /\\//, $raw_sid;\n+    foreach my $raw_sid_k (@raw_sid_parts) {\n+\tif (!exists $sid_map->{$raw_sid_k}) {\n+\t    $sid_map->{$raw_sid_k} = '_SID' . $sid_count . '_';\n+\t    $sid_count++;\n+\t}\n+\t$sid = $sid . $sid_sep . $sid_map->{$raw_sid_k};\n+\t$sid_sep = '/';\n+    }\n+    \n+    my $event = $line->{'event'};\n+\n+    if ($event eq 'version') {\n+\t$processes->{$sid}->{'version'} = $line->{'exe'};\n+\tif ($gen_heredoc == 1 && $gen_version eq $line->{'exe'}) {\n+\t    # If we are generating data FOR the test script, replace\n+\t    # the reported git.exe version with a reference to an\n+\t    # environment variable.  When our output is pasted into\n+\t    # the test script, it will then be expanded in future\n+\t    # test runs to the THEN current version of git.exe.\n+\t    # We assume that the test script uses env var $V.\n+\t    $processes->{$sid}->{'version'} = \"\\$V\";\n+\t}\n+    }\n+\n+    elsif ($event eq 'start') {\n+\t$processes->{$sid}->{'argv'} = $line->{'argv'};\n+\t$processes->{$sid}->{'argv'}[0] = \"_EXE_\";\n+    }\n+\n+    elsif ($event eq 'exit') {\n+\t$processes->{$sid}->{'exit_code'} = $line->{'code'};\n+    }\n+\n+    elsif ($event eq 'atexit') {\n+\t$processes->{$sid}->{'exit_code'} = $line->{'code'};\n+    }\n+\n+    elsif ($event eq 'error') {\n+\t# For HEREDOC purposes, use the error message format string if\n+\t# available, rather than the formatted message (which probably\n+\t# has an absolute pathname).\n+\tif (exists $line->{'fmt'}) {\n+\t    push( @{$processes->{$sid}->{'errors'}}, $line->{'fmt'} );\n+\t}\n+\telsif (exists $line->{'msg'}) {\n+\t    push( @{$processes->{$sid}->{'errors'}}, $line->{'msg'} );\n+\t}\n+    }\n+\n+    elsif ($event eq 'cmd_path') {\n+\t## $processes->{$sid}->{'path'} = $line->{'path'};\n+\t#\n+\t# Like in the 'start' event, we need to replace the value of\n+\t# argv[0] with a token for HEREDOC purposes.  However, the\n+\t# event is only emitted when RUNTIME_PREFIX is defined, so\n+\t# just omit it for testing purposes.\n+\t# $processes->{$sid}->{'path'} = \"_EXE_\";\n+    }\n+    \n+    elsif ($event eq 'cmd_verb') {\n+\t$processes->{$sid}->{'verb'} = $line->{'name'};\n+\t$processes->{$sid}->{'verb_hierarchy'} = $line->{'hierarchy'};\n+    }\n+\n+    elsif ($event eq 'alias') {\n+\t$processes->{$sid}->{'alias'}->{'key'} = $line->{'alias'};\n+\t$processes->{$sid}->{'alias'}->{'argv'} = $line->{'argv'};\n+    }\n+\n+    elsif ($event eq 'def_param') {\n+\tmy $kv;\n+\t$kv->{'param'} = $line->{'param'};\n+\t$kv->{'value'} = $line->{'value'};\n+\tpush( @{$processes->{$sid}->{'params'}}, $kv );\n+    }\n+\n+    elsif ($event eq 'child_start') {\n+\tif ($show_children == 1) {\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_class'} = $line->{'child_class'};\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_argv'} = $line->{'argv'};\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_argv'}[0] = \"_EXE_\";\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'use_shell'} = $line->{'use_shell'} ? 1 : 0;\n+\t}\n+    }\n+\n+    elsif ($event eq 'child_exit') {\n+\tif ($show_children == 1) {\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_code'} = $line->{'code'};\n+\t}\n+    }\n+\n+    # TODO decide what information we want to test from thread events.\n+\n+    elsif ($event eq 'thread_start') {\n+\tif ($show_threads == 1) {\n+\t}\n+    }\n+\n+    elsif ($event eq 'thread_exit') {\n+\tif ($show_threads == 1) {\n+\t}\n+    }\n+\n+    # TODO decide what information we want to test from exec events.\n+\n+    elsif ($event eq 'exec') {\n+\tif ($show_exec == 1) {\n+\t}\n+    }\n+\n+    elsif ($event eq 'exec_result') {\n+\tif ($show_exec == 1) {\n+\t}\n+    }\n+\n+    elsif ($event eq 'def_param') {\n+\t# Accumulate parameter key/value pairs by key rather than in an array\n+\t# so that we get overwrite (last one wins) effects.\n+\t$processes->{$sid}->{'params'}->{$line->{'param'}} = $line->{'value'};\n+    }\n+\n+    elsif ($event eq 'def_repo') {\n+\t# $processes->{$sid}->{'repos'}->{$line->{'repo'}} = $line->{'worktree'};\n+\t$processes->{$sid}->{'repos'}->{$line->{'repo'}} = \"_WORKTREE_\";\n+    }\n+\n+    # A series of potentially nested and threaded region and data events\n+    # is fundamentally incompatibile with the type of summary record we\n+    # are building in this script.  Since they are intended for\n+    # perf-trace-like analysis rather than a result summary, we ignore\n+    # most of them here.\n+\n+    # elsif ($event eq 'region_enter') {\n+    # }\n+    # elsif ($event eq 'region_leave') {\n+    # }\n+\n+    elsif ($event eq 'data') {\n+\tmy $cat = $line->{'category'};\n+\tif ($cat eq 'test_category') {\n+\t    \n+\t    my $key = $line->{'key'};\n+\t    my $value = $line->{'value'};\n+\t    $processes->{$sid}->{'data'}->{$cat}->{$key} = $value;\n+\t}\n+    }\n+\n+    # This trace2 target does not emit 'printf' events.\n+    #\n+    # elsif ($event eq 'printf') {\n+    # }\n+}\n+\n+# Dump the resulting hash into something that we can compare against\n+# in the test script.  These options make Dumper output look a little\n+# bit like JSON.  Also convert variable references of the form \"$VAR*\"\n+# so that the matching HEREDOC doesn't need to escape it.\n+\n+$Data::Dumper::Sortkeys = 1;\n+$Data::Dumper::Indent = 1;\n+$Data::Dumper::Purity = 1;\n+$Data::Dumper::Pair = ':';\n+\n+my $out = Dumper($processes);\n+$out =~ s/'/\"/g;\n+$out =~ s/\\$VAR/VAR/g;\n+\n+# Finally, if we're running this script to generate (manually confirmed)\n+# data to add to the test script, guard the indentation.\n+\n+if ($gen_heredoc == 1) {\n+    $out =~ s/^/\\t\\|/gms;\n+}\n+\n+print $out;\n-- \ngitgitgadget\n"},{"id":"368184","messageId":"ed1d6120067d78ed35cde8c6d3f609e9e5eed3a4.1548881779.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 12/14] trace2:data: add subverb to reset command","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T20:56:32Z","receivedAt":"2019-01-30T20:56:52Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/reset.c | 6 ++++++\n 1 file changed, 6 insertions(+)\n\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex 59898c972e..b65b4a66db 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -340,6 +340,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \tif (patch_mode) {\n \t\tif (reset_type != NONE)\n \t\t\tdie(_(\"--patch is incompatible with --{hard,mixed,soft}\"));\n+\t\ttrace2_cmd_subverb(\"patch-interactive\");\n \t\treturn run_add_interactive(rev, \"--patch=reset\", &pathspec);\n \t}\n \n@@ -356,6 +357,11 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \tif (reset_type == NONE)\n \t\treset_type = MIXED; /* by default */\n \n+\tif (pathspec.nr)\n+\t\ttrace2_cmd_subverb(\"path\");\n+\telse\n+\t\ttrace2_cmd_subverb(reset_type_names[reset_type]);\n+\n \tif (reset_type != SOFT && (reset_type != MIXED || get_git_work_tree()))\n \t\tsetup_work_tree();\n \n-- \ngitgitgadget\n\n"},{"id":"368185","messageId":"b4c8ff53dd8f9f04bb65ebc942b6319834897971.1548881779.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 11/14] trace2:data: add subverb to checkout command","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T20:56:31Z","receivedAt":"2019-01-30T20:56:56Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/checkout.c | 7 +++++++\n 1 file changed, 7 insertions(+)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 6fadf412e8..8939ae99ed 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -262,6 +262,8 @@ static int checkout_paths(const struct checkout_opts *opts,\n \tstruct lock_file lock_file = LOCK_INIT;\n \tint nr_checkouts = 0;\n \n+\ttrace2_cmd_subverb(opts->patch_mode ? \"patch\" : \"path\");\n+\n \tif (opts->track != BRANCH_TRACK_UNSPECIFIED)\n \t\tdie(_(\"'%s' cannot be used with updating paths\"), \"--track\");\n \n@@ -952,6 +954,9 @@ static int switch_branches(const struct checkout_opts *opts,\n \tvoid *path_to_free;\n \tstruct object_id rev;\n \tint flag, writeout_error = 0;\n+\n+\ttrace2_cmd_subverb(\"branch\");\n+\n \tmemset(&old_branch_info, 0, sizeof(old_branch_info));\n \told_branch_info.path = path_to_free = resolve_refdup(\"HEAD\", 0, &rev, &flag);\n \tif (old_branch_info.path)\n@@ -1189,6 +1194,8 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)\n \tint status;\n \tstruct strbuf branch_ref = STRBUF_INIT;\n \n+\ttrace2_cmd_subverb(\"unborn\");\n+\n \tif (!opts->new_branch)\n \t\tdie(_(\"You are on a branch yet to be born\"));\n \tstrbuf_addf(&branch_ref, \"refs/heads/%s\", opts->new_branch);\n-- \ngitgitgadget\n\n"},{"id":"368186","messageId":"8c9687830e2b270f7723a47cb2c79ab816d9e565.1548881779.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 02/14] trace2: create new combined trace facility","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T20:56:23Z","receivedAt":"2019-01-30T20:57:02Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate a new unified tracing facility for git.  The eventual intent is to\nreplace the current trace_printf* and trace_performance* routines with a\nunified set of git_trace2* routines.\n\nIn addition to the usual printf-style API, trace2 provides higer-level\nevent verbs with fixed-fields allowing structured data to be written.\nThis makes post-processing and analysis easier for external tools.\n\nTrace2 defines 3 output targets.  These are set using the environment\nvariables \"GIT_TR2\", \"GIT_TR2_PERF\", and \"GIT_TR2_EVENT\".  These may be\nset to \"1\" or to an absolute pathname (just like the current GIT_TRACE).\n\n* GIT_TR2 is intended to be a replacement for GIT_TRACE and logs command\n  summary data.\n\n* GIT_TR2_PERF is intended as a replacement for GIT_TRACE_PERFORMANCE.\n  It extends the output with columns for the command process, thread,\n  repo, absolute and relative elapsed times.  It reports events for\n  child process start/stop, thread start/stop, and per-thread function\n  nesting.\n\n* GIT_TR2_EVENT is a new structured format. It writes event data as a\n  series of JSON records.\n\nCalls to trace2 functions log to any of the 3 output targets enabled\nwithout the need to call different trace_printf* or trace_performance*\nroutines.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                      |  14 +-\n builtin/submodule--helper.c   |   9 +-\n cache.h                       |   1 +\n common-main.c                 |  12 +-\n compat/mingw.c                |  11 +-\n compat/mingw.h                |   3 +-\n config.c                      |   2 +\n exec-cmd.c                    |   2 +\n git-compat-util.h             |   7 +\n git.c                         |  65 +++\n remote-curl.c                 |   7 +\n repository.c                  |   2 +\n repository.h                  |   3 +\n run-command.c                 |  59 ++-\n run-command.h                 |  13 +-\n sh-i18n--envsubst.c           |   3 +\n submodule.c                   |  11 +-\n t/helper/test-parse-options.c |   3 +\n t/helper/test-tool.c          |   3 +\n t/t0001-init.sh               |   1 +\n trace2.c                      | 807 ++++++++++++++++++++++++++++++++++\n trace2.h                      | 366 +++++++++++++++\n trace2/tr2_cfg.c              |  90 ++++\n trace2/tr2_cfg.h              |  19 +\n trace2/tr2_dst.c              | 197 +++++++++\n trace2/tr2_dst.h              |  36 ++\n trace2/tr2_sid.c              |  67 +++\n trace2/tr2_sid.h              |  18 +\n trace2/tr2_tbuf.c             |  32 ++\n trace2/tr2_tbuf.h             |  23 +\n trace2/tr2_tgt.h              | 131 ++++++\n trace2/tr2_tgt_event.c        | 590 +++++++++++++++++++++++++\n trace2/tr2_tgt_normal.c       | 325 ++++++++++++++\n trace2/tr2_tgt_perf.c         | 536 ++++++++++++++++++++++\n trace2/tr2_tls.c              | 164 +++++++\n trace2/tr2_tls.h              |  97 ++++\n trace2/tr2_verb.c             |  30 ++\n trace2/tr2_verb.h             |  24 +\n usage.c                       |  31 ++\n 39 files changed, 3796 insertions(+), 18 deletions(-)\n create mode 100644 trace2.c\n create mode 100644 trace2.h\n create mode 100644 trace2/tr2_cfg.c\n create mode 100644 trace2/tr2_cfg.h\n create mode 100644 trace2/tr2_dst.c\n create mode 100644 trace2/tr2_dst.h\n create mode 100644 trace2/tr2_sid.c\n create mode 100644 trace2/tr2_sid.h\n create mode 100644 trace2/tr2_tbuf.c\n create mode 100644 trace2/tr2_tbuf.h\n create mode 100644 trace2/tr2_tgt.h\n create mode 100644 trace2/tr2_tgt_event.c\n create mode 100644 trace2/tr2_tgt_normal.c\n create mode 100644 trace2/tr2_tgt_perf.c\n create mode 100644 trace2/tr2_tls.c\n create mode 100644 trace2/tr2_tls.h\n create mode 100644 trace2/tr2_verb.c\n create mode 100644 trace2/tr2_verb.h\n\ndiff --git a/Makefile b/Makefile\nindex 6e8d017e8e..6b49729c3c 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1005,6 +1005,16 @@ LIB_OBJS += tempfile.o\n LIB_OBJS += thread-utils.o\n LIB_OBJS += tmp-objdir.o\n LIB_OBJS += trace.o\n+LIB_OBJS += trace2.o\n+LIB_OBJS += trace2/tr2_cfg.o\n+LIB_OBJS += trace2/tr2_dst.o\n+LIB_OBJS += trace2/tr2_sid.o\n+LIB_OBJS += trace2/tr2_tbuf.o\n+LIB_OBJS += trace2/tr2_tgt_event.o\n+LIB_OBJS += trace2/tr2_tgt_normal.o\n+LIB_OBJS += trace2/tr2_tgt_perf.o\n+LIB_OBJS += trace2/tr2_tls.o\n+LIB_OBJS += trace2/tr2_verb.o\n LIB_OBJS += trailer.o\n LIB_OBJS += transport.o\n LIB_OBJS += transport-helper.o\n@@ -1581,7 +1591,9 @@ ifdef NO_INET_PTON\n \tLIB_OBJS += compat/inet_pton.o\n \tBASIC_CFLAGS += -DNO_INET_PTON\n endif\n-ifndef NO_UNIX_SOCKETS\n+ifdef NO_UNIX_SOCKETS\n+\tBASIC_CFLAGS += -DNO_UNIX_SOCKETS\n+else\n \tLIB_OBJS += unix-socket.o\n \tPROGRAM_OBJS += credential-cache.o\n \tPROGRAM_OBJS += credential-cache--daemon.o\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 0e140f176c..f10dc5134a 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -1815,11 +1815,10 @@ static int update_submodules(struct submodule_update_clone *suc)\n {\n \tint i;\n \n-\trun_processes_parallel(suc->max_jobs,\n-\t\t\t       update_clone_get_next_task,\n-\t\t\t       update_clone_start_failure,\n-\t\t\t       update_clone_task_finished,\n-\t\t\t       suc);\n+\trun_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task,\n+\t\t\t\t   update_clone_start_failure,\n+\t\t\t\t   update_clone_task_finished, suc, \"submodule\",\n+\t\t\t\t   \"parallel/update\");\n \n \t/*\n \t * We saved the output and put it out all at once now.\ndiff --git a/cache.h b/cache.h\nindex 009e8b3b15..3d12ffe01f 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -9,6 +9,7 @@\n #include \"gettext.h\"\n #include \"convert.h\"\n #include \"trace.h\"\n+#include \"trace2.h\"\n #include \"string-list.h\"\n #include \"pack-revindex.h\"\n #include \"hash.h\"\ndiff --git a/common-main.c b/common-main.c\nindex 3728f66b4c..6dbdc4adf2 100644\n--- a/common-main.c\n+++ b/common-main.c\n@@ -25,12 +25,18 @@ static void restore_sigpipe_to_default(void)\n \n int main(int argc, const char **argv)\n {\n+\tint result;\n+\n \t/*\n \t * Always open file descriptors 0/1/2 to avoid clobbering files\n \t * in die().  It also avoids messing up when the pipes are dup'ed\n \t * onto stdin/stdout/stderr in the child processes we spawn.\n \t */\n \tsanitize_stdfds();\n+\trestore_sigpipe_to_default();\n+\n+\ttrace2_initialize();\n+\ttrace2_cmd_start(argv);\n \n \tgit_resolve_executable_dir(argv[0]);\n \n@@ -40,7 +46,9 @@ int main(int argc, const char **argv)\n \n \tattr_start();\n \n-\trestore_sigpipe_to_default();\n+\tresult = cmd_main(argc, argv);\n+\n+\ttrace2_cmd_exit(result);\n \n-\treturn cmd_main(argc, argv);\n+\treturn result;\n }\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex b459e1a291..111fe68baf 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -1476,19 +1476,23 @@ static int try_shell_exec(const char *cmd, char *const *argv)\n \t\treturn 0;\n \tprog = path_lookup(interpr, 1);\n \tif (prog) {\n+\t\tint exec_id;\n \t\tint argc = 0;\n \t\tconst char **argv2;\n \t\twhile (argv[argc]) argc++;\n \t\tALLOC_ARRAY(argv2, argc + 1);\n \t\targv2[0] = (char *)cmd;\t/* full path to the script file */\n \t\tmemcpy(&argv2[1], &argv[1], sizeof(*argv) * argc);\n+\t\texec_id = trace2_exec(prog, argv2);\n \t\tpid = mingw_spawnv(prog, argv2, 1);\n \t\tif (pid >= 0) {\n \t\t\tint status;\n \t\t\tif (waitpid(pid, &status, 0) < 0)\n \t\t\t\tstatus = 255;\n+\t\t\ttrace2_exec_result(exec_id, status);\n \t\t\texit(status);\n \t\t}\n+\t\ttrace2_exec_result(exec_id, -1);\n \t\tpid = 1;\t/* indicate that we tried but failed */\n \t\tfree(prog);\n \t\tfree(argv2);\n@@ -1501,12 +1505,17 @@ int mingw_execv(const char *cmd, char *const *argv)\n \t/* check if git_command is a shell script */\n \tif (!try_shell_exec(cmd, argv)) {\n \t\tint pid, status;\n+\t\tint exec_id;\n \n+\t\texec_id = trace2_exec(cmd, (const char **)argv);\n \t\tpid = mingw_spawnv(cmd, (const char **)argv, 0);\n-\t\tif (pid < 0)\n+\t\tif (pid < 0) {\n+\t\t\ttrace2_exec_result(exec_id, -1);\n \t\t\treturn -1;\n+\t\t}\n \t\tif (waitpid(pid, &status, 0) < 0)\n \t\t\tstatus = 255;\n+\t\ttrace2_exec_result(exec_id, status);\n \t\texit(status);\n \t}\n \treturn -1;\ndiff --git a/compat/mingw.h b/compat/mingw.h\nindex 30d9fb3e36..4d73f8aa9d 100644\n--- a/compat/mingw.h\n+++ b/compat/mingw.h\n@@ -147,8 +147,7 @@ static inline int fcntl(int fd, int cmd, ...)\n \terrno = EINVAL;\n \treturn -1;\n }\n-/* bash cannot reliably detect negative return codes as failure */\n-#define exit(code) exit((code) & 0xff)\n+\n #define sigemptyset(x) (void)0\n static inline int sigaddset(sigset_t *set, int signum)\n { return 0; }\ndiff --git a/config.c b/config.c\nindex ff521eb27a..4beeb7795a 100644\n--- a/config.c\n+++ b/config.c\n@@ -2656,6 +2656,8 @@ int git_config_set_gently(const char *key, const char *value)\n void git_config_set(const char *key, const char *value)\n {\n \tgit_config_set_multivar(key, value, NULL, 0);\n+\n+\ttrace2_cmd_set_config(key, value);\n }\n \n /*\ndiff --git a/exec-cmd.c b/exec-cmd.c\nindex 4f81f44310..7deeab3039 100644\n--- a/exec-cmd.c\n+++ b/exec-cmd.c\n@@ -209,6 +209,8 @@ static int git_get_exec_path(struct strbuf *buf, const char *argv0)\n \t\treturn -1;\n \t}\n \n+\ttrace2_cmd_path(buf->buf);\n+\n \treturn 0;\n }\n \ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 29a19902aa..1d0aedc11f 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -1251,6 +1251,13 @@ static inline int is_missing_file_error(int errno_)\n \n extern int cmd_main(int, const char **);\n \n+/*\n+ * Intercept all calls to exit() and route them to trace2 to\n+ * optionally emit a message before calling the real exit().\n+ */\n+int trace2_cmd_exit_fl(const char *file, int line, int code);\n+#define exit(code) exit(trace2_cmd_exit_fl(__FILE__, __LINE__, (code)))\n+\n /*\n  * You can mark a stack variable with UNLEAK(var) to avoid it being\n  * reported as a leak by tools like LSAN or valgrind. The argument\ndiff --git a/git.c b/git.c\nindex 0ce0e13f0f..179e52c42d 100644\n--- a/git.c\n+++ b/git.c\n@@ -147,16 +147,20 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\t\tgit_set_exec_path(cmd + 1);\n \t\t\telse {\n \t\t\t\tputs(git_exec_path());\n+\t\t\t\ttrace2_cmd_verb(\"_query_\");\n \t\t\t\texit(0);\n \t\t\t}\n \t\t} else if (!strcmp(cmd, \"--html-path\")) {\n \t\t\tputs(system_path(GIT_HTML_PATH));\n+\t\t\ttrace2_cmd_verb(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"--man-path\")) {\n \t\t\tputs(system_path(GIT_MAN_PATH));\n+\t\t\ttrace2_cmd_verb(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"--info-path\")) {\n \t\t\tputs(system_path(GIT_INFO_PATH));\n+\t\t\ttrace2_cmd_verb(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"-p\") || !strcmp(cmd, \"--paginate\")) {\n \t\t\tuse_pager = 1;\n@@ -285,6 +289,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\t(*argv)++;\n \t\t\t(*argc)--;\n \t\t} else if (skip_prefix(cmd, \"--list-cmds=\", &cmd)) {\n+\t\t\ttrace2_cmd_verb(\"_query_\");\n \t\t\tif (!strcmp(cmd, \"parseopt\")) {\n \t\t\t\tstruct string_list list = STRING_LIST_INIT_DUP;\n \t\t\t\tint i;\n@@ -332,9 +337,14 @@ static int handle_alias(int *argcp, const char ***argv)\n \t\t\tcommit_pager_choice();\n \n \t\t\tchild.use_shell = 1;\n+\t\t\tchild.trace2_child_class = \"shell_alias\";\n \t\t\targv_array_push(&child.args, alias_string + 1);\n \t\t\targv_array_pushv(&child.args, (*argv) + 1);\n \n+\t\t\ttrace2_cmd_alias(alias_command, child.args.argv);\n+\t\t\ttrace2_cmd_list_config();\n+\t\t\ttrace2_cmd_verb(\"_run_shell_alias_\");\n+\n \t\t\tret = run_command(&child);\n \t\t\tif (ret >= 0)   /* normal exit */\n \t\t\t\texit(ret);\n@@ -369,6 +379,9 @@ static int handle_alias(int *argcp, const char ***argv)\n \t\t/* insert after command name */\n \t\tmemcpy(new_argv + count, *argv + 1, sizeof(char *) * *argcp);\n \n+\t\ttrace2_cmd_alias(alias_command, new_argv);\n+\t\ttrace2_cmd_list_config();\n+\n \t\t*argv = new_argv;\n \t\t*argcp += count - 1;\n \n@@ -417,6 +430,8 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)\n \t\tsetup_work_tree();\n \n \ttrace_argv_printf(argv, \"trace: built-in: git\");\n+\ttrace2_cmd_verb(p->cmd);\n+\ttrace2_cmd_list_config();\n \n \tvalidate_cache_entries(&the_index);\n \tstatus = p->fn(argc, argv, prefix);\n@@ -666,7 +681,14 @@ static void execv_dashed_external(const char **argv)\n \tcmd.clean_on_exit = 1;\n \tcmd.wait_after_clean = 1;\n \tcmd.silent_exec_failure = 1;\n+\tcmd.trace2_child_class = \"dashed\";\n \n+\ttrace2_cmd_verb(\"_run_dashed_\");\n+\n+\t/*\n+\t * The code in run_command() logs trace2 child_start/child_exit\n+\t * events, so we do not need to report exec/exec_result events here.\n+\t */\n \ttrace_argv_printf(cmd.args.argv, \"trace: exec:\");\n \n \t/*\n@@ -676,6 +698,12 @@ static void execv_dashed_external(const char **argv)\n \t * the program.\n \t */\n \tstatus = run_command(&cmd);\n+\n+\t/*\n+\t * If the child process ran and we are now going to exit, emit a\n+\t * generic string as our trace2 command verb to indicate that we\n+\t * launched a dashed command.\n+\t */\n \tif (status >= 0)\n \t\texit(status);\n \telse if (errno != ENOENT)\n@@ -701,6 +729,43 @@ static int run_argv(int *argcp, const char ***argv)\n \t\tif (!done_alias)\n \t\t\thandle_builtin(*argcp, *argv);\n \n+#if 0 // TODO In GFW, need to amend a7924b655e940b06cb547c235d6bed9767929673 to include trace2_ and _tr2 lines.\n+\t\telse if (get_builtin(**argv)) {\n+\t\t\tstruct argv_array args = ARGV_ARRAY_INIT;\n+\t\t\tint i;\n+\n+\t\t\t/*\n+\t\t\t * The current process is committed to launching a\n+\t\t\t * child process to run the command named in (**argv)\n+\t\t\t * and exiting.  Log a generic string as the trace2\n+\t\t\t * command verb to indicate this.  Note that the child\n+\t\t\t * process will log the actual verb when it runs.\n+\t\t\t */\n+\t\t\ttrace2_cmd_verb(\"_run_git_alias_\");\n+\n+\t\t\tif (get_super_prefix())\n+\t\t\t\tdie(\"%s doesn't support --super-prefix\", **argv);\n+\n+\t\t\tcommit_pager_choice();\n+\n+\t\t\targv_array_push(&args, \"git\");\n+\t\t\tfor (i = 0; i < *argcp; i++)\n+\t\t\t\targv_array_push(&args, (*argv)[i]);\n+\n+\t\t\ttrace_argv_printf(args.argv, \"trace: exec:\");\n+\n+\t\t\t/*\n+\t\t\t * if we fail because the command is not found, it is\n+\t\t\t * OK to return. Otherwise, we just pass along the status code.\n+\t\t\t */\n+\t\t\ti = run_command_v_opt_tr2(args.argv, RUN_SILENT_EXEC_FAILURE |\n+\t\t\t\t\t\t  RUN_CLEAN_ON_EXIT, \"git_alias\");\n+\t\t\tif (i >= 0 || errno != ENOENT)\n+\t\t\t\texit(i);\n+\t\t\tdie(\"could not execute builtin %s\", **argv);\n+\t\t}\n+#endif // a7924b655e940b06cb547c235d6bed9767929673\n+\n \t\t/* .. then try the external ones */\n \t\texecv_dashed_external(*argv);\n \ndiff --git a/remote-curl.c b/remote-curl.c\nindex 6ff9c66b90..a1bcd5e60c 100644\n--- a/remote-curl.c\n+++ b/remote-curl.c\n@@ -1360,6 +1360,13 @@ int cmd_main(int argc, const char **argv)\n \tstring_list_init(&options.deepen_not, 1);\n \tstring_list_init(&options.push_options, 1);\n \n+\t/*\n+\t * Just report \"remote-curl\" here (folding all the various aliases\n+\t * (\"git-remote-http\", \"git-remote-https\", and etc.) here since they\n+\t * are all just copies of the same actual executable.\n+\t */\n+\ttrace2_cmd_verb(\"remote-curl\");\n+\n \tremote = remote_get(argv[1]);\n \n \tif (argc > 2) {\ndiff --git a/repository.c b/repository.c\nindex 20c509a922..b8da4d0bbf 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -119,6 +119,8 @@ static int repo_init_gitdir(struct repository *repo, const char *gitdir)\n void repo_set_worktree(struct repository *repo, const char *path)\n {\n \trepo->worktree = real_pathdup(path, 1);\n+\n+\ttrace2_def_repo(repo);\n }\n \n static int read_and_verify_repository_format(struct repository_format *format,\ndiff --git a/repository.h b/repository.h\nindex 0e482b7d49..4e2ad6e0be 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -90,6 +90,9 @@ struct repository {\n \t/* Repository's current hash algorithm, as serialized on disk. */\n \tconst struct git_hash_algo *hash_algo;\n \n+\t/* A unique-id for tracing purposes. */\n+\tint trace2_repo_id;\n+\n \t/* Configurations */\n \n \t/* Indicate if a repository has a different 'commondir' from 'gitdir' */\ndiff --git a/run-command.c b/run-command.c\nindex 3db26b7b0e..3449db319b 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -219,9 +219,29 @@ static int exists_in_PATH(const char *file)\n \n int sane_execvp(const char *file, char * const argv[])\n {\n+#ifndef GIT_WINDOWS_NATIVE\n+\t/*\n+\t * execvp() doesn't return, so we all we can do is tell trace2\n+\t * what we are about to do and let it leave a hint in the log\n+\t * (unless of course the execvp() fails).\n+\t *\n+\t * we skip this for Windows because the compat layer already\n+\t * has to emulate the execvp() call anyway.\n+\t */\n+\tint exec_id = trace2_exec(file, (const char **)argv);\n+#endif\n+\n \tif (!execvp(file, argv))\n \t\treturn 0; /* cannot happen ;-) */\n \n+#ifndef GIT_WINDOWS_NATIVE\n+\t{\n+\t\tint ec = errno;\n+\t\ttrace2_exec_result(exec_id, ec);\n+\t\terrno = ec;\n+\t}\n+#endif\n+\n \t/*\n \t * When a command can't be found because one of the directories\n \t * listed in $PATH is unsearchable, execvp reports EACCES, but\n@@ -712,6 +732,7 @@ int start_command(struct child_process *cmd)\n \t\tcmd->err = fderr[0];\n \t}\n \n+\ttrace2_child_start(cmd);\n \ttrace_run_command(cmd);\n \n \tfflush(NULL);\n@@ -926,6 +947,8 @@ int start_command(struct child_process *cmd)\n #endif\n \n \tif (cmd->pid < 0) {\n+\t\ttrace2_child_exit(cmd, -1);\n+\n \t\tif (need_in)\n \t\t\tclose_pair(fdin);\n \t\telse if (cmd->in)\n@@ -964,13 +987,16 @@ int start_command(struct child_process *cmd)\n int finish_command(struct child_process *cmd)\n {\n \tint ret = wait_or_whine(cmd->pid, cmd->argv[0], 0);\n+\ttrace2_child_exit(cmd, ret);\n \tchild_process_clear(cmd);\n \treturn ret;\n }\n \n int finish_command_in_signal(struct child_process *cmd)\n {\n-\treturn wait_or_whine(cmd->pid, cmd->argv[0], 1);\n+\tint ret = wait_or_whine(cmd->pid, cmd->argv[0], 1);\n+\ttrace2_child_exit(cmd, ret);\n+\treturn ret;\n }\n \n \n@@ -992,7 +1018,18 @@ int run_command_v_opt(const char **argv, int opt)\n \treturn run_command_v_opt_cd_env(argv, opt, NULL, NULL);\n }\n \n+int run_command_v_opt_tr2(const char **argv, int opt, const char *tr2_class)\n+{\n+\treturn run_command_v_opt_cd_env_tr2(argv, opt, NULL, NULL, tr2_class);\n+}\n+\n int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env)\n+{\n+\treturn run_command_v_opt_cd_env_tr2(argv, opt, dir, env, NULL);\n+}\n+\n+int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,\n+\t\t\t\t const char *const *env, const char *tr2_class)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \tcmd.argv = argv;\n@@ -1004,6 +1041,7 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const\n \tcmd.clean_on_exit = opt & RUN_CLEAN_ON_EXIT ? 1 : 0;\n \tcmd.dir = dir;\n \tcmd.env = env;\n+\tcmd.trace2_child_class = tr2_class;\n \treturn run_command(&cmd);\n }\n \n@@ -1319,6 +1357,7 @@ int run_hook_ve(const char *const *env, const char *name, va_list args)\n \thook.env = env;\n \thook.no_stdin = 1;\n \thook.stdout_to_stderr = 1;\n+\thook.trace2_hook_name = name;\n \n \treturn run_command(&hook);\n }\n@@ -1807,3 +1846,21 @@ int run_processes_parallel(int n,\n \tpp_cleanup(&pp);\n \treturn 0;\n }\n+\n+int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task,\n+\t\t\t       start_failure_fn start_failure,\n+\t\t\t       task_finished_fn task_finished, void *pp_cb,\n+\t\t\t       const char *tr2_category, const char *tr2_label)\n+{\n+\tint result;\n+\n+\ttrace2_region_enter_printf(tr2_category, tr2_label, NULL, \"max:%d\",\n+\t\t\t\t   ((n < 1) ? online_cpus() : n));\n+\n+\tresult = run_processes_parallel(n, get_next_task, start_failure,\n+\t\t\t\t\ttask_finished, pp_cb);\n+\n+\ttrace2_region_leave(tr2_category, tr2_label, NULL);\n+\n+\treturn result;\n+}\ndiff --git a/run-command.h b/run-command.h\nindex 68f5369fc2..a6950691c0 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -10,6 +10,12 @@ struct child_process {\n \tstruct argv_array args;\n \tstruct argv_array env_array;\n \tpid_t pid;\n+\n+\tint trace2_child_id;\n+\tuint64_t trace2_child_us_start;\n+\tconst char *trace2_child_class;\n+\tconst char *trace2_hook_name;\n+\n \t/*\n \t * Using .in, .out, .err:\n \t * - Specify 0 for no redirections (child inherits stdin, stdout,\n@@ -73,12 +79,14 @@ extern int run_hook_ve(const char *const *env, const char *name, va_list args);\n #define RUN_USING_SHELL 16\n #define RUN_CLEAN_ON_EXIT 32\n int run_command_v_opt(const char **argv, int opt);\n-\n+int run_command_v_opt_tr2(const char **argv, int opt, const char *tr2_class);\n /*\n  * env (the environment) is to be formatted like environ: \"VAR=VALUE\".\n  * To unset an environment variable use just \"VAR\".\n  */\n int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env);\n+int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,\n+\t\t\t\t const char *const *env, const char *tr2_class);\n \n /**\n  * Execute the given command, sending \"in\" to its stdin, and capturing its\n@@ -220,5 +228,8 @@ int run_processes_parallel(int n,\n \t\t\t   start_failure_fn,\n \t\t\t   task_finished_fn,\n \t\t\t   void *pp_cb);\n+int run_processes_parallel_tr2(int n, get_next_task_fn, start_failure_fn,\n+\t\t\t       task_finished_fn, void *pp_cb,\n+\t\t\t       const char *tr2_category, const char *tr2_label);\n \n #endif\ndiff --git a/sh-i18n--envsubst.c b/sh-i18n--envsubst.c\nindex 09c6b445b9..2296ba1a2d 100644\n--- a/sh-i18n--envsubst.c\n+++ b/sh-i18n--envsubst.c\n@@ -14,6 +14,7 @@\n  */\n \n #include \"git-compat-util.h\"\n+#include \"trace2.h\"\n \n /* Substitution of environment variables in shell format strings.\n    Copyright (C) 2003-2007 Free Software Foundation, Inc.\n@@ -67,6 +68,8 @@ cmd_main (int argc, const char *argv[])\n   /* Default values for command line options.  */\n   /* unsigned short int show_variables = 0; */\n \n+  trace2_cmd_verb(\"sh-i18n--envsubst\");\n+\n   switch (argc)\n \t{\n \tcase 1:\ndiff --git a/submodule.c b/submodule.c\nindex 7b5cea8522..a01c9dccf6 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1568,11 +1568,12 @@ int fetch_populated_submodules(struct repository *r,\n \n \tcalculate_changed_submodule_paths(r, &spf.changed_submodule_names);\n \tstring_list_sort(&spf.changed_submodule_names);\n-\trun_processes_parallel(max_parallel_jobs,\n-\t\t\t       get_next_submodule,\n-\t\t\t       fetch_start_failure,\n-\t\t\t       fetch_finish,\n-\t\t\t       &spf);\n+\trun_processes_parallel_tr2(max_parallel_jobs,\n+\t\t\t\t   get_next_submodule,\n+\t\t\t\t   fetch_start_failure,\n+\t\t\t\t   fetch_finish,\n+\t\t\t\t   &spf,\n+\t\t\t\t   \"submodule\", \"parallel/fetch\");\n \n \targv_array_clear(&spf.args);\n out:\ndiff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c\nindex 47fee660b8..5318681739 100644\n--- a/t/helper/test-parse-options.c\n+++ b/t/helper/test-parse-options.c\n@@ -2,6 +2,7 @@\n #include \"cache.h\"\n #include \"parse-options.h\"\n #include \"string-list.h\"\n+#include \"trace2.h\"\n \n static int boolean = 0;\n static int integer = 0;\n@@ -153,6 +154,8 @@ int cmd__parse_options(int argc, const char **argv)\n \tint i;\n \tint ret = 0;\n \n+\ttrace2_cmd_verb(\"_parse_\");\n+\n \targc = parse_options(argc, (const char **)argv, prefix, options, usage, 0);\n \n \tif (length_cb.called) {\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 5b137874e1..0a704fe8d2 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -1,5 +1,6 @@\n #include \"git-compat-util.h\"\n #include \"test-tool.h\"\n+#include \"trace2.h\"\n \n struct test_cmd {\n \tconst char *name;\n@@ -80,6 +81,8 @@ int cmd_main(int argc, const char **argv)\n \t\tif (!strcmp(cmds[i].name, argv[1])) {\n \t\t\targv++;\n \t\t\targc--;\n+\t\t\ttrace2_cmd_verb(cmds[i].name);\n+\t\t\ttrace2_cmd_list_config();\n \t\t\treturn cmds[i].fn(argc, argv);\n \t\t}\n \t}\ndiff --git a/t/t0001-init.sh b/t/t0001-init.sh\nindex 42a263cada..5e27604b24 100755\n--- a/t/t0001-init.sh\n+++ b/t/t0001-init.sh\n@@ -93,6 +93,7 @@ test_expect_success 'No extra GIT_* on alias scripts' '\n \t\tsed -n \\\n \t\t\t-e \"/^GIT_PREFIX=/d\" \\\n \t\t\t-e \"/^GIT_TEXTDOMAINDIR=/d\" \\\n+\t\t\t-e \"/^GIT_TR2_PARENT/d\" \\\n \t\t\t-e \"/^GIT_/s/=.*//p\" |\n \t\tsort\n \tEOF\ndiff --git a/trace2.c b/trace2.c\nnew file mode 100644\nindex 0000000000..bff1da327e\n--- /dev/null\n+++ b/trace2.c\n@@ -0,0 +1,807 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"json-writer.h\"\n+#include \"quote.h\"\n+#include \"run-command.h\"\n+#include \"sigchain.h\"\n+#include \"thread-utils.h\"\n+#include \"version.h\"\n+#include \"trace2/tr2_cfg.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_sid.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+#include \"trace2/tr2_verb.h\"\n+\n+static int trace2_enabled;\n+\n+static int tr2_next_child_id; /* modify under lock */\n+static int tr2_next_exec_id; /* modify under lock */\n+static int tr2_next_repo_id = 1; /* modify under lock. zero is reserved */\n+\n+/*\n+ * A table of the builtin TRACE2 targets.  Each of these may be independently\n+ * enabled or disabled.  Each TRACE2 API method will try to write an event to\n+ * *each* of the enabled targets.\n+ */\n+static struct tr2_tgt *tr2_tgt_builtins[] =\n+{\n+\t&tr2_tgt_normal,\n+\t&tr2_tgt_perf,\n+\t&tr2_tgt_event,\n+\tNULL\n+};\n+\n+#define for_each_builtin(j, tgt_j)\t\t\t\\\n+\tfor (j = 0, tgt_j = tr2_tgt_builtins[j];\t\\\n+\t     tgt_j;\t\t\t\t\t\\\n+\t     j++, tgt_j = tr2_tgt_builtins[j])\n+\n+#define for_each_wanted_builtin(j, tgt_j) \\\n+\tfor_each_builtin(j, tgt_j) if (tr2_dst_trace_want(tgt_j->pdst))\n+\n+/*\n+ * Force (rather than lazily) initialize any of the requested\n+ * builtin TRACE2 targets at startup (and before we've seen an\n+ * actual TRACE2 event call) so we can see if we need to setup\n+ * the TR2 and TLS machinery.\n+ *\n+ * Return the number of builtin targets enabled.\n+ */\n+static int tr2_tgt_want_builtins(void)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tint sum = 0;\n+\n+\tfor_each_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_init())\n+\t\t\tsum++;\n+\t}\n+\n+\treturn sum;\n+}\n+\n+/*\n+ * Properly terminate each builtin target.  Give each target\n+ * a chance to write a summary event and/or flush if necessary\n+ * and then close the fd.\n+ */\n+static void tr2_tgt_disable_builtins(void)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tfor_each_builtin(j, tgt_j)\n+\t{\n+\t\ttgt_j->pfn_term();\n+\t}\n+}\n+\n+static int tr2main_exit_code;\n+\n+/*\n+ * Our atexit routine should run after everything has finished.\n+ *\n+ * Note that events generated here might not actually appear if\n+ * we are writing to fd 1 or 2 and our atexit routine runs after\n+ * the pager's atexit routine (since it closes them to shutdown\n+ * the pipes).\n+ */\n+static void tr2main_atexit_handler(void)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Clear any unbalanced regions so that our atexit message\n+\t * does not appear nested.  This improves the appearance of\n+\t * the trace output if someone calls die(), for example.\n+\t */\n+\ttr2tls_pop_unwind_self();\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_atexit)\n+\t\t\ttgt_j->pfn_atexit(us_elapsed_absolute,\n+\t\t\t\t\t  tr2main_exit_code);\n+\t}\n+\n+\ttr2_tgt_disable_builtins();\n+\n+\ttr2tls_release();\n+\ttr2_sid_release();\n+\ttr2_verb_release();\n+\ttr2_cfg_free_patterns();\n+\n+\ttrace2_enabled = 0;\n+}\n+\n+static void tr2main_signal_handler(int signo)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_signal)\n+\t\t\ttgt_j->pfn_signal(us_elapsed_absolute, signo);\n+\t}\n+\n+\tsigchain_pop(signo);\n+\traise(signo);\n+}\n+\n+void trace2_initialize_fl(const char *file, int line)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (trace2_enabled)\n+\t\treturn;\n+\n+\tif (!tr2_tgt_want_builtins())\n+\t\treturn;\n+\ttrace2_enabled = 1;\n+\n+\ttr2_sid_get();\n+\n+\tatexit(tr2main_atexit_handler);\n+\tsigchain_push(SIGPIPE, tr2main_signal_handler);\n+\ttr2tls_init();\n+\n+\t/*\n+\t * Emit 'version' message on each active builtin target.\n+\t */\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_version_fl)\n+\t\t\ttgt_j->pfn_version_fl(file, line);\n+\t}\n+}\n+\n+int trace2_is_enabled(void)\n+{\n+\treturn trace2_enabled;\n+}\n+\n+void trace2_cmd_start_fl(const char *file, int line, const char **argv)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_start_fl)\n+\t\t\ttgt_j->pfn_start_fl(file, line, argv);\n+\t}\n+}\n+\n+int trace2_cmd_exit_fl(const char *file, int line, int code)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tcode &= 0xff;\n+\n+\tif (!trace2_enabled)\n+\t\treturn code;\n+\n+\ttr2main_exit_code = code;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_exit_fl)\n+\t\t\ttgt_j->pfn_exit_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t   code);\n+\t}\n+\n+\treturn code;\n+}\n+\n+void trace2_cmd_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t    va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\t/*\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy (because an 'ap' can only be walked once).\n+\t */\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_error_va_fl)\n+\t\t\ttgt_j->pfn_error_va_fl(file, line, fmt, ap);\n+\t}\n+}\n+\n+void trace2_cmd_path_fl(const char *file, int line, const char *pathname)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_command_path_fl)\n+\t\t\ttgt_j->pfn_command_path_fl(file, line, pathname);\n+\t}\n+}\n+\n+void trace2_cmd_verb_fl(const char *file, int line, const char *command_verb)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tconst char *hierarchy;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\ttr2_verb_append_hierarchy(command_verb);\n+\thierarchy = tr2_verb_get_hierarchy();\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_command_verb_fl)\n+\t\t\ttgt_j->pfn_command_verb_fl(file, line, command_verb,\n+\t\t\t\t\t\t   hierarchy);\n+\t}\n+}\n+\n+void trace2_cmd_subverb_fl(const char *file, int line,\n+\t\t\t   const char *command_subverb)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_command_subverb_fl)\n+\t\t\ttgt_j->pfn_command_subverb_fl(file, line,\n+\t\t\t\t\t\t      command_subverb);\n+\t}\n+}\n+\n+void trace2_cmd_alias_fl(const char *file, int line, const char *alias,\n+\t\t\t const char **argv)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_alias_fl)\n+\t\t\ttgt_j->pfn_alias_fl(file, line, alias, argv);\n+\t}\n+}\n+\n+void trace2_cmd_list_config_fl(const char *file, int line)\n+{\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\ttr2_cfg_list_config_fl(file, line);\n+}\n+\n+void trace2_cmd_set_config_fl(const char *file, int line, const char *key,\n+\t\t\t      const char *value)\n+{\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\ttr2_cfg_set_fl(file, line, key, value);\n+}\n+\n+void trace2_child_start_fl(const char *file, int line,\n+\t\t\t   struct child_process *cmd)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tcmd->trace2_child_id = tr2tls_locked_increment(&tr2_next_child_id);\n+\tcmd->trace2_child_us_start = us_now;\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_child_start_fl)\n+\t\t\ttgt_j->pfn_child_start_fl(file, line,\n+\t\t\t\t\t\t  us_elapsed_absolute, cmd);\n+\t}\n+}\n+\n+void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd,\n+\t\t\t  int child_exit_code)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_child;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tif (cmd->trace2_child_us_start)\n+\t\tus_elapsed_child = us_now - cmd->trace2_child_us_start;\n+\telse\n+\t\tus_elapsed_child = 0;\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_child_exit_fl)\n+\t\t\ttgt_j->pfn_child_exit_fl(file, line,\n+\t\t\t\t\t\t us_elapsed_absolute,\n+\t\t\t\t\t\t cmd->trace2_child_id, cmd->pid,\n+\t\t\t\t\t\t child_exit_code,\n+\t\t\t\t\t\t us_elapsed_child);\n+\t}\n+}\n+\n+int trace2_exec_fl(const char *file, int line, const char *exe,\n+\t\t   const char **argv)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tint exec_id;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn -1;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\texec_id = tr2tls_locked_increment(&tr2_next_exec_id);\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_exec_fl)\n+\t\t\ttgt_j->pfn_exec_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t   exec_id, exe, argv);\n+\t}\n+\n+\treturn exec_id;\n+}\n+\n+void trace2_exec_result_fl(const char *file, int line, int exec_id, int code)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_exec_result_fl)\n+\t\t\ttgt_j->pfn_exec_result_fl(\n+\t\t\t\tfile, line, us_elapsed_absolute, exec_id, code);\n+\t}\n+}\n+\n+void trace2_thread_start_fl(const char *file, int line, const char *thread_name)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tif (tr2tls_is_main_thread()) {\n+\t\t/*\n+\t\t * We should only be called from the new thread's thread-proc,\n+\t\t * so this is technically a bug.  But in those cases where the\n+\t\t * main thread also runs the thread-proc function (or when we\n+\t\t * are built with threading disabled), we need to allow it.\n+\t\t *\n+\t\t * Convert this call to a region-enter so the nesting looks\n+\t\t * correct.\n+\t\t */\n+\t\ttrace2_region_enter_printf_fl(file, line, NULL, NULL, NULL,\n+\t\t\t\t\t      \"thread-proc on main: %s\",\n+\t\t\t\t\t      thread_name);\n+\t\treturn;\n+\t}\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\ttr2tls_create_self(thread_name);\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_thread_start_fl)\n+\t\t\ttgt_j->pfn_thread_start_fl(file, line,\n+\t\t\t\t\t\t   us_elapsed_absolute);\n+\t}\n+}\n+\n+void trace2_thread_exit_fl(const char *file, int line)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_thread;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tif (tr2tls_is_main_thread()) {\n+\t\t/*\n+\t\t * We should only be called from the exiting thread's\n+\t\t * thread-proc, so this is technically a bug.  But in\n+\t\t * those cases where the main thread also runs the\n+\t\t * thread-proc function (or when we are built with\n+\t\t * threading disabled), we need to allow it.\n+\t\t *\n+\t\t * Convert this call to a region-leave so the nesting\n+\t\t * looks correct.\n+\t\t */\n+\t\ttrace2_region_leave_printf_fl(file, line, NULL, NULL, NULL,\n+\t\t\t\t\t      \"thread-proc on main\");\n+\t\treturn;\n+\t}\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Clear any unbalanced regions and then get the relative time\n+\t * for the outer-most region (which we pushed when the thread\n+\t * started).  This gives us the run time of the thread.\n+\t */\n+\ttr2tls_pop_unwind_self();\n+\tus_elapsed_thread = tr2tls_region_elasped_self(us_now);\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_thread_exit_fl)\n+\t\t\ttgt_j->pfn_thread_exit_fl(file, line,\n+\t\t\t\t\t\t  us_elapsed_absolute,\n+\t\t\t\t\t\t  us_elapsed_thread);\n+\t}\n+\n+\ttr2tls_unset_self();\n+}\n+\n+void trace2_def_param_fl(const char *file, int line, const char *param,\n+\t\t\t const char *value)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_param_fl)\n+\t\t\ttgt_j->pfn_param_fl(file, line, param, value);\n+\t}\n+}\n+\n+void trace2_def_repo_fl(const char *file, int line, struct repository *repo)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tif (repo->trace2_repo_id)\n+\t\treturn;\n+\n+\trepo->trace2_repo_id = tr2tls_locked_increment(&tr2_next_repo_id);\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_repo_fl)\n+\t\t\ttgt_j->pfn_repo_fl(file, line, repo);\n+\t}\n+}\n+\n+void trace2_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category, const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Print the region-enter message at the current nesting\n+\t * (indentation) level and then push a new level.\n+\t *\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy.\n+\t */\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_region_enter_printf_va_fl)\n+\t\t\ttgt_j->pfn_region_enter_printf_va_fl(\n+\t\t\t\tfile, line, us_elapsed_absolute, category,\n+\t\t\t\tlabel, repo, fmt, ap);\n+\t}\n+\n+\ttr2tls_push_self(us_now);\n+}\n+\n+void trace2_region_enter_fl(const char *file, int line, const char *category,\n+\t\t\t    const char *label, const struct repository *repo)\n+{\n+\ttrace2_region_enter_printf_va_fl(file, line, category, label, repo,\n+\t\t\t\t\t NULL, NULL);\n+}\n+\n+void trace2_region_enter_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category, const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_enter_printf_va_fl(file, line, category, label, repo, fmt,\n+\t\t\t\t\t ap);\n+\tva_end(ap);\n+}\n+\n+#ifndef HAVE_VARIADIC_MACROS\n+void trace2_region_enter_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo, const char *fmt,\n+\t\t\t\t...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_enter_printf_va_fl(NULL, 0, category, label, repo, fmt,\n+\t\t\t\t\t ap);\n+\tva_end(ap);\n+}\n+#endif\n+\n+void trace2_region_leave_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category, const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_region;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Get the elapsed time in the current region before we\n+\t * pop it off the stack.  Pop the stack.  And then print\n+\t * the perf message at the new (shallower) level so that\n+\t * it lines up with the corresponding push/enter.\n+\t */\n+\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n+\n+\ttr2tls_pop_self();\n+\n+\t/*\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy.\n+\t */\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_region_leave_printf_va_fl)\n+\t\t\ttgt_j->pfn_region_leave_printf_va_fl(\n+\t\t\t\tfile, line, us_elapsed_absolute,\n+\t\t\t\tus_elapsed_region, category, label, repo, fmt,\n+\t\t\t\tap);\n+\t}\n+}\n+\n+void trace2_region_leave_fl(const char *file, int line, const char *category,\n+\t\t\t    const char *label, const struct repository *repo)\n+{\n+\ttrace2_region_leave_printf_va_fl(file, line, category, label, repo,\n+\t\t\t\t\t NULL, NULL);\n+}\n+\n+void trace2_region_leave_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category, const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_leave_printf_va_fl(file, line, category, label, repo, fmt,\n+\t\t\t\t\t ap);\n+\tva_end(ap);\n+}\n+\n+#ifndef HAVE_VARIADIC_MACROS\n+void trace2_region_leave_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo, const char *fmt,\n+\t\t\t\t...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_leave_printf_va_fl(NULL, 0, category, label, repo, fmt,\n+\t\t\t\t\t ap);\n+\tva_end(ap);\n+}\n+#endif\n+\n+void trace2_data_string_fl(const char *file, int line, const char *category,\n+\t\t\t   const struct repository *repo, const char *key,\n+\t\t\t   const char *value)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_region;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_data_fl)\n+\t\t\ttgt_j->pfn_data_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t   us_elapsed_region, category, repo,\n+\t\t\t\t\t   key, value);\n+\t}\n+}\n+\n+void trace2_data_intmax_fl(const char *file, int line, const char *category,\n+\t\t\t   const struct repository *repo, const char *key,\n+\t\t\t   intmax_t value)\n+{\n+\tstruct strbuf buf_string = STRBUF_INIT;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tstrbuf_addf(&buf_string, \"%\" PRIdMAX, value);\n+\ttrace2_data_string_fl(file, line, category, repo, key, buf_string.buf);\n+\tstrbuf_release(&buf_string);\n+}\n+\n+void trace2_data_json_fl(const char *file, int line, const char *category,\n+\t\t\t const struct repository *repo, const char *key,\n+\t\t\t const struct json_writer *value)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_region;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n+\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_data_fl)\n+\t\t\ttgt_j->pfn_data_json_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t\tus_elapsed_region, category,\n+\t\t\t\t\t\trepo, key, value);\n+\t}\n+}\n+\n+void trace2_printf_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy.\n+\t */\n+\tfor_each_wanted_builtin(j, tgt_j)\n+\t{\n+\t\tif (tgt_j->pfn_printf_va_fl)\n+\t\t\ttgt_j->pfn_printf_va_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t\tfmt, ap);\n+\t}\n+}\n+\n+void trace2_printf_fl(const char *file, int line, const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_printf_va_fl(file, line, fmt, ap);\n+\tva_end(ap);\n+}\n+\n+#ifndef HAVE_VARIADIC_MACROS\n+void trace2_printf(const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_printf_va_fl(NULL, 0, fmt, ap);\n+\tva_end(ap);\n+}\n+#endif\ndiff --git a/trace2.h b/trace2.h\nnew file mode 100644\nindex 0000000000..a0e99d9c26\n--- /dev/null\n+++ b/trace2.h\n@@ -0,0 +1,366 @@\n+#ifndef TRACE2_H\n+#define TRACE2_H\n+\n+struct child_process;\n+struct repository;\n+struct json_writer;\n+\n+/*\n+ * The public TRACE2 routines are grouped into the following groups:\n+ *\n+ * [] trace2_initialize -- initialization.\n+ * [] trace2_cmd_*      -- emit command/control messages.\n+ * [] trace2_child*     -- emit child start/stop messages.\n+ * [] trace2_exec*      -- emit exec start/stop messages.\n+ * [] trace2_thread*    -- emit thread start/stop messages.\n+ * [] trace2_def*       -- emit definition/parameter mesasges.\n+ * [] trace2_region*    -- emit region nesting messages.\n+ * [] trace2_data*      -- emit region/thread/repo data messages.\n+ * [] trace2_printf*    -- legacy trace[1] messages.\n+ */\n+\n+/*\n+ * Initialize TRACE2 tracing facility if any of the builtin TRACE2\n+ * targets are enabled in the environment.  Emits a 'version' event.\n+ *\n+ * Cleanup/Termination is handled automatically by a registered\n+ * atexit() routine.\n+ */\n+void trace2_initialize_fl(const char *file, int line);\n+\n+#define trace2_initialize() trace2_initialize_fl(__FILE__, __LINE__)\n+\n+/*\n+ * Return true if trace2 is enabled.\n+ */\n+int trace2_is_enabled(void);\n+\n+/*\n+ * Emit a 'start' event with the original (unmodified) argv.\n+ */\n+void trace2_cmd_start_fl(const char *file, int line, const char **argv);\n+\n+#define trace2_cmd_start(argv) trace2_cmd_start_fl(__FILE__, __LINE__, (argv))\n+\n+/*\n+ * Emit an 'exit' event.\n+ *\n+ * Write the exit-code that will be passed to exit() or returned\n+ * from main().\n+ *\n+ * Use this prior to actually calling exit().\n+ * See \"#define exit()\" in git-compat-util.h\n+ */\n+int trace2_cmd_exit_fl(const char *file, int line, int code);\n+\n+#define trace2_cmd_exit(code) (trace2_cmd_exit_fl(__FILE__, __LINE__, (code)))\n+\n+/*\n+ * Emit an 'error' event.\n+ *\n+ * Write an error message to the TRACE2 targets.\n+ */\n+void trace2_cmd_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t    va_list ap);\n+\n+#define trace2_cmd_error_va(fmt, ap) \\\n+\ttrace2_cmd_error_va_fl(__FILE__, __LINE__, (fmt), (ap))\n+\n+/*\n+ * Emit a 'pathname' event with the canonical pathname of the current process\n+ * This gives post-processors a simple field to identify the command without\n+ * having to parse the argv.  For example, to distinguish invocations from\n+ * installed versus debug executables.\n+ */\n+void trace2_cmd_path_fl(const char *file, int line, const char *pathname);\n+\n+#define trace2_cmd_path(p) trace2_cmd_path_fl(__FILE__, __LINE__, (p))\n+\n+/*\n+ * Emit a 'cmd_verb' event with the canonical name of the (usually)\n+ * builtin command.  This gives post-processors a simple field\n+ * to identify the command verb without having to parse the argv.\n+ */\n+void trace2_cmd_verb_fl(const char *file, int line, const char *command_verb);\n+\n+#define trace2_cmd_verb(v) trace2_cmd_verb_fl(__FILE__, __LINE__, (v))\n+\n+/*\n+ * Emit a 'cmd_subverb' event to further describe the command being run.\n+ * For example, \"checkout\" can checkout a single file or can checkout a\n+ * different branch.  This gives post-processors a simple field to compare\n+ * equivalent commands without having to parse the argv.\n+ */\n+void trace2_cmd_subverb_fl(const char *file, int line,\n+\t\t\t   const char *command_subverb);\n+\n+#define trace2_cmd_subverb(sv) trace2_cmd_subverb_fl(__FILE__, __LINE__, (sv))\n+\n+/*\n+ * Emit an 'alias' expansion event.\n+ */\n+void trace2_cmd_alias_fl(const char *file, int line, const char *alias,\n+\t\t\t const char **argv);\n+\n+#define trace2_cmd_alias(alias, argv) \\\n+\ttrace2_cmd_alias_fl(__FILE__, __LINE__, (alias), (argv))\n+\n+/*\n+ * Emit one or more 'def_param' events for \"interesting\" configuration\n+ * settings.\n+ *\n+ * The environment variable \"GIT_TR2_CONFIG_PARAMS\" can be set to a\n+ * list of patterns considered important.  For example:\n+ *\n+ *    GIT_TR2_CONFIG_PARAMS=\"core.*,remote.*.url\"\n+ *\n+ * Note: this routine does a read-only iteration on the config data\n+ * (using read_early_config()), so it must not be called until enough\n+ * of the process environment has been established.  This includes the\n+ * location of the git and worktree directories, expansion of any \"-c\"\n+ * and \"-C\" command line options, and etc.\n+ */\n+void trace2_cmd_list_config_fl(const char *file, int line);\n+\n+#define trace2_cmd_list_config() trace2_cmd_list_config_fl(__FILE__, __LINE__)\n+\n+/*\n+ * Emit a \"def_param\" event for the given config key/value pair IF\n+ * we consider the key to be \"interesting\".\n+ *\n+ * Use this for new/updated config settings created/updated after\n+ * trace2_cmd_list_config() is called.\n+ */\n+void trace2_cmd_set_config_fl(const char *file, int line, const char *key,\n+\t\t\t      const char *value);\n+\n+#define trace2_cmd_set_config(k, v) \\\n+\ttrace2_cmd_set_config_fl(__FILE__, __LINE__, (k), (v))\n+\n+/*\n+ * Emit a 'child_start' event prior to spawning a child process.\n+ *\n+ * Before calling optionally set \"cmd->trace2_child_class\" to a string\n+ * describing the type of the child process.  For example, \"editor\" or\n+ * \"pager\".\n+ */\n+void trace2_child_start_fl(const char *file, int line,\n+\t\t\t   struct child_process *cmd);\n+\n+#define trace2_child_start(cmd) trace2_child_start_fl(__FILE__, __LINE__, (cmd))\n+\n+/*\n+ * Emit a 'child_exit' event after the child process completes.\n+ */\n+void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd,\n+\t\t\t  int child_exit_code);\n+\n+#define trace2_child_exit(cmd, code) \\\n+\ttrace2_child_exit_fl(__FILE__, __LINE__, (cmd), (code))\n+\n+/*\n+ * Emit an 'exec' event prior to calling one of exec(), execv(),\n+ * execvp(), and etc.  On Unix-derived systems, this will be the\n+ * last event emitted for the current process, unless the exec\n+ * fails.  On Windows, exec() behaves like 'child_start' and a\n+ * waitpid(), so additional events may be emitted.\n+ *\n+ * Returns the \"exec_id\".\n+ */\n+int trace2_exec_fl(const char *file, int line, const char *exe,\n+\t\t   const char **argv);\n+\n+#define trace2_exec(exe, argv) trace2_exec_fl(__FILE__, __LINE__, (exe), (argv))\n+\n+/*\n+ * Emit an 'exec_result' when possible.  On Unix-derived systems,\n+ * this should be called after exec() returns (which only happens\n+ * when there is an error starting the new process).  On Windows,\n+ * this should be called after the waitpid().\n+ *\n+ * The \"exec_id\" should be the value returned from trace2_exec().\n+ */\n+void trace2_exec_result_fl(const char *file, int line, int exec_id, int code);\n+\n+#define trace2_exec_result(id, code) \\\n+\ttrace2_exec_result_fl(__FILE__, __LINE__, (id), (code))\n+\n+/*\n+ * Emit a 'thread_start' event.  This must be called from inside the\n+ * thread-proc to set up the trace2 TLS data for the thread.\n+ *\n+ * Thread names should be descriptive, like \"preload_index\".\n+ * Thread names will be decorated with an instance number automatically.\n+ */\n+void trace2_thread_start_fl(const char *file, int line,\n+\t\t\t    const char *thread_name);\n+\n+#define trace2_thread_start(thread_name) \\\n+\ttrace2_thread_start_fl(__FILE__, __LINE__, (thread_name))\n+\n+/*\n+ * Emit a 'thread_exit' event.  This must be called from inside the\n+ * thread-proc to report thread-specific data and cleanup TLS data\n+ * for the thread.\n+ */\n+void trace2_thread_exit_fl(const char *file, int line);\n+\n+#define trace2_thread_exit() trace2_thread_exit_fl(__FILE__, __LINE__)\n+\n+/*\n+ * Emit a 'param' event.\n+ *\n+ * Write a \"<param> = <value>\" pair describing some aspect of the\n+ * run such as an important configuration setting or command line\n+ * option that significantly changes command behavior.\n+ */\n+void trace2_def_param_fl(const char *file, int line, const char *param,\n+\t\t\t const char *value);\n+\n+#define trace2_def_param(param, value) \\\n+\ttrace2_def_param_fl(__FILE__, __LINE__, (param), (value))\n+\n+/*\n+ * Tell trace2 about a newly instantiated repo object and assign\n+ * a trace2-repo-id to be used in subsequent activity events.\n+ *\n+ * Emits a 'worktree' event for this repo instance.\n+ */\n+void trace2_def_repo_fl(const char *file, int line, struct repository *repo);\n+\n+#define trace2_def_repo(repo) trace2_def_repo_fl(__FILE__, __LINE__, repo)\n+\n+/*\n+ * Emit a 'region_enter' event for <category>.<label> with optional\n+ * repo-id and printf message.\n+ *\n+ * Enter a new nesting level on the current thread and remember the\n+ * current time.  This controls the indenting of all subsequent events\n+ * on this thread.\n+ */\n+void trace2_region_enter_fl(const char *file, int line, const char *category,\n+\t\t\t    const char *label, const struct repository *repo);\n+\n+#define trace2_region_enter(category, label, repo) \\\n+\ttrace2_region_enter_fl(__FILE__, __LINE__, (category), (label), (repo))\n+\n+void trace2_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category, const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap);\n+\n+#define trace2_region_enter_printf_va(category, label, repo, fmt, ap)    \\\n+\ttrace2_region_enter_printf_va_fl(__FILE__, __LINE__, (category), \\\n+\t\t\t\t\t (label), (repo), (fmt), (ap))\n+\n+void trace2_region_enter_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category, const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...);\n+\n+#ifdef HAVE_VARIADIC_MACROS\n+#define trace2_region_enter_printf(category, label, repo, ...)                 \\\n+\ttrace2_region_enter_printf_fl(__FILE__, __LINE__, (category), (label), \\\n+\t\t\t\t      (repo), __VA_ARGS__)\n+#else\n+__attribute__((format (region_enter_printf, 4, 5)))\n+void trace2_region_enter_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo, const char *fmt,\n+\t\t\t\t...);\n+#endif\n+\n+/*\n+ * Emit a 'region_leave' event for <category>.<label> with optional\n+ * repo-id and printf message.\n+ *\n+ * Leave current nesting level and report the elapsed time spent\n+ * in this nesting level.\n+ */\n+void trace2_region_leave_fl(const char *file, int line, const char *category,\n+\t\t\t    const char *label, const struct repository *repo);\n+\n+#define trace2_region_leave(category, label, repo) \\\n+\ttrace2_region_leave_fl(__FILE__, __LINE__, (category), (label), (repo))\n+\n+void trace2_region_leave_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category, const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap);\n+\n+#define trace2_region_leave_printf_va(category, label, repo, fmt, ap)    \\\n+\ttrace2_region_leave_printf_va_fl(__FILE__, __LINE__, (category), \\\n+\t\t\t\t\t (label), (repo), (fmt), (ap))\n+\n+void trace2_region_leave_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category, const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...);\n+\n+#ifdef HAVE_VARIADIC_MACROS\n+#define trace2_region_leave_printf(category, label, repo, ...)                 \\\n+\ttrace2_region_leave_printf_fl(__FILE__, __LINE__, (category), (label), \\\n+\t\t\t\t      (repo), __VA_ARGS__)\n+#else\n+__attribute__((format (region_leave_printf, 4, 5)))\n+void trace2_region_leave_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo, const char *fmt,\n+\t\t\t\t...);\n+#endif\n+\n+/*\n+ * Emit a key-value pair 'data' event of the form <category>.<key> = <value>.\n+ * This event implicitly contains information about thread, nesting region,\n+ * and optional repo-id.\n+ *\n+ * On event-based TRACE2 targets, this generates a 'data' event suitable\n+ * for post-processing.  On printf-based TRACE2 targets, this is converted\n+ * into a fixed-format printf message.\n+ */\n+void trace2_data_string_fl(const char *file, int line, const char *category,\n+\t\t\t   const struct repository *repo, const char *key,\n+\t\t\t   const char *value);\n+\n+#define trace2_data_string(category, repo, key, value)                       \\\n+\ttrace2_data_string_fl(__FILE__, __LINE__, (category), (repo), (key), \\\n+\t\t\t      (value))\n+\n+void trace2_data_intmax_fl(const char *file, int line, const char *category,\n+\t\t\t   const struct repository *repo, const char *key,\n+\t\t\t   intmax_t value);\n+\n+#define trace2_data_intmax(category, repo, key, value)                       \\\n+\ttrace2_data_intmax_fl(__FILE__, __LINE__, (category), (repo), (key), \\\n+\t\t\t      (value))\n+\n+void trace2_data_json_fl(const char *file, int line, const char *category,\n+\t\t\t const struct repository *repo, const char *key,\n+\t\t\t const struct json_writer *jw);\n+\n+#define trace2_data_json(category, repo, key, value)                       \\\n+\ttrace2_data_json_fl(__FILE__, __LINE__, (category), (repo), (key), \\\n+\t\t\t    (value))\n+\n+/*\n+ * Emit a 'printf' event.\n+ *\n+ * Write an arbitrary formatted message to the TRACE2 targets.  These\n+ * text messages should be considered as human-readable strings without\n+ * any formatting guidelines.  Post-processors may choose to ignore\n+ * them.\n+ */\n+void trace2_printf_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t va_list ap);\n+\n+#define trace2_printf_va(fmt, ap) \\\n+\ttrace2_printf_va_fl(__FILE__, __LINE__, (fmt), (ap))\n+\n+void trace2_printf_fl(const char *file, int line, const char *fmt, ...);\n+\n+#ifdef HAVE_VARIADIC_MACROS\n+#define trace2_printf(...) trace2_printf_fl(__FILE__, __LINE__, __VA_ARGS__)\n+#else\n+__attribute__((format (printf, 1, 2)))\n+void trace2_printf(const char *fmt, ...);\n+#endif\n+\n+#endif /* TRACE2_H */\ndiff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c\nnew file mode 100644\nindex 0000000000..b329921ac5\n--- /dev/null\n+++ b/trace2/tr2_cfg.c\n@@ -0,0 +1,90 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"tr2_cfg.h\"\n+\n+#define TR2_ENVVAR_CFG_PARAM \"GIT_TR2_CONFIG_PARAMS\"\n+\n+static struct strbuf **tr2_cfg_patterns;\n+static int tr2_cfg_count_patterns;\n+static int tr2_cfg_loaded;\n+\n+/*\n+ * Parse a string containing a comma-delimited list of config keys\n+ * or wildcard patterns into a list of strbufs.\n+ */\n+static int tr2_cfg_load_patterns(void)\n+{\n+\tstruct strbuf **s;\n+\tconst char *envvar;\n+\n+\tif (tr2_cfg_loaded)\n+\t\treturn tr2_cfg_count_patterns;\n+\ttr2_cfg_loaded = 1;\n+\n+\tenvvar = getenv(TR2_ENVVAR_CFG_PARAM);\n+\tif (!envvar || !*envvar)\n+\t\treturn tr2_cfg_count_patterns;\n+\n+\ttr2_cfg_patterns = strbuf_split_buf(envvar, strlen(envvar), ',', -1);\n+\tfor (s = tr2_cfg_patterns; *s; s++) {\n+\t\tstruct strbuf *buf = *s;\n+\n+\t\tif (buf->len && buf->buf[buf->len - 1] == ',')\n+\t\t\tstrbuf_setlen(buf, buf->len - 1);\n+\t\tstrbuf_trim_trailing_newline(*s);\n+\t\tstrbuf_trim(*s);\n+\t}\n+\n+\ttr2_cfg_count_patterns = s - tr2_cfg_patterns;\n+\treturn tr2_cfg_count_patterns;\n+}\n+\n+void tr2_cfg_free_patterns(void)\n+{\n+\tif (tr2_cfg_patterns)\n+\t\tstrbuf_list_free(tr2_cfg_patterns);\n+\ttr2_cfg_count_patterns = 0;\n+\ttr2_cfg_loaded = 0;\n+}\n+\n+struct tr2_cfg_data {\n+\tconst char *file;\n+\tint line;\n+};\n+\n+/*\n+ * See if the given config key matches any of our patterns of interest.\n+ */\n+static int tr2_cfg_cb(const char *key, const char *value, void *d)\n+{\n+\tstruct strbuf **s;\n+\tstruct tr2_cfg_data *data = (struct tr2_cfg_data *)d;\n+\n+\tfor (s = tr2_cfg_patterns; *s; s++) {\n+\t\tstruct strbuf *buf = *s;\n+\t\tint wm = wildmatch(buf->buf, key, WM_CASEFOLD);\n+\t\tif (wm == WM_MATCH) {\n+\t\t\ttrace2_def_param_fl(data->file, data->line, key, value);\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void tr2_cfg_list_config_fl(const char *file, int line)\n+{\n+\tstruct tr2_cfg_data data = { file, line };\n+\n+\tif (tr2_cfg_load_patterns() > 0)\n+\t\tread_early_config(tr2_cfg_cb, &data);\n+}\n+\n+void tr2_cfg_set_fl(const char *file, int line, const char *key,\n+\t\t    const char *value)\n+{\n+\tstruct tr2_cfg_data data = { file, line };\n+\n+\tif (tr2_cfg_load_patterns() > 0)\n+\t\ttr2_cfg_cb(key, value, &data);\n+}\ndiff --git a/trace2/tr2_cfg.h b/trace2/tr2_cfg.h\nnew file mode 100644\nindex 0000000000..d9c98f64dd\n--- /dev/null\n+++ b/trace2/tr2_cfg.h\n@@ -0,0 +1,19 @@\n+#ifndef TR2_CFG_H\n+#define TR2_CFG_H\n+\n+/*\n+ * Iterate over all config settings and emit 'def_param' events for the\n+ * \"interesting\" ones to TRACE2.\n+ */\n+void tr2_cfg_list_config_fl(const char *file, int line);\n+\n+/*\n+ * Emit a \"def_param\" event for the given key/value pair IF we consider\n+ * the key to be \"interesting\".\n+ */\n+void tr2_cfg_set_fl(const char *file, int line, const char *key,\n+\t\t    const char *value);\n+\n+void tr2_cfg_free_patterns(void);\n+\n+#endif /* TR2_CFG_H */\ndiff --git a/trace2/tr2_dst.c b/trace2/tr2_dst.c\nnew file mode 100644\nindex 0000000000..b4f2b40e61\n--- /dev/null\n+++ b/trace2/tr2_dst.c\n@@ -0,0 +1,197 @@\n+#include \"cache.h\"\n+#include \"trace2/tr2_dst.h\"\n+\n+/*\n+ * If a Trace2 target cannot be opened for writing, we should issue a\n+ * warning to stderr, but this is very annoying if the target is a pipe\n+ * or socket and beyond the user's control -- especially since every\n+ * git command (and sub-command) will print the message.  So we silently\n+ * eat these warnings and just discard the trace data.\n+ *\n+ * Enable the following environment variable to see these warnings.\n+ */\n+#define TR2_ENVVAR_DST_DEBUG \"GIT_TR2_DST_DEBUG\"\n+\n+static int tr2_dst_want_warning(void)\n+{\n+\tstatic int tr2env_dst_debug = -1;\n+\n+\tif (tr2env_dst_debug == -1) {\n+\t\tconst char *env_value = getenv(TR2_ENVVAR_DST_DEBUG);\n+\t\tif (!env_value || !*env_value)\n+\t\t\ttr2env_dst_debug = 0;\n+\t\telse\n+\t\t\ttr2env_dst_debug = atoi(env_value) > 0;\n+\t}\n+\n+\treturn tr2env_dst_debug;\n+}\n+\n+void tr2_dst_trace_disable(struct tr2_dst *dst)\n+{\n+\tif (dst->need_close)\n+\t\tclose(dst->fd);\n+\tdst->fd = 0;\n+\tdst->initialized = 1;\n+\tdst->need_close = 0;\n+}\n+\n+static int tr2_dst_try_path(struct tr2_dst *dst, const char *tgt_value)\n+{\n+\tint fd = open(tgt_value, O_WRONLY | O_APPEND | O_CREAT, 0666);\n+\tif (fd == -1) {\n+\t\tif (tr2_dst_want_warning())\n+\t\t\twarning(\"trace2: could not open '%s' for '%s' tracing: %s\",\n+\t\t\t\ttgt_value, dst->env_var_name, strerror(errno));\n+\n+\t\ttr2_dst_trace_disable(dst);\n+\t\treturn 0;\n+\t}\n+\n+\tdst->fd = fd;\n+\tdst->need_close = 1;\n+\tdst->initialized = 1;\n+\n+\treturn dst->fd;\n+}\n+\n+#ifndef NO_UNIX_SOCKETS\n+#define PREFIX_AF_UNIX \"af_unix:\"\n+#define PREFIX_AF_UNIX_LEN (8)\n+\n+static int tr2_dst_try_unix_domain_socket(struct tr2_dst *dst,\n+\t\t\t\t\t  const char *tgt_value)\n+{\n+\tint fd;\n+\tstruct sockaddr_un sa;\n+\tconst char *path = tgt_value + PREFIX_AF_UNIX_LEN;\n+\tint path_len = strlen(path);\n+\n+\tif (!is_absolute_path(path) || path_len >= sizeof(sa.sun_path)) {\n+\t\tif (tr2_dst_want_warning())\n+\t\t\twarning(\"trace2: invalid AF_UNIX path '%s' for '%s' tracing\",\n+\t\t\t\tpath, dst->env_var_name);\n+\n+\t\ttr2_dst_trace_disable(dst);\n+\t\treturn 0;\n+\t}\n+\n+\tsa.sun_family = AF_UNIX;\n+\tstrlcpy(sa.sun_path, path, sizeof(sa.sun_path));\n+\tif ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1 ||\n+\t    connect(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {\n+\t\tif (tr2_dst_want_warning())\n+\t\t\twarning(\"trace2: could not connect to socket '%s' for '%s' tracing: %s\",\n+\t\t\t\tpath, dst->env_var_name, strerror(errno));\n+\n+\t\ttr2_dst_trace_disable(dst);\n+\t\treturn 0;\n+\t}\n+\n+\tdst->fd = fd;\n+\tdst->need_close = 1;\n+\tdst->initialized = 1;\n+\n+\treturn dst->fd;\n+}\n+#endif\n+\n+static void tr2_dst_malformed_warning(struct tr2_dst *dst,\n+\t\t\t\t      const char *tgt_value)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf, \"trace2: unknown trace value for '%s': '%s'\",\n+\t\t    dst->env_var_name, tgt_value);\n+\tstrbuf_addstr(\n+\t\t&buf,\n+\t\t\"\\n         If you want to trace into a file, then please set it\"\n+\t\t\"\\n         to an absolute pathname.\");\n+#ifndef NO_UNIX_SOCKETS\n+\tstrbuf_addstr(\n+\t\t&buf,\n+\t\t\"\\n         If you want to trace to a unix domain socket, prefix\"\n+\t\t\"\\n         the absolute pathname with \\\"af_unix:\\\".\");\n+#endif\n+\n+\twarning(\"%s\", buf.buf);\n+\n+\tstrbuf_release(&buf);\n+}\n+\n+int tr2_dst_get_trace_fd(struct tr2_dst *dst)\n+{\n+\tconst char *tgt_value;\n+\n+\t/* don't open twice */\n+\tif (dst->initialized)\n+\t\treturn dst->fd;\n+\n+\tdst->initialized = 1;\n+\n+\ttgt_value = getenv(dst->env_var_name);\n+\n+\tif (!tgt_value || !strcmp(tgt_value, \"\") || !strcmp(tgt_value, \"0\") ||\n+\t    !strcasecmp(tgt_value, \"false\")) {\n+\t\tdst->fd = 0;\n+\t\treturn dst->fd;\n+\t}\n+\n+\tif (!strcmp(tgt_value, \"1\") || !strcasecmp(tgt_value, \"true\")) {\n+\t\tdst->fd = STDERR_FILENO;\n+\t\treturn dst->fd;\n+\t}\n+\n+\tif (strlen(tgt_value) == 1 && isdigit(*tgt_value)) {\n+\t\tdst->fd = atoi(tgt_value);\n+\t\treturn dst->fd;\n+\t}\n+\n+\tif (is_absolute_path(tgt_value))\n+\t\treturn tr2_dst_try_path(dst, tgt_value);\n+\n+#ifndef NO_UNIX_SOCKETS\n+\tif (!strncmp(tgt_value, PREFIX_AF_UNIX, PREFIX_AF_UNIX_LEN))\n+\t\treturn tr2_dst_try_unix_domain_socket(dst, tgt_value);\n+#endif\n+\n+\t/* Always warn about malformed values. */\n+\ttr2_dst_malformed_warning(dst, tgt_value);\n+\ttr2_dst_trace_disable(dst);\n+\treturn 0;\n+}\n+\n+int tr2_dst_trace_want(struct tr2_dst *dst)\n+{\n+\treturn !!tr2_dst_get_trace_fd(dst);\n+}\n+\n+void tr2_dst_write_line(struct tr2_dst *dst, struct strbuf *buf_line)\n+{\n+\tint fd = tr2_dst_get_trace_fd(dst);\n+\n+\tstrbuf_complete_line(buf_line); /* ensure final NL on buffer */\n+\n+\t/*\n+\t * We do not use write_in_full() because we do not want\n+\t * a short-write to try again.  We are using O_APPEND mode\n+\t * files and the kernel handles the atomic seek+write. If\n+\t * another thread or git process is concurrently writing to\n+\t * this fd or file, our remainder-write may not be contiguous\n+\t * with our initial write of this message.  And that will\n+\t * confuse readers.  So just don't bother.\n+\t *\n+\t * It is assumed that TRACE2 messages are short enough that\n+\t * the system can write them in 1 attempt and we won't see\n+\t * a short-write.\n+\t *\n+\t * If we get an IO error, just close the trace dst.\n+\t */\n+\tif (write(fd, buf_line->buf, buf_line->len) >= 0)\n+\t\treturn;\n+\n+\tif (tr2_dst_want_warning())\n+\t\twarning(\"unable to write trace to '%s': %s\", dst->env_var_name,\n+\t\t\tstrerror(errno));\n+\ttr2_dst_trace_disable(dst);\n+}\ndiff --git a/trace2/tr2_dst.h b/trace2/tr2_dst.h\nnew file mode 100644\nindex 0000000000..9a64f05b02\n--- /dev/null\n+++ b/trace2/tr2_dst.h\n@@ -0,0 +1,36 @@\n+#ifndef TR2_DST_H\n+#define TR2_DST_H\n+\n+struct strbuf;\n+\n+struct tr2_dst {\n+\tconst char *const env_var_name;\n+\tint fd;\n+\tunsigned int initialized : 1;\n+\tunsigned int need_close : 1;\n+};\n+\n+/*\n+ * Disable TRACE2 on the destination.  In TRACE2 a destination (DST)\n+ * wraps a file descriptor; it is associated with a TARGET which\n+ * defines the formatting.\n+ */\n+void tr2_dst_trace_disable(struct tr2_dst *dst);\n+\n+/*\n+ * Return the file descriptor for the DST.\n+ * If 0, the dst is closed or disabled.\n+ */\n+int tr2_dst_get_trace_fd(struct tr2_dst *dst);\n+\n+/*\n+ * Return true if the DST is opened for writing.\n+ */\n+int tr2_dst_trace_want(struct tr2_dst *dst);\n+\n+/*\n+ * Write a single line/message to the trace file.\n+ */\n+void tr2_dst_write_line(struct tr2_dst *dst, struct strbuf *buf_line);\n+\n+#endif /* TR2_DST_H */\ndiff --git a/trace2/tr2_sid.c b/trace2/tr2_sid.c\nnew file mode 100644\nindex 0000000000..984524a43c\n--- /dev/null\n+++ b/trace2/tr2_sid.c\n@@ -0,0 +1,67 @@\n+#include \"cache.h\"\n+#include \"trace2/tr2_sid.h\"\n+\n+#define TR2_ENVVAR_PARENT_SID \"GIT_TR2_PARENT_SID\"\n+\n+static struct strbuf tr2sid_buf = STRBUF_INIT;\n+static int tr2sid_nr_git_parents;\n+\n+/*\n+ * Compute a \"unique\" session id (SID) for the current process.  This allows\n+ * all events from this process to have a single label (much like a PID).\n+ *\n+ * Export this into our environment so that all child processes inherit it.\n+ *\n+ * If we were started by another git instance, use our parent's SID as a\n+ * prefix.  (This lets us track parent/child relationships even if there\n+ * is an intermediate shell process.)\n+ *\n+ * Additionally, count the number of nested git processes.\n+ */\n+static void tr2_sid_compute(void)\n+{\n+\tuint64_t us_now;\n+\tconst char *parent_sid;\n+\n+\tif (tr2sid_buf.len)\n+\t\treturn;\n+\n+\tparent_sid = getenv(TR2_ENVVAR_PARENT_SID);\n+\tif (parent_sid && *parent_sid) {\n+\t\tconst char *p;\n+\t\tfor (p = parent_sid; *p; p++)\n+\t\t\tif (*p == '/')\n+\t\t\t\ttr2sid_nr_git_parents++;\n+\n+\t\tstrbuf_addstr(&tr2sid_buf, parent_sid);\n+\t\tstrbuf_addch(&tr2sid_buf, '/');\n+\t\ttr2sid_nr_git_parents++;\n+\t}\n+\n+\tus_now = getnanotime() / 1000;\n+\tstrbuf_addf(&tr2sid_buf, \"%\" PRIuMAX \"-%\" PRIdMAX, (uintmax_t)us_now,\n+\t\t    (intmax_t)getpid());\n+\n+\tsetenv(TR2_ENVVAR_PARENT_SID, tr2sid_buf.buf, 1);\n+}\n+\n+const char *tr2_sid_get(void)\n+{\n+\tif (!tr2sid_buf.len)\n+\t\ttr2_sid_compute();\n+\n+\treturn tr2sid_buf.buf;\n+}\n+\n+int tr2_sid_depth(void)\n+{\n+\tif (!tr2sid_buf.len)\n+\t\ttr2_sid_compute();\n+\n+\treturn tr2sid_nr_git_parents;\n+}\n+\n+void tr2_sid_release(void)\n+{\n+\tstrbuf_release(&tr2sid_buf);\n+}\ndiff --git a/trace2/tr2_sid.h b/trace2/tr2_sid.h\nnew file mode 100644\nindex 0000000000..9bef321708\n--- /dev/null\n+++ b/trace2/tr2_sid.h\n@@ -0,0 +1,18 @@\n+#ifndef TR2_SID_H\n+#define TR2_SID_H\n+\n+/*\n+ * Get our session id. Compute if necessary.\n+ */\n+const char *tr2_sid_get(void);\n+\n+/*\n+ * Get our process depth.  A top-level git process invoked from the\n+ * command line will have depth=0.  A child git process will have\n+ * depth=1 and so on.\n+ */\n+int tr2_sid_depth(void);\n+\n+void tr2_sid_release(void);\n+\n+#endif /* TR2_SID_H */\ndiff --git a/trace2/tr2_tbuf.c b/trace2/tr2_tbuf.c\nnew file mode 100644\nindex 0000000000..0844910423\n--- /dev/null\n+++ b/trace2/tr2_tbuf.c\n@@ -0,0 +1,32 @@\n+#include \"cache.h\"\n+#include \"tr2_tbuf.h\"\n+\n+void tr2_tbuf_local_time(struct tr2_tbuf *tb)\n+{\n+\tstruct timeval tv;\n+\tstruct tm tm;\n+\ttime_t secs;\n+\n+\tgettimeofday(&tv, NULL);\n+\tsecs = tv.tv_sec;\n+\tlocaltime_r(&secs, &tm);\n+\n+\txsnprintf(tb->buf, sizeof(tb->buf), \"%02d:%02d:%02d.%06ld\", tm.tm_hour,\n+\t\t  tm.tm_min, tm.tm_sec, (long)tv.tv_usec);\n+}\n+\n+void tr2_tbuf_utc_time(struct tr2_tbuf *tb)\n+{\n+\tstruct timeval tv;\n+\tstruct tm tm;\n+\ttime_t secs;\n+\n+\tgettimeofday(&tv, NULL);\n+\tsecs = tv.tv_sec;\n+\tgmtime_r(&secs, &tm);\n+\n+\txsnprintf(tb->buf, sizeof(tb->buf),\n+\t\t  \"%4d-%02d-%02d %02d:%02d:%02d.%06ld\", tm.tm_year + 1900,\n+\t\t  tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,\n+\t\t  (long)tv.tv_usec);\n+}\ndiff --git a/trace2/tr2_tbuf.h b/trace2/tr2_tbuf.h\nnew file mode 100644\nindex 0000000000..9cdefa3957\n--- /dev/null\n+++ b/trace2/tr2_tbuf.h\n@@ -0,0 +1,23 @@\n+#ifndef TR2_TBUF_H\n+#define TR2_TBUF_H\n+\n+/*\n+ * A simple wrapper around a fixed buffer to avoid C syntax\n+ * quirks and the need to pass around an additional size_t\n+ * argument.\n+ */\n+struct tr2_tbuf {\n+\tchar buf[32];\n+};\n+\n+/*\n+ * Fill buffer with formatted local time string.\n+ */\n+void tr2_tbuf_local_time(struct tr2_tbuf *tb);\n+\n+/*\n+ * Fill buffer with formatted UTC time string.\n+ */\n+void tr2_tbuf_utc_time(struct tr2_tbuf *tb);\n+\n+#endif /* TR2_TBUF_H */\ndiff --git a/trace2/tr2_tgt.h b/trace2/tr2_tgt.h\nnew file mode 100644\nindex 0000000000..3be0a233e7\n--- /dev/null\n+++ b/trace2/tr2_tgt.h\n@@ -0,0 +1,131 @@\n+#ifndef TR2_TGT_H\n+#define TR2_TGT_H\n+\n+struct child_process;\n+struct repository;\n+struct json_writer;\n+\n+/*\n+ * Function prototypes for a TRACE2 \"target\" vtable.\n+ */\n+\n+typedef int(tr2_tgt_init_t)(void);\n+typedef void(tr2_tgt_term_t)(void);\n+\n+typedef void(tr2_tgt_evt_version_fl_t)(const char *file, int line);\n+\n+typedef void(tr2_tgt_evt_start_fl_t)(const char *file, int line,\n+\t\t\t\t     const char **argv);\n+typedef void(tr2_tgt_evt_exit_fl_t)(const char *file, int line,\n+\t\t\t\t    uint64_t us_elapsed_absolute, int code);\n+typedef void(tr2_tgt_evt_signal_t)(uint64_t us_elapsed_absolute, int signo);\n+typedef void(tr2_tgt_evt_atexit_t)(uint64_t us_elapsed_absolute, int code);\n+\n+typedef void(tr2_tgt_evt_error_va_fl_t)(const char *file, int line,\n+\t\t\t\t\tconst char *fmt, va_list ap);\n+\n+typedef void(tr2_tgt_evt_command_path_fl_t)(const char *file, int line,\n+\t\t\t\t\t    const char *command_path);\n+typedef void(tr2_tgt_evt_command_verb_fl_t)(const char *file, int line,\n+\t\t\t\t\t    const char *command_verb,\n+\t\t\t\t\t    const char *hierarchy);\n+typedef void(tr2_tgt_evt_command_subverb_fl_t)(const char *file, int line,\n+\t\t\t\t\t       const char *command_subverb);\n+\n+typedef void(tr2_tgt_evt_alias_fl_t)(const char *file, int line,\n+\t\t\t\t     const char *alias, const char **argv);\n+\n+typedef void(tr2_tgt_evt_child_start_fl_t)(const char *file, int line,\n+\t\t\t\t\t   uint64_t us_elapsed_absolute,\n+\t\t\t\t\t   const struct child_process *cmd);\n+typedef void(tr2_tgt_evt_child_exit_fl_t)(const char *file, int line,\n+\t\t\t\t\t  uint64_t us_elapsed_absolute, int cid,\n+\t\t\t\t\t  int pid, int code,\n+\t\t\t\t\t  uint64_t us_elapsed_child);\n+\n+typedef void(tr2_tgt_evt_thread_start_fl_t)(const char *file, int line,\n+\t\t\t\t\t    uint64_t us_elapsed_absolute);\n+typedef void(tr2_tgt_evt_thread_exit_fl_t)(const char *file, int line,\n+\t\t\t\t\t   uint64_t us_elapsed_absolute,\n+\t\t\t\t\t   uint64_t us_elapsed_thread);\n+\n+typedef void(tr2_tgt_evt_exec_fl_t)(const char *file, int line,\n+\t\t\t\t    uint64_t us_elapsed_absolute, int exec_id,\n+\t\t\t\t    const char *exe, const char **argv);\n+typedef void(tr2_tgt_evt_exec_result_fl_t)(const char *file, int line,\n+\t\t\t\t\t   uint64_t us_elapsed_absolute,\n+\t\t\t\t\t   int exec_id, int code);\n+\n+typedef void(tr2_tgt_evt_param_fl_t)(const char *file, int line,\n+\t\t\t\t     const char *param, const char *value);\n+\n+typedef void(tr2_tgt_evt_repo_fl_t)(const char *file, int line,\n+\t\t\t\t    const struct repository *repo);\n+\n+typedef void(tr2_tgt_evt_region_enter_printf_va_fl_t)(\n+\tconst char *file, int line, uint64_t us_elapsed_absolute,\n+\tconst char *category, const char *label, const struct repository *repo,\n+\tconst char *fmt, va_list ap);\n+typedef void(tr2_tgt_evt_region_leave_printf_va_fl_t)(\n+\tconst char *file, int line, uint64_t us_elapsed_absolute,\n+\tuint64_t us_elapsed_region, const char *category, const char *label,\n+\tconst struct repository *repo, const char *fmt, va_list ap);\n+\n+typedef void(tr2_tgt_evt_data_fl_t)(const char *file, int line,\n+\t\t\t\t    uint64_t us_elapsed_absolute,\n+\t\t\t\t    uint64_t us_elapsed_region,\n+\t\t\t\t    const char *category,\n+\t\t\t\t    const struct repository *repo,\n+\t\t\t\t    const char *key, const char *value);\n+typedef void(tr2_tgt_evt_data_json_fl_t)(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t uint64_t us_elapsed_region,\n+\t\t\t\t\t const char *category,\n+\t\t\t\t\t const struct repository *repo,\n+\t\t\t\t\t const char *key,\n+\t\t\t\t\t const struct json_writer *value);\n+\n+typedef void(tr2_tgt_evt_printf_va_fl_t)(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t const char *fmt, va_list ap);\n+\n+/*\n+ * \"vtable\" for a TRACE2 target.  Use NULL if a target does not want\n+ * to emit that message.\n+ */\n+struct tr2_tgt {\n+\tstruct tr2_dst                          *pdst;\n+\n+\ttr2_tgt_init_t                          *pfn_init;\n+\ttr2_tgt_term_t                          *pfn_term;\n+\n+\ttr2_tgt_evt_version_fl_t                *pfn_version_fl;\n+\ttr2_tgt_evt_start_fl_t                  *pfn_start_fl;\n+\ttr2_tgt_evt_exit_fl_t                   *pfn_exit_fl;\n+\ttr2_tgt_evt_signal_t                    *pfn_signal;\n+\ttr2_tgt_evt_atexit_t                    *pfn_atexit;\n+\ttr2_tgt_evt_error_va_fl_t               *pfn_error_va_fl;\n+\ttr2_tgt_evt_command_path_fl_t           *pfn_command_path_fl;\n+\ttr2_tgt_evt_command_verb_fl_t           *pfn_command_verb_fl;\n+\ttr2_tgt_evt_command_subverb_fl_t        *pfn_command_subverb_fl;\n+\ttr2_tgt_evt_alias_fl_t                  *pfn_alias_fl;\n+\ttr2_tgt_evt_child_start_fl_t            *pfn_child_start_fl;\n+\ttr2_tgt_evt_child_exit_fl_t             *pfn_child_exit_fl;\n+\ttr2_tgt_evt_thread_start_fl_t           *pfn_thread_start_fl;\n+\ttr2_tgt_evt_thread_exit_fl_t            *pfn_thread_exit_fl;\n+\ttr2_tgt_evt_exec_fl_t                   *pfn_exec_fl;\n+\ttr2_tgt_evt_exec_result_fl_t            *pfn_exec_result_fl;\n+\ttr2_tgt_evt_param_fl_t                  *pfn_param_fl;\n+\ttr2_tgt_evt_repo_fl_t                   *pfn_repo_fl;\n+\ttr2_tgt_evt_region_enter_printf_va_fl_t *pfn_region_enter_printf_va_fl;\n+\ttr2_tgt_evt_region_leave_printf_va_fl_t *pfn_region_leave_printf_va_fl;\n+\ttr2_tgt_evt_data_fl_t                   *pfn_data_fl;\n+\ttr2_tgt_evt_data_json_fl_t              *pfn_data_json_fl;\n+\ttr2_tgt_evt_printf_va_fl_t              *pfn_printf_va_fl;\n+};\n+\n+extern struct tr2_tgt tr2_tgt_event;\n+extern struct tr2_tgt tr2_tgt_normal;\n+extern struct tr2_tgt tr2_tgt_perf;\n+\n+#endif /* TR2_TGT_H */\ndiff --git a/trace2/tr2_tgt_event.c b/trace2/tr2_tgt_event.c\nnew file mode 100644\nindex 0000000000..7d4efc8a9b\n--- /dev/null\n+++ b/trace2/tr2_tgt_event.c\n@@ -0,0 +1,590 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"json-writer.h\"\n+#include \"run-command.h\"\n+#include \"version.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_tbuf.h\"\n+#include \"trace2/tr2_sid.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+static struct tr2_dst tr2dst_event = { \"GIT_TR2_EVENT\", 0, 0, 0 };\n+\n+/*\n+ * The version number of the JSON data generated by the EVENT target\n+ * in this source file.  Update this if you make a significant change\n+ * to the JSON fields or message structure.  You probably do not need\n+ * to update this if you just add another call to one of the existing\n+ * TRACE2 API methods.\n+ */\n+#define TR2_EVENT_VERSION \"1\"\n+\n+/*\n+ * Region nesting limit for messages written to the event target.\n+ *\n+ * The \"region_enter\" and \"region_leave\" messages (especially recursive\n+ * messages such as those produced while diving the worktree or index)\n+ * are primarily intended for the performance target during debugging.\n+ *\n+ * Some of the outer-most messages, however, may be of interest to the\n+ * event target.  Set this environment variable to a larger integer for\n+ * more detail in the event target.\n+ */\n+#define TR2_ENVVAR_EVENT_NESTING \"GIT_TR2_EVENT_NESTING\"\n+static int tr2env_event_nesting_wanted = 2;\n+\n+/*\n+ * Set this environment variable to true to omit the <time>, <file>, and\n+ * <line> fields from most events.\n+ */\n+#define TR2_ENVVAR_EVENT_BRIEF \"GIT_TR2_EVENT_BRIEF\"\n+static int tr2env_event_brief;\n+\n+static int fn_init(void)\n+{\n+\tint want = tr2_dst_trace_want(&tr2dst_event);\n+\tint want_nesting;\n+\tint want_brief;\n+\tchar *nesting;\n+\tchar *brief;\n+\n+\tif (!want)\n+\t\treturn want;\n+\n+\tnesting = getenv(TR2_ENVVAR_EVENT_NESTING);\n+\tif (nesting && ((want_nesting = atoi(nesting)) > 0))\n+\t\ttr2env_event_nesting_wanted = want_nesting;\n+\n+\tbrief = getenv(TR2_ENVVAR_EVENT_BRIEF);\n+\tif (brief && ((want_brief = atoi(brief)) > 0))\n+\t\ttr2env_event_brief = want_brief;\n+\n+\treturn want;\n+}\n+\n+static void fn_term(void)\n+{\n+\ttr2_dst_trace_disable(&tr2dst_event);\n+}\n+\n+/*\n+ * Append common key-value pairs to the currently open JSON object.\n+ *     \"event:\"<event_name>\"\n+ *      \"sid\":\"<sid>\"\n+ *   \"thread\":\"<thread_name>\"\n+ *     \"time\":\"<time>\"\n+ *     \"file\":\"<filename>\"\n+ *     \"line\":<line_number>\n+ *     \"repo\":<repo_id>\n+ */\n+static void event_fmt_prepare(const char *event_name, const char *file,\n+\t\t\t      int line, const struct repository *repo,\n+\t\t\t      struct json_writer *jw)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tstruct tr2_tbuf tb_now;\n+\n+\tjw_object_string(jw, \"event\", event_name);\n+\tjw_object_string(jw, \"sid\", tr2_sid_get());\n+\tjw_object_string(jw, \"thread\", ctx->thread_name.buf);\n+\n+\t/*\n+\t * In brief mode, only emit <time> on these 2 event types.\n+\t */\n+\tif (!tr2env_event_brief || !strcmp(event_name, \"version\") ||\n+\t    !strcmp(event_name, \"atexit\")) {\n+\t\ttr2_tbuf_utc_time(&tb_now);\n+\t\tjw_object_string(jw, \"time\", tb_now.buf);\n+\t}\n+\n+\tif (!tr2env_event_brief && file && *file) {\n+\t\tjw_object_string(jw, \"file\", file);\n+\t\tjw_object_intmax(jw, \"line\", line);\n+\t}\n+\n+\tif (repo)\n+\t\tjw_object_intmax(jw, \"repo\", repo->trace2_repo_id);\n+}\n+\n+static void fn_version_fl(const char *file, int line)\n+{\n+\tconst char *event_name = \"version\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"evt\", TR2_EVENT_VERSION);\n+\tjw_object_string(&jw, \"exe\", git_version_string);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_start_fl(const char *file, int line, const char **argv)\n+{\n+\tconst char *event_name = \"start\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tjw_array_argv(&jw, argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_exit_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int code)\n+{\n+\tconst char *event_name = \"exit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_signal(uint64_t us_elapsed_absolute, int signo)\n+{\n+\tconst char *event_name = \"signal\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, __FILE__, __LINE__, NULL, &jw);\n+\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\tjw_object_intmax(&jw, \"signo\", signo);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_atexit(uint64_t us_elapsed_absolute, int code)\n+{\n+\tconst char *event_name = \"atexit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, __FILE__, __LINE__, NULL, &jw);\n+\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void maybe_add_string_va(struct json_writer *jw, const char *field_name,\n+\t\t\t\tconst char *fmt, va_list ap)\n+{\n+\tif (fmt && *fmt && ap) {\n+\t\tva_list copy_ap;\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\n+\t\tva_copy(copy_ap, ap);\n+\t\tstrbuf_vaddf(&buf, fmt, copy_ap);\n+\t\tva_end(copy_ap);\n+\n+\t\tjw_object_string(jw, field_name, buf.buf);\n+\t\tstrbuf_release(&buf);\n+\t\treturn;\n+\t}\n+\n+\tif (fmt && *fmt) {\n+\t\tjw_object_string(jw, field_name, fmt);\n+\t\treturn;\n+\t}\n+}\n+\n+static void fn_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t   va_list ap)\n+{\n+\tconst char *event_name = \"error\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tmaybe_add_string_va(&jw, \"msg\", fmt, ap);\n+\t/*\n+\t * Also emit the format string as a field in case\n+\t * post-processors want to aggregate common error\n+\t * messages by type without argument fields (such\n+\t * as pathnames or branch names) cluttering it up.\n+\t */\n+\tif (fmt && *fmt)\n+\t\tjw_object_string(&jw, \"fmt\", fmt);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_command_path_fl(const char *file, int line, const char *pathname)\n+{\n+\tconst char *event_name = \"cmd_path\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"path\", pathname);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_command_verb_fl(const char *file, int line,\n+\t\t\t       const char *command_verb,\n+\t\t\t       const char *verb_hierarchy)\n+{\n+\tconst char *event_name = \"cmd_verb\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"name\", command_verb);\n+\tif (verb_hierarchy && *verb_hierarchy)\n+\t\tjw_object_string(&jw, \"hierarchy\", verb_hierarchy);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_command_subverb_fl(const char *file, int line,\n+\t\t\t\t  const char *command_subverb)\n+{\n+\tconst char *event_name = \"cmd_subverb\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"name\", command_subverb);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_alias_fl(const char *file, int line, const char *alias,\n+\t\t\tconst char **argv)\n+{\n+\tconst char *event_name = \"alias\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"alias\", alias);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tjw_array_argv(&jw, argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_child_start_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      const struct child_process *cmd)\n+{\n+\tconst char *event_name = \"child_start\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"child_id\", cmd->trace2_child_id);\n+\tif (cmd->trace2_hook_name) {\n+\t\tjw_object_string(&jw, \"child_class\", \"hook\");\n+\t\tjw_object_string(&jw, \"hook_name\", cmd->trace2_hook_name);\n+\t} else {\n+\t\tconst char *child_class =\n+\t\t\tcmd->trace2_child_class ? cmd->trace2_child_class : \"?\";\n+\t\tjw_object_string(&jw, \"child_class\", child_class);\n+\t}\n+\tif (cmd->dir)\n+\t\tjw_object_string(&jw, \"cd\", cmd->dir);\n+\tjw_object_bool(&jw, \"use_shell\", cmd->use_shell);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tif (cmd->git_cmd)\n+\t\tjw_array_string(&jw, \"git\");\n+\tjw_array_argv(&jw, cmd->argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_child_exit_fl(const char *file, int line,\n+\t\t\t     uint64_t us_elapsed_absolute, int cid, int pid,\n+\t\t\t     int code, uint64_t us_elapsed_child)\n+{\n+\tconst char *event_name = \"child_exit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_rel = (double)us_elapsed_child / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"child_id\", cid);\n+\tjw_object_intmax(&jw, \"pid\", pid);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\n+\tjw_release(&jw);\n+}\n+\n+static void fn_thread_start_fl(const char *file, int line,\n+\t\t\t       uint64_t us_elapsed_absolute)\n+{\n+\tconst char *event_name = \"thread_start\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_thread_exit_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      uint64_t us_elapsed_thread)\n+{\n+\tconst char *event_name = \"thread_exit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_rel = (double)us_elapsed_thread / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int exec_id, const char *exe, const char **argv)\n+{\n+\tconst char *event_name = \"exec\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"exec_id\", exec_id);\n+\tif (exe)\n+\t\tjw_object_string(&jw, \"exe\", exe);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tjw_array_argv(&jw, argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_exec_result_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute, int exec_id,\n+\t\t\t      int code)\n+{\n+\tconst char *event_name = \"exec_result\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"exec_id\", exec_id);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_param_fl(const char *file, int line, const char *param,\n+\t\t\tconst char *value)\n+{\n+\tconst char *event_name = \"def_param\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"param\", param);\n+\tjw_object_string(&jw, \"value\", value);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_repo_fl(const char *file, int line,\n+\t\t       const struct repository *repo)\n+{\n+\tconst char *event_name = \"def_repo\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\tjw_object_string(&jw, \"worktree\", repo->worktree);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t const char *category,\n+\t\t\t\t\t const char *label,\n+\t\t\t\t\t const struct repository *repo,\n+\t\t\t\t\t const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_enter\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tif (category)\n+\t\t\tjw_object_string(&jw, \"category\", category);\n+\t\tif (label)\n+\t\t\tjw_object_string(&jw, \"label\", label);\n+\t\tmaybe_add_string_va(&jw, \"msg\", fmt, ap);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+static void fn_region_leave_printf_va_fl(\n+\tconst char *file, int line, uint64_t us_elapsed_absolute,\n+\tuint64_t us_elapsed_region, const char *category, const char *label,\n+\tconst struct repository *repo, const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_leave\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\t\tdouble t_rel = (double)us_elapsed_region / 1000000.0;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tif (category)\n+\t\t\tjw_object_string(&jw, \"category\", category);\n+\t\tif (label)\n+\t\t\tjw_object_string(&jw, \"label\", label);\n+\t\tmaybe_add_string_va(&jw, \"msg\", fmt, ap);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+static void fn_data_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       uint64_t us_elapsed_region, const char *category,\n+\t\t       const struct repository *repo, const char *key,\n+\t\t       const char *value)\n+{\n+\tconst char *event_name = \"data\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\t\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\t\tdouble t_rel = (double)us_elapsed_region / 1000000.0;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\t\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tjw_object_string(&jw, \"category\", category);\n+\t\tjw_object_string(&jw, \"key\", key);\n+\t\tjw_object_string(&jw, \"value\", value);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+static void fn_data_json_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute,\n+\t\t\t    uint64_t us_elapsed_region, const char *category,\n+\t\t\t    const struct repository *repo, const char *key,\n+\t\t\t    const struct json_writer *value)\n+{\n+\tconst char *event_name = \"data_json\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\t\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\t\tdouble t_rel = (double)us_elapsed_region / 1000000.0;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\t\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tjw_object_string(&jw, \"category\", category);\n+\t\tjw_object_string(&jw, \"key\", key);\n+\t\tjw_object_sub_jw(&jw, \"value\", value);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+struct tr2_tgt tr2_tgt_event = {\n+\t&tr2dst_event,\n+\n+\tfn_init,\n+\tfn_term,\n+\n+\tfn_version_fl,\n+\tfn_start_fl,\n+\tfn_exit_fl,\n+\tfn_signal,\n+\tfn_atexit,\n+\tfn_error_va_fl,\n+\tfn_command_path_fl,\n+\tfn_command_verb_fl,\n+\tfn_command_subverb_fl,\n+\tfn_alias_fl,\n+\tfn_child_start_fl,\n+\tfn_child_exit_fl,\n+\tfn_thread_start_fl,\n+\tfn_thread_exit_fl,\n+\tfn_exec_fl,\n+\tfn_exec_result_fl,\n+\tfn_param_fl,\n+\tfn_repo_fl,\n+\tfn_region_enter_printf_va_fl,\n+\tfn_region_leave_printf_va_fl,\n+\tfn_data_fl,\n+\tfn_data_json_fl,\n+\tNULL, /* printf */\n+};\ndiff --git a/trace2/tr2_tgt_normal.c b/trace2/tr2_tgt_normal.c\nnew file mode 100644\nindex 0000000000..239bcb8980\n--- /dev/null\n+++ b/trace2/tr2_tgt_normal.c\n@@ -0,0 +1,325 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"run-command.h\"\n+#include \"quote.h\"\n+#include \"version.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_tbuf.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+static struct tr2_dst tr2dst_normal = { \"GIT_TR2\", 0, 0, 0 };\n+\n+/*\n+ * Set this environment variable to true to omit the \"<time> <file>:<line>\"\n+ * fields from each line written to the builtin normal target.\n+ *\n+ * Unit tests may want to use this to help with testing.\n+ */\n+#define TR2_ENVVAR_NORMAL_BRIEF \"GIT_TR2_BRIEF\"\n+static int tr2env_normal_brief;\n+\n+#define TR2FMT_NORMAL_FL_WIDTH (50)\n+\n+static int fn_init(void)\n+{\n+\tint want = tr2_dst_trace_want(&tr2dst_normal);\n+\tint want_brief;\n+\tchar *brief;\n+\n+\tif (!want)\n+\t\treturn want;\n+\n+\tbrief = getenv(TR2_ENVVAR_NORMAL_BRIEF);\n+\tif (brief && *brief &&\n+\t    ((want_brief = git_parse_maybe_bool(brief)) != -1))\n+\t\ttr2env_normal_brief = want_brief;\n+\n+\treturn want;\n+}\n+\n+static void fn_term(void)\n+{\n+\ttr2_dst_trace_disable(&tr2dst_normal);\n+}\n+\n+static void normal_fmt_prepare(const char *file, int line, struct strbuf *buf)\n+{\n+\tstrbuf_setlen(buf, 0);\n+\n+\tif (!tr2env_normal_brief) {\n+\t\tstruct tr2_tbuf tb_now;\n+\n+\t\ttr2_tbuf_local_time(&tb_now);\n+\t\tstrbuf_addstr(buf, tb_now.buf);\n+\t\tstrbuf_addch(buf, ' ');\n+\n+\t\tif (file && *file)\n+\t\t\tstrbuf_addf(buf, \"%s:%d \", file, line);\n+\t\twhile (buf->len < TR2FMT_NORMAL_FL_WIDTH)\n+\t\t\tstrbuf_addch(buf, ' ');\n+\t}\n+}\n+\n+static void normal_io_write_fl(const char *file, int line,\n+\t\t\t       const struct strbuf *buf_payload)\n+{\n+\tstruct strbuf buf_line = STRBUF_INIT;\n+\n+\tnormal_fmt_prepare(file, line, &buf_line);\n+\tstrbuf_addbuf(&buf_line, buf_payload);\n+\ttr2_dst_write_line(&tr2dst_normal, &buf_line);\n+\tstrbuf_release(&buf_line);\n+}\n+\n+static void fn_version_fl(const char *file, int line)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"version %s\", git_version_string);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_start_fl(const char *file, int line, const char **argv)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"start \");\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exit_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int code)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"exit elapsed:%.6f code:%d\", elapsed, code);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_signal(uint64_t us_elapsed_absolute, int signo)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"signal elapsed:%.6f code:%d\", elapsed,\n+\t\t    signo);\n+\tnormal_io_write_fl(__FILE__, __LINE__, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_atexit(uint64_t us_elapsed_absolute, int code)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"atexit elapsed:%.6f code:%d\", elapsed, code);\n+\tnormal_io_write_fl(__FILE__, __LINE__, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void maybe_append_string_va(struct strbuf *buf, const char *fmt,\n+\t\t\t\t   va_list ap)\n+{\n+\tif (fmt && *fmt && ap) {\n+\t\tva_list copy_ap;\n+\n+\t\tva_copy(copy_ap, ap);\n+\t\tstrbuf_vaddf(buf, fmt, copy_ap);\n+\t\tva_end(copy_ap);\n+\t\treturn;\n+\t}\n+\n+\tif (fmt && *fmt) {\n+\t\tstrbuf_addstr(buf, fmt);\n+\t\treturn;\n+\t}\n+}\n+\n+static void fn_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t   va_list ap)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"error \");\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_path_fl(const char *file, int line, const char *pathname)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"cmd_path %s\", pathname);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_verb_fl(const char *file, int line,\n+\t\t\t       const char *command_verb,\n+\t\t\t       const char *verb_hierarchy)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"cmd_verb %s\", command_verb);\n+\tif (verb_hierarchy && *verb_hierarchy)\n+\t\tstrbuf_addf(&buf_payload, \" (%s)\", verb_hierarchy);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_subverb_fl(const char *file, int line,\n+\t\t\t\t  const char *command_subverb)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"cmd_subverb %s\", command_subverb);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_alias_fl(const char *file, int line, const char *alias,\n+\t\t\tconst char **argv)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"alias %s ->\", alias);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_start_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      const struct child_process *cmd)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"child_start[%d] \", cmd->trace2_child_id);\n+\n+\tif (cmd->dir) {\n+\t\tstrbuf_addstr(&buf_payload, \" cd\");\n+\t\tsq_quote_buf_pretty(&buf_payload, cmd->dir);\n+\t\tstrbuf_addstr(&buf_payload, \"; \");\n+\t}\n+\n+\t/*\n+\t * TODO if (cmd->env) { Consider dumping changes to environment. }\n+\t * See trace_add_env() in run-command.c as used by original trace.c\n+\t */\n+\n+\tif (cmd->git_cmd)\n+\t\tstrbuf_addstr(&buf_payload, \"git\");\n+\tsq_quote_argv_pretty(&buf_payload, cmd->argv);\n+\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_exit_fl(const char *file, int line,\n+\t\t\t     uint64_t us_elapsed_absolute, int cid, int pid,\n+\t\t\t     int code, uint64_t us_elapsed_child)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_child / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"child_exit[%d] pid:%d code:%d elapsed:%.6f\",\n+\t\t    cid, pid, code, elapsed);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int exec_id, const char *exe, const char **argv)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"exec[%d] \", exec_id);\n+\tif (exe)\n+\t\tstrbuf_addstr(&buf_payload, exe);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_result_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute, int exec_id,\n+\t\t\t      int code)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"exec_result[%d] code:%d\", exec_id, code);\n+\tif (code > 0)\n+\t\tstrbuf_addf(&buf_payload, \" err:%s\", strerror(code));\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_param_fl(const char *file, int line, const char *param,\n+\t\t\tconst char *value)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"def_param %s=%s\", param, value);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_repo_fl(const char *file, int line,\n+\t\t       const struct repository *repo)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"worktree \");\n+\tsq_quote_buf_pretty(&buf_payload, repo->worktree);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_printf_va_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute, const char *fmt,\n+\t\t\t    va_list ap)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+struct tr2_tgt tr2_tgt_normal = {\n+\t&tr2dst_normal,\n+\n+\tfn_init,\n+\tfn_term,\n+\n+\tfn_version_fl,\n+\tfn_start_fl,\n+\tfn_exit_fl,\n+\tfn_signal,\n+\tfn_atexit,\n+\tfn_error_va_fl,\n+\tfn_command_path_fl,\n+\tfn_command_verb_fl,\n+\tfn_command_subverb_fl,\n+\tfn_alias_fl,\n+\tfn_child_start_fl,\n+\tfn_child_exit_fl,\n+\tNULL, /* thread_start */\n+\tNULL, /* thread_exit */\n+\tfn_exec_fl,\n+\tfn_exec_result_fl,\n+\tfn_param_fl,\n+\tfn_repo_fl,\n+\tNULL, /* region_enter */\n+\tNULL, /* region_leave */\n+\tNULL, /* data */\n+\tNULL, /* data_json */\n+\tfn_printf_va_fl,\n+};\ndiff --git a/trace2/tr2_tgt_perf.c b/trace2/tr2_tgt_perf.c\nnew file mode 100644\nindex 0000000000..dbdb08b4f3\n--- /dev/null\n+++ b/trace2/tr2_tgt_perf.c\n@@ -0,0 +1,536 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"run-command.h\"\n+#include \"quote.h\"\n+#include \"version.h\"\n+#include \"json-writer.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_sid.h\"\n+#include \"trace2/tr2_tbuf.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+static struct tr2_dst tr2dst_perf = { \"GIT_TR2_PERF\", 0, 0, 0 };\n+\n+/*\n+ * Set this environment variable to true to omit the \"<time> <file>:<line>\"\n+ * fields from each line written to the builtin performance target.\n+ *\n+ * Unit tests may want to use this to help with testing.\n+ */\n+#define TR2_ENVVAR_PERF_BRIEF \"GIT_TR2_PERF_BRIEF\"\n+static int tr2env_perf_brief;\n+\n+#define TR2FMT_PERF_FL_WIDTH (50)\n+#define TR2FMT_PERF_MAX_EVENT_NAME (12)\n+#define TR2FMT_PERF_REPO_WIDTH (4)\n+#define TR2FMT_PERF_CATEGORY_WIDTH (10)\n+\n+#define TR2_DOTS_BUFFER_SIZE (100)\n+#define TR2_INDENT (2)\n+#define TR2_INDENT_LENGTH(ctx) (((ctx)->nr_open_regions - 1) * TR2_INDENT)\n+\n+static struct strbuf dots = STRBUF_INIT;\n+\n+static int fn_init(void)\n+{\n+\tint want = tr2_dst_trace_want(&tr2dst_perf);\n+\tint want_brief;\n+\tchar *brief;\n+\n+\tif (!want)\n+\t\treturn want;\n+\n+\tstrbuf_addchars(&dots, '.', TR2_DOTS_BUFFER_SIZE);\n+\n+\tbrief = getenv(TR2_ENVVAR_PERF_BRIEF);\n+\tif (brief && *brief &&\n+\t    ((want_brief = git_parse_maybe_bool(brief)) != -1))\n+\t\ttr2env_perf_brief = want_brief;\n+\n+\treturn want;\n+}\n+\n+static void fn_term(void)\n+{\n+\ttr2_dst_trace_disable(&tr2dst_perf);\n+\n+\tstrbuf_release(&dots);\n+}\n+\n+/*\n+ * Format trace line prefix in human-readable classic format for\n+ * the performance target:\n+ *     \"[<time> [<file>:<line>] <bar>] <nr_parents> <bar>\n+ *         <thread_name> <bar> <event_name> <bar> [<repo>] <bar>\n+ *         [<elapsed_absolute>] [<elapsed_relative>] <bar>\n+ *         [<category>] <bar> [<dots>] \"\n+ */\n+static void perf_fmt_prepare(const char *event_name,\n+\t\t\t     struct tr2tls_thread_ctx *ctx, const char *file,\n+\t\t\t     int line, const struct repository *repo,\n+\t\t\t     uint64_t *p_us_elapsed_absolute,\n+\t\t\t     uint64_t *p_us_elapsed_relative,\n+\t\t\t     const char *category, struct strbuf *buf)\n+{\n+\tint len;\n+\n+\tstrbuf_setlen(buf, 0);\n+\n+\tif (!tr2env_perf_brief) {\n+\t\tstruct tr2_tbuf tb_now;\n+\n+\t\ttr2_tbuf_local_time(&tb_now);\n+\t\tstrbuf_addstr(buf, tb_now.buf);\n+\t\tstrbuf_addch(buf, ' ');\n+\n+\t\tif (file && *file)\n+\t\t\tstrbuf_addf(buf, \"%s:%d \", file, line);\n+\t\twhile (buf->len < TR2FMT_PERF_FL_WIDTH)\n+\t\t\tstrbuf_addch(buf, ' ');\n+\n+\t\tstrbuf_addstr(buf, \"| \");\n+\t}\n+\n+\tstrbuf_addf(buf, \"d%d | \", tr2_sid_depth());\n+\tstrbuf_addf(buf, \"%-*s | %-*s | \", TR2_MAX_THREAD_NAME,\n+\t\t    ctx->thread_name.buf, TR2FMT_PERF_MAX_EVENT_NAME,\n+\t\t    event_name);\n+\n+\tlen = buf->len + TR2FMT_PERF_REPO_WIDTH;\n+\tif (repo)\n+\t\tstrbuf_addf(buf, \"r%d \", repo->trace2_repo_id);\n+\twhile (buf->len < len)\n+\t\tstrbuf_addch(buf, ' ');\n+\tstrbuf_addstr(buf, \"| \");\n+\n+\tif (p_us_elapsed_absolute)\n+\t\tstrbuf_addf(buf, \"%9.6f | \",\n+\t\t\t    ((double)(*p_us_elapsed_absolute)) / 1000000.0);\n+\telse\n+\t\tstrbuf_addf(buf, \"%9s | \", \" \");\n+\n+\tif (p_us_elapsed_relative)\n+\t\tstrbuf_addf(buf, \"%9.6f | \",\n+\t\t\t    ((double)(*p_us_elapsed_relative)) / 1000000.0);\n+\telse\n+\t\tstrbuf_addf(buf, \"%9s | \", \" \");\n+\n+\tstrbuf_addf(buf, \"%-*s | \", TR2FMT_PERF_CATEGORY_WIDTH,\n+\t\t    (category ? category : \"\"));\n+\n+\tif (ctx->nr_open_regions > 0) {\n+\t\tint len_indent = TR2_INDENT_LENGTH(ctx);\n+\t\twhile (len_indent > dots.len) {\n+\t\t\tstrbuf_addbuf(buf, &dots);\n+\t\t\tlen_indent -= dots.len;\n+\t\t}\n+\t\tstrbuf_addf(buf, \"%.*s\", len_indent, dots.buf);\n+\t}\n+}\n+\n+static void perf_io_write_fl(const char *file, int line, const char *event_name,\n+\t\t\t     const struct repository *repo,\n+\t\t\t     uint64_t *p_us_elapsed_absolute,\n+\t\t\t     uint64_t *p_us_elapsed_relative,\n+\t\t\t     const char *category,\n+\t\t\t     const struct strbuf *buf_payload)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tstruct strbuf buf_line = STRBUF_INIT;\n+\n+\tperf_fmt_prepare(event_name, ctx, file, line, repo,\n+\t\t\t p_us_elapsed_absolute, p_us_elapsed_relative, category,\n+\t\t\t &buf_line);\n+\tstrbuf_addbuf(&buf_line, buf_payload);\n+\ttr2_dst_write_line(&tr2dst_perf, &buf_line);\n+\tstrbuf_release(&buf_line);\n+}\n+\n+static void fn_version_fl(const char *file, int line)\n+{\n+\tconst char *event_name = \"version\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, git_version_string);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_start_fl(const char *file, int line, const char **argv)\n+{\n+\tconst char *event_name = \"start\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exit_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int code)\n+{\n+\tconst char *event_name = \"exit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"code:%d\", code);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_signal(uint64_t us_elapsed_absolute, int signo)\n+{\n+\tconst char *event_name = \"signal\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"signo:%d\", signo);\n+\n+\tperf_io_write_fl(__FILE__, __LINE__, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_atexit(uint64_t us_elapsed_absolute, int code)\n+{\n+\tconst char *event_name = \"atexit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"code:%d\", code);\n+\n+\tperf_io_write_fl(__FILE__, __LINE__, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void maybe_append_string_va(struct strbuf *buf, const char *fmt,\n+\t\t\t\t   va_list ap)\n+{\n+\tif (fmt && *fmt && ap) {\n+\t\tva_list copy_ap;\n+\n+\t\tva_copy(copy_ap, ap);\n+\t\tstrbuf_vaddf(buf, fmt, copy_ap);\n+\t\tva_end(copy_ap);\n+\t\treturn;\n+\t}\n+\n+\tif (fmt && *fmt) {\n+\t\tstrbuf_addstr(buf, fmt);\n+\t\treturn;\n+\t}\n+}\n+\n+static void fn_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t   va_list ap)\n+{\n+\tconst char *event_name = \"error\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_path_fl(const char *file, int line, const char *pathname)\n+{\n+\tconst char *event_name = \"cmd_path\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, pathname);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_verb_fl(const char *file, int line,\n+\t\t\t       const char *command_verb,\n+\t\t\t       const char *verb_hierarchy)\n+{\n+\tconst char *event_name = \"cmd_verb\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, command_verb);\n+\tif (verb_hierarchy && *verb_hierarchy)\n+\t\tstrbuf_addf(&buf_payload, \" (%s)\", verb_hierarchy);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_subverb_fl(const char *file, int line,\n+\t\t\t\t  const char *command_subverb)\n+{\n+\tconst char *event_name = \"cmd_subverb\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, command_subverb);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_alias_fl(const char *file, int line, const char *alias,\n+\t\t\tconst char **argv)\n+{\n+\tconst char *event_name = \"alias\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"alias:%s argv:\", alias);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_start_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      const struct child_process *cmd)\n+{\n+\tconst char *event_name = \"child_start\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tif (cmd->trace2_hook_name) {\n+\t\tstrbuf_addf(&buf_payload, \"[ch%d] class:hook hook:%s\",\n+\t\t\t    cmd->trace2_child_id, cmd->trace2_hook_name);\n+\t} else {\n+\t\tconst char *child_class =\n+\t\t\tcmd->trace2_child_class ? cmd->trace2_child_class : \"?\";\n+\t\tstrbuf_addf(&buf_payload, \"[ch%d] class:%s\",\n+\t\t\t    cmd->trace2_child_id, child_class);\n+\t}\n+\n+\tif (cmd->dir) {\n+\t\tstrbuf_addstr(&buf_payload, \" cd:\");\n+\t\tsq_quote_buf_pretty(&buf_payload, cmd->dir);\n+\t}\n+\n+\tstrbuf_addstr(&buf_payload, \" argv:\");\n+\tif (cmd->git_cmd)\n+\t\tstrbuf_addstr(&buf_payload, \" git\");\n+\tsq_quote_argv_pretty(&buf_payload, cmd->argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_exit_fl(const char *file, int line,\n+\t\t\t     uint64_t us_elapsed_absolute, int cid, int pid,\n+\t\t\t     int code, uint64_t us_elapsed_child)\n+{\n+\tconst char *event_name = \"child_exit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"[ch%d] pid:%d code:%d\", cid, pid, code);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_child, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_thread_start_fl(const char *file, int line,\n+\t\t\t       uint64_t us_elapsed_absolute)\n+{\n+\tconst char *event_name = \"thread_start\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_thread_exit_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      uint64_t us_elapsed_thread)\n+{\n+\tconst char *event_name = \"thread_exit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_thread, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int exec_id, const char *exe, const char **argv)\n+{\n+\tconst char *event_name = \"exec\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"id:%d \", exec_id);\n+\tstrbuf_addstr(&buf_payload, \"argv:\");\n+\tif (exe)\n+\t\tstrbuf_addf(&buf_payload, \" %s\", exe);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_result_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute, int exec_id,\n+\t\t\t      int code)\n+{\n+\tconst char *event_name = \"exec_result\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"id:%d code:%d\", exec_id, code);\n+\tif (code > 0)\n+\t\tstrbuf_addf(&buf_payload, \" err:%s\", strerror(code));\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_param_fl(const char *file, int line, const char *param,\n+\t\t\tconst char *value)\n+{\n+\tconst char *event_name = \"def_param\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"%s:%s\", param, value);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_repo_fl(const char *file, int line,\n+\t\t       const struct repository *repo)\n+{\n+\tconst char *event_name = \"def_repo\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"worktree:\");\n+\tsq_quote_buf_pretty(&buf_payload, repo->worktree);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t const char *category,\n+\t\t\t\t\t const char *label,\n+\t\t\t\t\t const struct repository *repo,\n+\t\t\t\t\t const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_enter\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tif (label)\n+\t\tstrbuf_addf(&buf_payload, \"label:%s \", label);\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,\n+\t\t\t NULL, category, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_region_leave_printf_va_fl(\n+\tconst char *file, int line, uint64_t us_elapsed_absolute,\n+\tuint64_t us_elapsed_region, const char *category, const char *label,\n+\tconst struct repository *repo, const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_leave\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tif (label)\n+\t\tstrbuf_addf(&buf_payload, \"label:%s \", label);\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_region, category, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_data_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       uint64_t us_elapsed_region, const char *category,\n+\t\t       const struct repository *repo, const char *key,\n+\t\t       const char *value)\n+{\n+\tconst char *event_name = \"data\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"%s:%s\", key, value);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_region, category, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_data_json_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute,\n+\t\t\t    uint64_t us_elapsed_region, const char *category,\n+\t\t\t    const struct repository *repo, const char *key,\n+\t\t\t    const struct json_writer *value)\n+{\n+\tconst char *event_name = \"data_json\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"%s:%s\", key, value->json.buf);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_region, category, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_printf_va_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute, const char *fmt,\n+\t\t\t    va_list ap)\n+{\n+\tconst char *event_name = \"printf\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+struct tr2_tgt tr2_tgt_perf = {\n+\t&tr2dst_perf,\n+\n+\tfn_init,\n+\tfn_term,\n+\n+\tfn_version_fl,\n+\tfn_start_fl,\n+\tfn_exit_fl,\n+\tfn_signal,\n+\tfn_atexit,\n+\tfn_error_va_fl,\n+\tfn_command_path_fl,\n+\tfn_command_verb_fl,\n+\tfn_command_subverb_fl,\n+\tfn_alias_fl,\n+\tfn_child_start_fl,\n+\tfn_child_exit_fl,\n+\tfn_thread_start_fl,\n+\tfn_thread_exit_fl,\n+\tfn_exec_fl,\n+\tfn_exec_result_fl,\n+\tfn_param_fl,\n+\tfn_repo_fl,\n+\tfn_region_enter_printf_va_fl,\n+\tfn_region_leave_printf_va_fl,\n+\tfn_data_fl,\n+\tfn_data_json_fl,\n+\tfn_printf_va_fl,\n+};\ndiff --git a/trace2/tr2_tls.c b/trace2/tr2_tls.c\nnew file mode 100644\nindex 0000000000..8e65b0361d\n--- /dev/null\n+++ b/trace2/tr2_tls.c\n@@ -0,0 +1,164 @@\n+#include \"cache.h\"\n+#include \"thread-utils.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+/*\n+ * Initialize size of the thread stack for nested regions.\n+ * This is used to store nested region start times.  Note that\n+ * this stack is per-thread and not per-trace-key.\n+ */\n+#define TR2_REGION_NESTING_INITIAL_SIZE (100)\n+\n+static struct tr2tls_thread_ctx *tr2tls_thread_main;\n+static uint64_t tr2tls_us_start_main;\n+\n+static pthread_mutex_t tr2tls_mutex;\n+static pthread_key_t tr2tls_key;\n+\n+static int tr2_next_thread_id; /* modify under lock */\n+\n+struct tr2tls_thread_ctx *tr2tls_create_self(const char *thread_name)\n+{\n+\tuint64_t us_now = getnanotime() / 1000;\n+\tstruct tr2tls_thread_ctx *ctx = xcalloc(1, sizeof(*ctx));\n+\n+\t/*\n+\t * Implicitly \"tr2tls_push_self()\" to capture the thread's start\n+\t * time in array_us_start[0].  For the main thread this gives us the\n+\t * application run time.\n+\t */\n+\tctx->alloc = TR2_REGION_NESTING_INITIAL_SIZE;\n+\tctx->array_us_start = (uint64_t *)xcalloc(ctx->alloc, sizeof(uint64_t));\n+\tctx->array_us_start[ctx->nr_open_regions++] = us_now;\n+\n+\tctx->thread_id = tr2tls_locked_increment(&tr2_next_thread_id);\n+\n+\tstrbuf_init(&ctx->thread_name, 0);\n+\tif (ctx->thread_id)\n+\t\tstrbuf_addf(&ctx->thread_name, \"th%02d:\", ctx->thread_id);\n+\tstrbuf_addstr(&ctx->thread_name, thread_name);\n+\tif (ctx->thread_name.len > TR2_MAX_THREAD_NAME)\n+\t\tstrbuf_setlen(&ctx->thread_name, TR2_MAX_THREAD_NAME);\n+\n+\tpthread_setspecific(tr2tls_key, ctx);\n+\n+\treturn ctx;\n+}\n+\n+struct tr2tls_thread_ctx *tr2tls_get_self(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = pthread_getspecific(tr2tls_key);\n+\n+\t/*\n+\t * If the thread-proc did not call trace2_thread_start(), we won't\n+\t * have any TLS data associated with the current thread.  Fix it\n+\t * here and silently continue.\n+\t */\n+\tif (!ctx)\n+\t\tctx = tr2tls_create_self(\"unknown\");\n+\n+\treturn ctx;\n+}\n+\n+int tr2tls_is_main_thread(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = pthread_getspecific(tr2tls_key);\n+\n+\treturn ctx == tr2tls_thread_main;\n+}\n+\n+void tr2tls_unset_self(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx;\n+\n+\tctx = tr2tls_get_self();\n+\n+\tpthread_setspecific(tr2tls_key, NULL);\n+\n+\tfree(ctx->array_us_start);\n+\tfree(ctx);\n+}\n+\n+void tr2tls_push_self(uint64_t us_now)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\n+\tALLOC_GROW(ctx->array_us_start, ctx->nr_open_regions + 1, ctx->alloc);\n+\tctx->array_us_start[ctx->nr_open_regions++] = us_now;\n+}\n+\n+void tr2tls_pop_self(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\n+\tif (!ctx->nr_open_regions)\n+\t\tBUG(\"no open regions in thread '%s'\", ctx->thread_name.buf);\n+\n+\tctx->nr_open_regions--;\n+}\n+\n+void tr2tls_pop_unwind_self(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\n+\twhile (ctx->nr_open_regions > 1)\n+\t\ttr2tls_pop_self();\n+}\n+\n+uint64_t tr2tls_region_elasped_self(uint64_t us)\n+{\n+\tstruct tr2tls_thread_ctx *ctx;\n+\tuint64_t us_start;\n+\n+\tctx = tr2tls_get_self();\n+\tif (!ctx->nr_open_regions)\n+\t\treturn 0;\n+\n+\tus_start = ctx->array_us_start[ctx->nr_open_regions - 1];\n+\n+\treturn us - us_start;\n+}\n+\n+uint64_t tr2tls_absolute_elapsed(uint64_t us)\n+{\n+\tif (!tr2tls_thread_main)\n+\t\treturn 0;\n+\n+\treturn us - tr2tls_us_start_main;\n+}\n+\n+void tr2tls_init(void)\n+{\n+\tpthread_key_create(&tr2tls_key, NULL);\n+\tinit_recursive_mutex(&tr2tls_mutex);\n+\n+\ttr2tls_thread_main = tr2tls_create_self(\"main\");\n+\t/*\n+\t * Keep a copy of the absolute start time of the main thread\n+\t * in a fixed variable since other threads need to access it.\n+\t * This also eliminates the need to lock accesses to the main\n+\t * thread's array (because of reallocs).\n+\t */\n+\ttr2tls_us_start_main = tr2tls_thread_main->array_us_start[0];\n+}\n+\n+void tr2tls_release(void)\n+{\n+\ttr2tls_unset_self();\n+\ttr2tls_thread_main = NULL;\n+\n+\tpthread_mutex_destroy(&tr2tls_mutex);\n+\tpthread_key_delete(tr2tls_key);\n+}\n+\n+int tr2tls_locked_increment(int *p)\n+{\n+\tint current_value;\n+\n+\tpthread_mutex_lock(&tr2tls_mutex);\n+\tcurrent_value = *p;\n+\t*p = current_value + 1;\n+\tpthread_mutex_unlock(&tr2tls_mutex);\n+\n+\treturn current_value;\n+}\ndiff --git a/trace2/tr2_tls.h b/trace2/tr2_tls.h\nnew file mode 100644\nindex 0000000000..b271ccd8dd\n--- /dev/null\n+++ b/trace2/tr2_tls.h\n@@ -0,0 +1,97 @@\n+#ifndef TR2_TLS_H\n+#define TR2_TLS_H\n+\n+struct strbuf;\n+\n+/*\n+ * Arbitry limit for thread names for column alignment.\n+ */\n+#define TR2_MAX_THREAD_NAME (24)\n+\n+struct tr2tls_thread_ctx {\n+\tstruct strbuf thread_name;\n+\tuint64_t *array_us_start;\n+\tint alloc;\n+\tint nr_open_regions; /* plays role of \"nr\" in ALLOC_GROW */\n+\tint thread_id;\n+};\n+\n+/*\n+ * Create TLS data for the current thread.  This gives us a place to\n+ * put per-thread data, such as thread start time, function nesting\n+ * and a per-thread label for our messages.\n+ *\n+ * We assume the first thread is \"main\".  Other threads are given\n+ * non-zero thread-ids to help distinguish messages from concurrent\n+ * threads.\n+ *\n+ * Truncate the thread name if necessary to help with column alignment\n+ * in printf-style messages.\n+ *\n+ * In this and all following functions the term \"self\" refers to the\n+ * current thread.\n+ */\n+struct tr2tls_thread_ctx *tr2tls_create_self(const char *thread_name);\n+\n+/*\n+ * Get our TLS data.\n+ */\n+struct tr2tls_thread_ctx *tr2tls_get_self(void);\n+\n+/*\n+ * return true if the current thread is the main thread.\n+ */\n+int tr2tls_is_main_thread(void);\n+\n+/*\n+ * Free our TLS data.\n+ */\n+void tr2tls_unset_self(void);\n+\n+/*\n+ * Begin a new nested region and remember the start time.\n+ */\n+void tr2tls_push_self(uint64_t us_now);\n+\n+/*\n+ * End the innermost nested region.\n+ */\n+void tr2tls_pop_self(void);\n+\n+/*\n+ * Pop any extra (above the first) open regions on the current\n+ * thread and discard.  During a thread-exit, we should only\n+ * have region[0] that was pushed in trace2_thread_start() if\n+ * the thread exits normally.\n+ */\n+void tr2tls_pop_unwind_self(void);\n+\n+/*\n+ * Compute the elapsed time since the innermost region in the\n+ * current thread started and the given time (usually now).\n+ */\n+uint64_t tr2tls_region_elasped_self(uint64_t us);\n+\n+/*\n+ * Compute the elapsed time since the main thread started\n+ * and the given time (usually now).  This is assumed to\n+ * be the absolute run time of the process.\n+ */\n+uint64_t tr2tls_absolute_elapsed(uint64_t us);\n+\n+/*\n+ * Initialize the tr2 TLS system.\n+ */\n+void tr2tls_init(void);\n+\n+/*\n+ * Free all tr2 TLS resources.\n+ */\n+void tr2tls_release(void);\n+\n+/*\n+ * Protected increment of an integer.\n+ */\n+int tr2tls_locked_increment(int *p);\n+\n+#endif /* TR2_TLS_H */\ndiff --git a/trace2/tr2_verb.c b/trace2/tr2_verb.c\nnew file mode 100644\nindex 0000000000..52be27c29f\n--- /dev/null\n+++ b/trace2/tr2_verb.c\n@@ -0,0 +1,30 @@\n+#include \"cache.h\"\n+#include \"trace2/tr2_verb.h\"\n+\n+#define TR2_ENVVAR_PARENT_VERB \"GIT_TR2_PARENT_VERB\"\n+\n+static struct strbuf tr2verb_hierarchy = STRBUF_INIT;\n+\n+void tr2_verb_append_hierarchy(const char *verb)\n+{\n+\tconst char *parent_verb = getenv(TR2_ENVVAR_PARENT_VERB);\n+\n+\tstrbuf_reset(&tr2verb_hierarchy);\n+\tif (parent_verb && *parent_verb) {\n+\t\tstrbuf_addstr(&tr2verb_hierarchy, parent_verb);\n+\t\tstrbuf_addch(&tr2verb_hierarchy, '/');\n+\t}\n+\tstrbuf_addstr(&tr2verb_hierarchy, verb);\n+\n+\tsetenv(TR2_ENVVAR_PARENT_VERB, tr2verb_hierarchy.buf, 1);\n+}\n+\n+const char *tr2_verb_get_hierarchy(void)\n+{\n+\treturn tr2verb_hierarchy.buf;\n+}\n+\n+void tr2_verb_release(void)\n+{\n+\tstrbuf_release(&tr2verb_hierarchy);\n+}\ndiff --git a/trace2/tr2_verb.h b/trace2/tr2_verb.h\nnew file mode 100644\nindex 0000000000..f84f873463\n--- /dev/null\n+++ b/trace2/tr2_verb.h\n@@ -0,0 +1,24 @@\n+#ifndef TR2_VERB_H\n+#define TR2_VERB_H\n+\n+/*\n+ * Append the current git command's \"verb\" to the list being maintained\n+ * in the environment.\n+ *\n+ * The hierarchy for a top-level git command is just the current verb.\n+ * For a child git process, the hierarchy lists the verbs of the parent\n+ * git processes (much like the SID).\n+ *\n+ * The hierarchy for the current process will be exported to the environment\n+ * and inherited by child processes.\n+ */\n+void tr2_verb_append_hierarchy(const char *verb);\n+\n+/*\n+ * Get the verb hierarchy for the current process.\n+ */\n+const char *tr2_verb_get_hierarchy(void);\n+\n+void tr2_verb_release(void);\n+\n+#endif /* TR2_VERB_H */\ndiff --git a/usage.c b/usage.c\nindex cc803336bd..5ec7511e11 100644\n--- a/usage.c\n+++ b/usage.c\n@@ -22,17 +22,48 @@ void vreportf(const char *prefix, const char *err, va_list params)\n static NORETURN void usage_builtin(const char *err, va_list params)\n {\n \tvreportf(\"usage: \", err, params);\n+\n+\t/*\n+\t * When we detect a usage error *before* the command dispatch in\n+\t * cmd_main(), we don't know what verb to report.  Force it to this\n+\t * to facilitate post-processing.\n+\t */\n+\ttrace2_cmd_verb(\"_usage_\");\n+\n+\t/*\n+\t * Currently, the (err, params) are usually just the static usage\n+\t * string which isn't very useful here.  Usually, the call site\n+\t * manually calls fprintf(stderr,...) with the actual detailed\n+\t * syntax error before calling usage().\n+\t *\n+\t * TODO It would be nice to update the call sites to pass both\n+\t * the static usage string and the detailed error message.\n+\t */\n+\n \texit(129);\n }\n \n static NORETURN void die_builtin(const char *err, va_list params)\n {\n+\t/*\n+\t * We call this trace2 function first and expect it to va_copy 'params'\n+\t * before using it (because an 'ap' can only be walked once).\n+\t */\n+\ttrace2_cmd_error_va(err, params);\n+\n \tvreportf(\"fatal: \", err, params);\n+\n \texit(128);\n }\n \n static void error_builtin(const char *err, va_list params)\n {\n+\t/*\n+\t * We call this trace2 function first and expect it to va_copy 'params'\n+\t * before using it (because an 'ap' can only be walked once).\n+\t */\n+\ttrace2_cmd_error_va(err, params);\n+\n \tvreportf(\"error: \", err, params);\n }\n \n-- \ngitgitgadget\n\n"},{"id":"368187","messageId":"87a73af3382c53a917026524d2308b0b8475ded0.1548881779.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 09/14] trace2:data: add trace2 instrumentation to index read/write","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T20:56:29Z","receivedAt":"2019-01-30T20:57:07Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 events to measure reading and writing the index.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n read-cache.c | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 50 insertions(+), 1 deletion(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex bfff271a3d..240531d70d 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -2232,6 +2232,16 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)\n \t\tload_index_extensions(&p);\n \t}\n \tmunmap((void *)mmap, mmap_size);\n+\n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/version\",\n+\t\t\t   istate->version);\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/cache_nr\",\n+\t\t\t   istate->cache_nr);\n+\n \treturn istate->cache_nr;\n \n unmap:\n@@ -2263,9 +2273,17 @@ int read_index_from(struct index_state *istate, const char *path,\n \tif (istate->initialized)\n \t\treturn istate->cache_nr;\n \n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_region_enter_printf(\"index\", \"do_read_index\", the_repository,\n+\t\t\t\t   \"%s\", path);\n \ttrace_performance_enter();\n \tret = do_read_index(istate, path, 0);\n \ttrace_performance_leave(\"read cache %s\", path);\n+\ttrace2_region_leave_printf(\"index\", \"do_read_index\", the_repository,\n+\t\t\t\t   \"%s\", path);\n \n \tsplit_index = istate->split_index;\n \tif (!split_index || is_null_oid(&split_index->base_oid)) {\n@@ -2281,7 +2299,11 @@ int read_index_from(struct index_state *istate, const char *path,\n \n \tbase_oid_hex = oid_to_hex(&split_index->base_oid);\n \tbase_path = xstrfmt(\"%s/sharedindex.%s\", gitdir, base_oid_hex);\n+\ttrace2_region_enter_printf(\"index\", \"shared/do_read_index\",\n+\t\t\t\t   the_repository, \"%s\", base_path);\n \tret = do_read_index(split_index->base, base_path, 1);\n+\ttrace2_region_leave_printf(\"index\", \"shared/do_read_index\",\n+\t\t\t\t   the_repository, \"%s\", base_path);\n \tif (!oideq(&split_index->base_oid, &split_index->base->oid))\n \t\tdie(_(\"broken index, expect %s in %s, got %s\"),\n \t\t    base_oid_hex, base_path,\n@@ -2988,6 +3010,16 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \tistate->timestamp.sec = (unsigned int)st.st_mtime;\n \tistate->timestamp.nsec = ST_MTIME_NSEC(st);\n \ttrace_performance_since(start, \"write index, changed mask = %x\", istate->cache_changed);\n+\n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_data_intmax(\"index\", the_repository, \"write/version\",\n+\t\t\t   istate->version);\n+\ttrace2_data_intmax(\"index\", the_repository, \"write/cache_nr\",\n+\t\t\t   istate->cache_nr);\n+\n \treturn 0;\n }\n \n@@ -3007,7 +3039,18 @@ static int commit_locked_index(struct lock_file *lk)\n static int do_write_locked_index(struct index_state *istate, struct lock_file *lock,\n \t\t\t\t unsigned flags)\n {\n-\tint ret = do_write_index(istate, lock->tempfile, 0);\n+\tint ret;\n+\n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_region_enter_printf(\"index\", \"do_write_index\", the_repository,\n+\t\t\t\t   \"%s\", lock->tempfile->filename.buf);\n+\tret = do_write_index(istate, lock->tempfile, 0);\n+\ttrace2_region_leave_printf(\"index\", \"do_write_index\", the_repository,\n+\t\t\t\t   \"%s\", lock->tempfile->filename.buf);\n+\n \tif (ret)\n \t\treturn ret;\n \tif (flags & COMMIT_LOCK)\n@@ -3092,7 +3135,13 @@ static int write_shared_index(struct index_state *istate,\n \tint ret;\n \n \tmove_cache_to_base_index(istate);\n+\n+\ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n+\t\t\t\t   the_repository, \"%s\", (*temp)->filename.buf);\n \tret = do_write_index(si->base, *temp, 1);\n+\ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n+\t\t\t\t   the_repository, \"%s\", (*temp)->filename.buf);\n+\n \tif (ret)\n \t\treturn ret;\n \tret = adjust_shared_perm(get_tempfile_path(*temp));\n-- \ngitgitgadget\n\n"},{"id":"368188","messageId":"be707ea9600a86d349471be00829c924ce99b035.1548881779.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 10/14] pack-objects: add trace2 regions","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T20:56:30Z","receivedAt":"2019-01-30T20:57:08Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWhen studying the performance of 'git push' we would like to know\nhow much time is spent at various parts of the command. One area\nthat could cause performance trouble is 'git pack-objects'.\n\nAdd trace2 regions around the three main actions taken in this\ncommand:\n\n1. Enumerate objects.\n2. Prepare pack.\n3. Write pack-file.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/pack-objects.c | 16 +++++++++++++++-\n 1 file changed, 15 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 0a70d04604..8a64c2868e 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -33,6 +33,7 @@\n #include \"object-store.h\"\n #include \"dir.h\"\n #include \"midx.h\"\n+#include \"trace2.h\"\n \n #define IN_PACK(obj) oe_in_pack(&to_pack, obj)\n #define SIZE(obj) oe_size(&to_pack, obj)\n@@ -3472,6 +3473,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n+\ttrace2_region_enter(\"pack-objects\", \"enumerate-objects\",\n+\t\t\t    the_repository);\n \tprepare_packing_data(the_repository, &to_pack);\n \n \tif (progress)\n@@ -3486,12 +3489,23 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)\n \tif (include_tag && nr_result)\n \t\tfor_each_ref(add_ref_tag, NULL);\n \tstop_progress(&progress_state);\n+\ttrace2_region_leave(\"pack-objects\", \"enumerate-objects\",\n+\t\t\t    the_repository);\n \n \tif (non_empty && !nr_result)\n \t\treturn 0;\n-\tif (nr_result)\n+\tif (nr_result) {\n+\t\ttrace2_region_enter(\"pack-objects\", \"prepare-pack\",\n+\t\t\t\t    the_repository);\n \t\tprepare_pack(window, depth);\n+\t\ttrace2_region_leave(\"pack-objects\", \"prepare-pack\",\n+\t\t\t\t    the_repository);\n+\t}\n+\n+\ttrace2_region_enter(\"pack-objects\", \"write-pack-file\", the_repository);\n \twrite_pack_file();\n+\ttrace2_region_leave(\"pack-objects\", \"write-pack-file\", the_repository);\n+\n \tif (progress)\n \t\tfprintf_ln(stderr,\n \t\t\t   _(\"Total %\"PRIu32\" (delta %\"PRIu32\"),\"\n-- \ngitgitgadget\n\n"},{"id":"368189","messageId":"660b83c453d416ed8c1754a2d2934abbd99d0660.1548881779.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 07/14] trace2:data: add trace2 transport child classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T20:56:27Z","receivedAt":"2019-01-30T20:57:10Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 child classification for transport processes.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n connect.c          | 3 +++\n transport-helper.c | 2 ++\n 2 files changed, 5 insertions(+)\n\ndiff --git a/connect.c b/connect.c\nindex 24281b6082..3c6f829a05 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -1251,6 +1251,7 @@ struct child_process *git_connect(int fd[2], const char *url,\n \t\tconn = NULL;\n \t} else if (protocol == PROTO_GIT) {\n \t\tconn = git_connect_git(fd, hostandport, path, prog, version, flags);\n+\t\tconn->trace2_child_class = \"transport/git\";\n \t} else {\n \t\tstruct strbuf cmd = STRBUF_INIT;\n \t\tconst char *const *var;\n@@ -1293,9 +1294,11 @@ struct child_process *git_connect(int fd[2], const char *url,\n \t\t\t\tstrbuf_release(&cmd);\n \t\t\t\treturn NULL;\n \t\t\t}\n+\t\t\tconn->trace2_child_class = \"transport/ssh\";\n \t\t\tfill_ssh_args(conn, ssh_host, port, version, flags);\n \t\t} else {\n \t\t\ttransport_check_allowed(\"file\");\n+\t\t\tconn->trace2_child_class = \"transport/file\";\n \t\t\tif (version > 0) {\n \t\t\t\targv_array_pushf(&conn->env_array, GIT_PROTOCOL_ENVIRONMENT \"=version=%d\",\n \t\t\t\t\t\t version);\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 6cf3bb324e..a01cc0093f 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -127,6 +127,8 @@ static struct child_process *get_helper(struct transport *transport)\n \t\targv_array_pushf(&helper->env_array, \"%s=%s\",\n \t\t\t\t GIT_DIR_ENVIRONMENT, get_git_dir());\n \n+\thelper->trace2_child_class = helper->args.argv[0]; /* \"remote-<name>\" */\n+\n \tcode = start_command(helper);\n \tif (code < 0 && errno == ENOENT)\n \t\tdie(_(\"unable to find remote helper for '%s'\"), data->name);\n-- \ngitgitgadget\n\n"},{"id":"368190","messageId":"ab26888126b94d409d8bbbfd3a755ab1ceca28d3.1548881779.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 08/14] trace2:data: add trace2 hook classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T20:56:28Z","receivedAt":"2019-01-30T20:57:11Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nClassify certain child processes as hooks.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/am.c           | 1 +\n builtin/receive-pack.c | 4 ++++\n builtin/worktree.c     | 1 +\n sequencer.c            | 2 ++\n transport.c            | 1 +\n 5 files changed, 9 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 95370313b6..a81e61c1bc 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -468,6 +468,7 @@ static int run_post_rewrite_hook(const struct am_state *state)\n \n \tcp.in = xopen(am_path(state, \"rewritten\"), O_RDONLY);\n \tcp.stdout_to_stderr = 1;\n+\tcp.trace2_hook_name = \"post-rewrite\";\n \n \tret = run_command(&cp);\n \ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 33187bd8e9..6eef8575b9 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -694,6 +694,8 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,\n \tproc.argv = argv;\n \tproc.in = -1;\n \tproc.stdout_to_stderr = 1;\n+\tproc.trace2_hook_name = hook_name;\n+\n \tif (feed_state->push_options) {\n \t\tint i;\n \t\tfor (i = 0; i < feed_state->push_options->nr; i++)\n@@ -807,6 +809,7 @@ static int run_update_hook(struct command *cmd)\n \tproc.stdout_to_stderr = 1;\n \tproc.err = use_sideband ? -1 : 0;\n \tproc.argv = argv;\n+\tproc.trace2_hook_name = \"update\";\n \n \tcode = start_command(&proc);\n \tif (code)\n@@ -1190,6 +1193,7 @@ static void run_update_post_hook(struct command *commands)\n \tproc.no_stdin = 1;\n \tproc.stdout_to_stderr = 1;\n \tproc.err = use_sideband ? -1 : 0;\n+\tproc.trace2_hook_name = \"post-update\";\n \n \tif (!start_command(&proc)) {\n \t\tif (use_sideband)\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 3f9907fcc9..6cc094a453 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -402,6 +402,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\t\tcp.dir = path;\n \t\t\tcp.env = env;\n \t\t\tcp.argv = NULL;\n+\t\t\tcp.trace2_hook_name = \"post-checkout\";\n \t\t\targv_array_pushl(&cp.args, absolute_path(hook),\n \t\t\t\t\t oid_to_hex(&null_oid),\n \t\t\t\t\t oid_to_hex(&commit->object.oid),\ndiff --git a/sequencer.c b/sequencer.c\nindex 213815dbfc..c1e36196a1 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1103,6 +1103,7 @@ static int run_rewrite_hook(const struct object_id *oldoid,\n \tproc.argv = argv;\n \tproc.in = -1;\n \tproc.stdout_to_stderr = 1;\n+\tproc.trace2_hook_name = \"post-rewrite\";\n \n \tcode = start_command(&proc);\n \tif (code)\n@@ -3784,6 +3785,7 @@ static int pick_commits(struct repository *r,\n \t\t\t\thook.in = open(rebase_path_rewritten_list(),\n \t\t\t\t\tO_RDONLY);\n \t\t\t\thook.stdout_to_stderr = 1;\n+\t\t\t\thook.trace2_hook_name = \"post-rewrite\";\n \t\t\t\targv_array_push(&hook.args, post_rewrite_hook);\n \t\t\t\targv_array_push(&hook.args, \"rebase\");\n \t\t\t\t/* we don't care if this hook failed */\ndiff --git a/transport.c b/transport.c\nindex 99678153c1..ed5a733c4a 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1061,6 +1061,7 @@ static int run_pre_push_hook(struct transport *transport,\n \n \tproc.argv = argv;\n \tproc.in = -1;\n+\tproc.trace2_hook_name = \"pre-push\";\n \n \tif (start_command(&proc)) {\n \t\tfinish_command(&proc);\n-- \ngitgitgadget\n\n"},{"id":"368192","messageId":"f4f0453e6bde289c8d4a696521148d41320bbb8b.1548881779.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 06/14] trace2:data: add trace2 sub-process classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T20:56:26Z","receivedAt":"2019-01-30T20:57:20Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 classification for long-running processes\nstarted in sub-process.c\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n sub-process.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/sub-process.c b/sub-process.c\nindex 8d2a1707cf..3f4af93555 100644\n--- a/sub-process.c\n+++ b/sub-process.c\n@@ -88,6 +88,7 @@ int subprocess_start(struct hashmap *hashmap, struct subprocess_entry *entry, co\n \tprocess->out = -1;\n \tprocess->clean_on_exit = 1;\n \tprocess->clean_on_exit_handler = subprocess_exit_handler;\n+\tprocess->trace2_child_class = \"subprocess\";\n \n \terr = start_command(process);\n \tif (err) {\n-- \ngitgitgadget\n\n"},{"id":"368193","messageId":"638fdb294f46001de15006a49e919519c7555465.1548881779.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 05/14] trace2:data: add editor/pager child classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T20:56:25Z","receivedAt":"2019-01-30T20:57:25Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 process classification for editor and pager\nchild processes.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n editor.c | 1 +\n pager.c  | 1 +\n 2 files changed, 2 insertions(+)\n\ndiff --git a/editor.c b/editor.c\nindex c985eee1f9..71547674ab 100644\n--- a/editor.c\n+++ b/editor.c\n@@ -78,6 +78,7 @@ static int launch_specified_editor(const char *editor, const char *path,\n \t\tp.argv = args;\n \t\tp.env = env;\n \t\tp.use_shell = 1;\n+\t\tp.trace2_child_class = \"editor\";\n \t\tif (start_command(&p) < 0)\n \t\t\treturn error(\"unable to start editor '%s'\", editor);\n \ndiff --git a/pager.c b/pager.c\nindex a768797fcf..4168460ae9 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -100,6 +100,7 @@ void prepare_pager_args(struct child_process *pager_process, const char *pager)\n \targv_array_push(&pager_process->args, pager);\n \tpager_process->use_shell = 1;\n \tsetup_pager_env(&pager_process->env_array);\n+\tpager_process->trace2_child_class = \"pager\";\n }\n \n void setup_pager(void)\n-- \ngitgitgadget\n\n"},{"id":"368194","messageId":"3624c6cbd914f13cabca7c94a10f9512a883d1e1.1548881779.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 04/14] trace2:data: add trace2 regions to wt-status","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T20:56:24Z","receivedAt":"2019-01-30T20:57:43Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2_region_enter() and trace2_region_leave() calls around the\nvarious phases of a status scan.  This gives elapsed time for each\nphase in the GIT_TR2_PERF and GIT_TR2_EVENT trace target.\n\nAlso, these Trace2 calls now use s->repo rather than the_repository.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n wt-status.c | 24 ++++++++++++++++++++++--\n 1 file changed, 22 insertions(+), 2 deletions(-)\n\ndiff --git a/wt-status.c b/wt-status.c\nindex 0fe3bcd4cd..434636850e 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -748,12 +748,23 @@ static int has_unmerged(struct wt_status *s)\n \n void wt_status_collect(struct wt_status *s)\n {\n+\ttrace2_region_enter(\"status\", \"worktrees\", s->repo);\n \twt_status_collect_changes_worktree(s);\n-\tif (s->is_initial)\n+\ttrace2_region_leave(\"status\", \"worktrees\", s->repo);\n+\n+\tif (s->is_initial) {\n+\t\ttrace2_region_enter(\"status\", \"initial\", s->repo);\n \t\twt_status_collect_changes_initial(s);\n-\telse\n+\t\ttrace2_region_leave(\"status\", \"initial\", s->repo);\n+\t} else {\n+\t\ttrace2_region_enter(\"status\", \"index\", s->repo);\n \t\twt_status_collect_changes_index(s);\n+\t\ttrace2_region_leave(\"status\", \"index\", s->repo);\n+\t}\n+\n+\ttrace2_region_enter(\"status\", \"untracked\", s->repo);\n \twt_status_collect_untracked(s);\n+\ttrace2_region_leave(\"status\", \"untracked\", s->repo);\n \n \twt_status_get_state(s->repo, &s->state, s->branch && !strcmp(s->branch, \"HEAD\"));\n \tif (s->state.merge_in_progress && !has_unmerged(s))\n@@ -2291,6 +2302,13 @@ static void wt_porcelain_v2_print(struct wt_status *s)\n \n void wt_status_print(struct wt_status *s)\n {\n+\ttrace2_data_intmax(\"status\", s->repo, \"count/changed\", s->change.nr);\n+\ttrace2_data_intmax(\"status\", s->repo, \"count/untracked\",\n+\t\t\t   s->untracked.nr);\n+\ttrace2_data_intmax(\"status\", s->repo, \"count/ignored\", s->ignored.nr);\n+\n+\ttrace2_region_enter(\"status\", \"print\", s->repo);\n+\n \tswitch (s->status_format) {\n \tcase STATUS_FORMAT_SHORT:\n \t\twt_shortstatus_print(s);\n@@ -2309,6 +2327,8 @@ void wt_status_print(struct wt_status *s)\n \t\twt_longstatus_print(s);\n \t\tbreak;\n \t}\n+\n+\ttrace2_region_leave(\"status\", \"print\", s->repo);\n }\n \n /**\n-- \ngitgitgadget\n\n"},{"id":"368195","messageId":"6167aba76ca43707bbba6de9f7c41a9fcb16063c.1548881779.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 03/14] trace2: collect platform-specific process information","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T20:56:24Z","receivedAt":"2019-01-30T20:58:20Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd optional platform-specific code to log information about\nthe current process.\n\nOn Windows, this includes whether git.exe is running under a\ndebugger and information about the ancestors of the process.\n\nThe purpose of this information is to help indicate if the\nprocess was launched interactively or in the background by\nan IDE, for example.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n common-main.c           |   1 +\n compat/win32/ancestry.c | 101 ++++++++++++++++++++++++++++++++++++++++\n config.mak.uname        |   2 +\n trace2.h                |  14 ++++++\n 4 files changed, 118 insertions(+)\n create mode 100644 compat/win32/ancestry.c\n\ndiff --git a/common-main.c b/common-main.c\nindex 6dbdc4adf2..d484aec209 100644\n--- a/common-main.c\n+++ b/common-main.c\n@@ -37,6 +37,7 @@ int main(int argc, const char **argv)\n \n \ttrace2_initialize();\n \ttrace2_cmd_start(argv);\n+\ttrace2_collect_process_info();\n \n \tgit_resolve_executable_dir(argv[0]);\n \ndiff --git a/compat/win32/ancestry.c b/compat/win32/ancestry.c\nnew file mode 100644\nindex 0000000000..253199f812\n--- /dev/null\n+++ b/compat/win32/ancestry.c\n@@ -0,0 +1,101 @@\n+#include \"../../cache.h\"\n+#include \"../../json-writer.h\"\n+#include <Psapi.h>\n+#include <tlHelp32.h>\n+\n+/*\n+ * Find the process data for the given PID in the given snapshot\n+ * and update the PROCESSENTRY32 data.\n+ */\n+static int find_pid(DWORD pid, HANDLE hSnapshot, PROCESSENTRY32 *pe32)\n+{\n+\tpe32->dwSize = sizeof(PROCESSENTRY32);\n+\n+\tif (Process32First(hSnapshot, pe32)) {\n+\t\tdo {\n+\t\t\tif (pe32->th32ProcessID == pid)\n+\t\t\t\treturn 1;\n+\t\t} while (Process32Next(hSnapshot, pe32));\n+\t}\n+\treturn 0;\n+}\n+\n+/*\n+ * Accumulate JSON array:\n+ *     [\n+ *         exe-name-parent,\n+ *         exe-name-grand-parent,\n+ *         ...\n+ *     ]\n+ *\n+ * Note: we only report the filename of the process executable; the\n+ *       only way to get its full pathname is to use OpenProcess()\n+ *       and GetModuleFileNameEx() or QueryfullProcessImageName()\n+ *       and that seems rather expensive (on top of the cost of\n+ *       getting the snapshot).\n+ */\n+static void get_processes(struct json_writer *jw, HANDLE hSnapshot)\n+{\n+\tPROCESSENTRY32 pe32;\n+\tDWORD pid;\n+\n+\tpid = GetCurrentProcessId();\n+\n+\t/* We only want parent processes, so skip self. */\n+\tif (!find_pid(pid, hSnapshot, &pe32))\n+\t\treturn;\n+\tpid = pe32.th32ParentProcessID;\n+\n+\twhile (find_pid(pid, hSnapshot, &pe32)) {\n+\t\tjw_array_string(jw, pe32.szExeFile);\n+\n+\t\tpid = pe32.th32ParentProcessID;\n+\t}\n+}\n+\n+/*\n+ * Emit JSON data for the current and parent processes.  Individual\n+ * trace2 targets can decide how to actually print it.\n+ */\n+static void get_ancestry(void)\n+{\n+\tHANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);\n+\n+\tif (hSnapshot != INVALID_HANDLE_VALUE) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\t\tjw_array_begin(&jw, 0);\n+\t\tget_processes(&jw, hSnapshot);\n+\t\tjw_end(&jw);\n+\n+\t\ttrace2_data_json(\"process\", the_repository, \"windows/ancestry\",\n+\t\t\t\t &jw);\n+\n+\t\tjw_release(&jw);\n+\t\tCloseHandle(hSnapshot);\n+\t}\n+}\n+\n+/*\n+ * Is a debugger attached to the current process?\n+ *\n+ * This will catch debug runs (where the debugger started the process).\n+ * This is the normal case.  Since this code is called during our startup,\n+ * it will not report instances where a debugger is attached dynamically\n+ * to a running git process, but that is relatively rare.\n+ */\n+static void get_is_being_debugged(void)\n+{\n+\tif (IsDebuggerPresent())\n+\t\ttrace2_data_intmax(\"process\", the_repository,\n+\t\t\t\t   \"windows/debugger_present\", 1);\n+}\n+\n+void trace2_collect_process_info(void)\n+{\n+\tif (!trace2_is_enabled())\n+\t\treturn;\n+\n+\tget_is_being_debugged();\n+\tget_ancestry();\n+}\ndiff --git a/config.mak.uname b/config.mak.uname\nindex 7b36a1dfe7..853aa2e77b 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -391,6 +391,7 @@ ifeq ($(uname_S),Windows)\n \tBASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE\n \tCOMPAT_OBJS = compat/msvc.o compat/winansi.o \\\n \t\tcompat/win32/pthread.o compat/win32/syslog.o \\\n+\t\tcompat/win32/ancestry.o \\\n \t\tcompat/win32/dirent.o\n \tCOMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\\\".exe\\\"\n \tBASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE\n@@ -545,6 +546,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))\n \tCOMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32\n \tCOMPAT_CFLAGS += -DSTRIP_EXTENSION=\\\".exe\\\"\n \tCOMPAT_OBJS += compat/mingw.o compat/winansi.o \\\n+\t\tcompat/win32/ancestry.o \\\n \t\tcompat/win32/path-utils.o \\\n \t\tcompat/win32/pthread.o compat/win32/syslog.o \\\n \t\tcompat/win32/dirent.o\ndiff --git a/trace2.h b/trace2.h\nindex a0e99d9c26..cb11a46366 100644\n--- a/trace2.h\n+++ b/trace2.h\n@@ -363,4 +363,18 @@ __attribute__((format (printf, 1, 2)))\n void trace2_printf(const char *fmt, ...);\n #endif\n \n+/*\n+ * Optional platform-specific code to dump information about the\n+ * current and any parent process(es).  This is intended to allow\n+ * post-processors to know who spawned this git instance and anything\n+ * else the platform may be able to tell us about the current process.\n+ */\n+#if defined(GIT_WINDOWS_NATIVE)\n+void trace2_collect_process_info(void);\n+#else\n+#define trace2_collect_process_info() \\\n+\tdo {                          \\\n+\t} while (0)\n+#endif\n+\n #endif /* TRACE2_H */\n-- \ngitgitgadget\n\n"},{"id":"368196","messageId":"60b56d6a8fe9c0e37563f66809cc12c98c9eb21d.1548881779.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v4 01/14] trace2: Documentation/technical/api-trace2.txt","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-01-30T20:56:21Z","receivedAt":"2019-01-30T20:58:24Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreated design document for Trace2 feature.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Documentation/technical/api-trace2.txt | 1347 ++++++++++++++++++++++++\n 1 file changed, 1347 insertions(+)\n create mode 100644 Documentation/technical/api-trace2.txt\n\ndiff --git a/Documentation/technical/api-trace2.txt b/Documentation/technical/api-trace2.txt\nnew file mode 100644\nindex 0000000000..193518e55a\n--- /dev/null\n+++ b/Documentation/technical/api-trace2.txt\n@@ -0,0 +1,1347 @@\n+= Trace2 API\n+\n+The Trace2 API can be used to print debug, performance, and telemetry\n+information to stderr or a file.  The Trace2 feature is inactive unless\n+explicitly enabled by enabling one or more Trace2 Targets.\n+\n+The Trace2 API is intended to replace the existing (Trace1)\n+printf-style tracing provided by the existing `GIT_TRACE` and\n+`GIT_TRACE_PERFORMANCE` facilities.  During initial implementation,\n+Trace2 and Trace1 may operate in parallel.\n+\n+The Trace2 API defines a set of high-level messages with known fields,\n+such as (`start`: `argv`) and (`exit`: {`exit-code`, `elapsed-time`}).\n+\n+Trace2 instrumentation throughout the Git code base sends Trace2\n+messages to the enabled Trace2 Targets.  Targets transform these\n+messages content into purpose-specific formats and write events to\n+their data streams.  In this manner, the Trace2 API can drive\n+many different types of analysis.\n+\n+Targets are defined using a VTable allowing easy extension to other\n+formats in the future.  This might be used to define a binary format,\n+for example.\n+\n+== Trace2 Targets\n+\n+Trace2 defines the following set of Trace2 Targets.\n+Format details are given in a later section.\n+\n+`GIT_TR2` (NORMAL)::\n+\n+\ta simple printf format like GIT_TRACE.\n++\n+------------\n+$ export GIT_TR2=~/log.normal\n+$ git version\n+git version 2.20.1.155.g426c96fcdb\n+------------\n++\n+------------\n+$ cat ~/log.normal\n+12:28:42.620009 common-main.c:38                  version 2.20.1.155.g426c96fcdb\n+12:28:42.620989 common-main.c:39                  start git version\n+12:28:42.621101 git.c:432                         cmd_verb version (version)\n+12:28:42.621215 git.c:662                         exit elapsed:0.001227 code:0\n+12:28:42.621250 trace2/tr2_tgt_normal.c:124       atexit elapsed:0.001265 code:0\n+------------\n+\n+`GIT_TR2_PERF` (PERF)::\n+\n+\ta column-based format to replace GIT_TRACE_PERFORMANCE suitable for\n+\tdevelopment and testing, possibly to complement tools like gprof.\n++\n+------------\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git version\n+git version 2.20.1.155.g426c96fcdb\n+------------\n++\n+------------\n+$ cat ~/log.perf\n+12:28:42.620675 common-main.c:38                  | d0 | main                     | version      |     |           |           |            | 2.20.1.155.g426c96fcdb\n+12:28:42.621001 common-main.c:39                  | d0 | main                     | start        |     |           |           |            | git version\n+12:28:42.621111 git.c:432                         | d0 | main                     | cmd_verb     |     |           |           |            | version (version)\n+12:28:42.621225 git.c:662                         | d0 | main                     | exit         |     |  0.001227 |           |            | code:0\n+12:28:42.621259 trace2/tr2_tgt_perf.c:211         | d0 | main                     | atexit       |     |  0.001265 |           |            | code:0\n+------------\n+\n+`GIT_TR2_EVENT` (EVENT)::\n+\n+\ta JSON-based format of event data suitable for telemetry analysis.\n++\n+------------\n+$ export GIT_TR2_EVENT=~/log.event\n+$ git version\n+git version 2.20.1.155.g426c96fcdb\n+------------\n++\n+------------\n+$ cat ~/log.event\n+{\"event\":\"version\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.620713\",\"file\":\"common-main.c\",\"line\":38,\"evt\":\"1\",\"exe\":\"2.20.1.155.g426c96fcdb\"}\n+{\"event\":\"start\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621027\",\"file\":\"common-main.c\",\"line\":39,\"argv\":[\"git\",\"version\"]}\n+{\"event\":\"cmd_verb\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621122\",\"file\":\"git.c\",\"line\":432,\"name\":\"version\",\"hierarchy\":\"version\"}\n+{\"event\":\"exit\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621236\",\"file\":\"git.c\",\"line\":662,\"t_abs\":0.001227,\"code\":0}\n+{\"event\":\"atexit\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621268\",\"file\":\"trace2/tr2_tgt_event.c\",\"line\":163,\"t_abs\":0.001265,\"code\":0}\n+------------\n+\n+== Enabling a Target\n+\n+A Trace2 Target is enabled when the corresponding environment variable\n+(`GIT_TR2`, `GIT_TR2_PERF`, or `GIT_TR2_EVENT`) is set.  The following\n+values are recognized.\n+\n+`0`::\n+`false`::\n+\n+\tDisables the target.\n+\n+`1`::\n+`true`::\n+\n+\tEnables the target and writes stream to `STDERR`.\n+\n+`[2-9]`::\n+\n+\tEnables the target and writes to the already opened file descriptor.\n+\n+`<absolute-pathname>`::\n+\n+\tEnables the target, opens and writes to the file in append mode.\n+\n+`af_unix:<absolute-pathname>`::\n+\n+\tEnables the target, opens and writes to a Unix Domain Socket\n+\t(on platforms that support them).\n+\n+== Trace2 API\n+\n+All public Trace2 functions and macros are defined in `trace2.h` and\n+`trace2.c`.  All public symbols are prefixed with `trace2_`.\n+\n+There are no public Trace2 data structures.\n+\n+The Trace2 code also defines a set of private functions and data types\n+in the `trace2/` directory.  These symbols are prefixed with `tr2_`\n+and should only be used by functions in `trace2.c`.\n+\n+== Conventions for Public Functions and Macros\n+\n+The functions defined by the Trace2 API are declared and documented\n+in `trace2.h`.  It defines the API functions and wrapper macros for\n+Trace2.\n+\n+Some functions have a `_fl()` suffix to indicate that they take `file`\n+and `line-number` arguments.\n+\n+Some functions have a `_va_fl()` suffix to indicate that they also\n+take a `va_list` argument.\n+\n+Some functions have a `_printf_fl()` suffix to indicate that they also\n+take a varargs argument.\n+\n+There are CPP wrapper macros and ifdefs to hide most of these details.\n+See `trace2.h` for more details.  The following discussion will only\n+describe the simplified forms.\n+\n+== Public API\n+\n+All Trace2 API functions send a messsage to all of the active\n+Trace2 Targets.  This section describes the set of available\n+messages.\n+\n+It helps to divide these functions into groups for discussion\n+purposes.\n+\n+=== Basic Command Messages\n+\n+These are concerned with the lifetime of the overall git process.\n+\n+`void trace2_initialize()`::\n+\n+\tDetermines if any Trace2 Targets should be enabled and\n+\tinitializes the Trace2 facility.  This includes starting the\n+\telapsed time clocks and thread local storage (TLS).\n++\n+This function emits a \"version\" message containing the version of git\n+and the Trace2 protocol.\n++\n+This function should be called from `main()` as early as possible in\n+the life of the process.\n+\n+`int trace2_is_enabled()`::\n+\n+\tReturns 1 if Trace2 is enabled (at least one target is\n+\tactive).\n+\n+`void trace2_cmd_start(int argc, const char **argv)`::\n+\n+\tEmits a \"start\" message containing the process command line\n+\targuments.\n+\n+`int trace2_cmd_exit(int exit_code)`::\n+\n+\tEmits an \"exit\" message containing the process exit-code and\n+\telapsed time.\n++\n+Returns the exit-code.\n+\n+`void trace2_cmd_error(const char *fmt, va_list ap)`::\n+\n+\tEmits an \"error\" message containing a formatted error message.\n+\n+`void trace2_cmd_path(const char *pathname)`::\n+\n+\tEmits a \"cmd_path\" message with the full pathname of the\n+\tcurrent process.\n+\n+=== Command Detail Messages\n+\n+These are concerned with describing the specific Git command\n+after the command line, config, and environment are inspected.\n+\n+`void trace2_cmd_verb(const char *command_verb)`::\n+\n+\tEmits a \"cmd_verb\" message with the canonical name of\n+\t(usually) builtin command, for example \"status\" or \"checkout\".\n+\n+`void trace2_cmd_subverb(const char *command_subverb)`::\n+\n+\tEmits a \"cmd_subverb\" message with a qualifier name to further\n+\tdescribe the current git command.\n++\n+This message is intended to be used with git commands having multiple\n+major modes.  For example, a \"checkout\" command can checkout a new\n+branch or can checkout a single file, so the checkout code could\n+emit a subverb message of \"branch\" or \"file\".\n+\n+`void trace2_cmd_alias(const char *alias, const char **argv_expansion)`::\n+\n+\tEmits an \"alias\" message containing the alias used and the\n+\targument expansion.\n+\n+`void trace2_def_param(const char *parameter, const char *value)`::\n+\n+\tEmits a \"def_param\" message containing a key/value pair.\n++\n+This message is intended to report some global aspect of the current\n+command, such as a configuration setting or command line switch that\n+significantly affects program performance or behavior, such as\n+`core.abbrev`, `status.showUntrackedFiles`, or `--no-ahead-behind`.\n+\n+`void trace2_cmd_list_config()`::\n+\n+\tEmits a \"def_param\" messages for \"important\" configuration\n+\tsettings.\n++\n+The environment variable `GIT_TR2_CONFIG_PARAMS` can be set to a\n+list of patterns of important configuration settings, for example:\n+`core.*,remote.*.url`.  This function will iterate over all config\n+settings and emit a \"def_param\" message for each match.\n+\n+`void trace2_cmd_set_config(const char *key, const char *value)`::\n+\n+\tEmits a \"def_param\" message for a specific configuration\n+\tsetting IFF it matches the `GIT_TR2_CONFIG_PARAMS` pattern.\n++\n+This is used to hook into `git_config_set()` and catch any\n+configuration changes and update a value previously reported by\n+`trace2_cmd_list_config()`.\n+\n+`void trace2_def_repo(struct repository *repo)`::\n+\n+\tRegisters a repository with the Trace2 layer.  Assigns a\n+\tunique \"repo-id\" to `repo->trace2_repo_id`.\n++\n+Emits a \"worktree\" messages containing the repo-id and the worktree\n+pathname.\n++\n+Region and data messages (described later) may refer to this repo-id.\n++\n+The main/top-level repository will have repo-id value 1 (aka \"r1\").\n++\n+The repo-id field is in anticipation of future in-proc submodule\n+repositories.\n+\n+=== Child Process Messages\n+\n+These are concerned with the various spawned child processes,\n+including shell scripts, git commands, editors, pagers, and hooks.\n+\n+`void trace2_child_start(struct child_process *cmd)`::\n+\n+\tEmits a \"child_start\" message containing the \"child-id\",\n+\t\"child-argv\", and \"child-classification\".\n++\n+Before calling this, set `cmd->trace2_child_class` to a name\n+describing the type of child process, for example \"editor\".\n++\n+This function assigns a unique \"child-id\" to `cmd->trace2_child_id`.\n+This field is used later during the \"child_exit\" message to associate\n+it with the \"child_start\" message.\n++\n+This function should be called before spawning the child process.\n+\n+`void trace2_child_exit(struct child_proess *cmd, int child_exit_code)`::\n+\n+\tEmits a \"child_exit\" message containing the \"child-id\",\n+\tthe child's elapsed time and exit-code.\n++\n+The reported elapsed time includes the process creation overhead and\n+time spend waiting for it to exit, so it may be slightly longer than\n+the time reported by the child itself.\n++\n+This function should be called after reaping the child process.\n+\n+`int trace2_exec(const char *exe, const char **argv)`::\n+\n+\tEmits a \"exec\" message containing the \"exec-id\" and the\n+\targv of the new process.\n++\n+This function should be called before calling one of the `exec()`\n+variants, such as `execvp()`.\n++\n+This function returns a unique \"exec-id\".  This value is used later\n+if the exec() fails and a \"exec-result\" message is necessary.\n+\n+`void trace2_exec_result(int exec_id, int error_code)`::\n+\n+\tEmits a \"exec_result\" message containing the \"exec-id\"\n+\tand the error code.\n++\n+On Unix-based systems, `exec()` does not return if successful.\n+This message is used to indicate that the `exec()` failed and\n+that the current program is continuing.\n+\n+=== Git Thread Messages\n+\n+These messages are concerned with Git thread usage.\n+\n+`void trace2_thread_start(const char *thread_name)`::\n+\n+\tEmits a \"thread_start\" message.\n++\n+The `thread_name` field should be a descriptive name, such as the\n+unique name of the thread-proc.  A unique \"thread-id\" will be added\n+to the name to uniquely identify thread instances.\n++\n+Region and data messages (described later) may refer to this thread\n+name.\n++\n+This function must be called by the thread-proc of the new thread\n+(so that TLS data is properly initialized) and not by the caller\n+of `pthread_create()`.\n+\n+`void trace2_thread_exit()`::\n+\n+\tEmits a \"thread_exit\" message containing the thread name\n+\tand the thread elapsed time.\n++\n+This function must be called by the thread-proc before it returns\n+(so that the coorect TLS data is used and cleaned up.  It should\n+not be called by the caller of `pthread_join()`.\n+\n+=== Region and Data Messages\n+\n+These are concerned with recording performance data\n+over regions or spans of code.\n+\n+`void trace2_region_enter(const char *category, const char *label, const struct repository *repo)`::\n+   \n+`void trace2_region_enter_printf(const char *category, const char *label, const struct repository *repo, const char *fmt, ...)`::\n+\n+`void trace2_region_enter_printf_va(const char *category, const char *label, const struct repository *repo, const char *fmt, va_list ap)`::\n+\n+\tEmits a thread-relative \"region_enter\" message with optional\n+\tprintf string.\n++\n+This function pushes a new region nesting stack level on the current\n+thread and starts a clock for the new stack frame.\n++\n+The `category` field is an arbitrary category name used to classify\n+regions by feature area, such as \"status\" or \"index\".  At this time\n+it is only just printed along with the rest of the message.  It may\n+be used in the future to filter messages.\n++\n+The `label` field is an arbitrary label used to describe the activity\n+being started, such as \"read_recursive\" or \"do_read_index\".\n++\n+The `repo` field, if set, will be used to get the \"repo-id\", so that\n+recursive oerations can be attributed to the correct repository.\n+\n+`void trace2_region_leave(const char *category, const char *label, const struct repository *repo)`::\n+\n+`void trace2_region_leave_printf(const char *category, const char *label, const struct repository *repo, const char *fmt, ...)`::\n+\n+`void trace2_region_leave_printf_va(const char *category, const char *label, const struct repository *repo, const char *fmt, va_list ap)`::\n+\n+\tEmits a thread-relative \"region_leave\" message with optional\n+\tprintf string.\n++\n+This function pops the region nesting stack on the current thread\n+and reports the elapsed time of the stack frame.\n++\n+The `category`, `label`, and `repo` fields are the same as above.\n+The `category` and `label` do not need to match the correpsonding\n+\"region_enter\" message, but it makes the data stream easier to\n+understand.\n+\n+`void trace2_data_string(const char *category, const struct repository *repo, const char *key, const char * value)`::\n+\n+`void trace2_data_intmax(const char *category, const struct repository *repo, const char *key, intmax value)`::\n+\n+`void trace2_data_json(const char *category, const struct repository *repo, const char *key, const struct json_writer *jw)`::\n+\n+\tEmits a region- and thread-relative \"data\" or \"data_json\" message.\n++\n+This is a key/value pair message containing information about the\n+current thread, region stack, and repository.  This could be used\n+to print the number of files in a directory during a multi-threaded\n+recursive tree walk.\n+\n+`void trace2_printf(const char *fmt, ...)`::\n+\n+`void trace2_printf_va(const char *fmt, va_list ap)`::\n+\n+\tEmits a region- and thread-relative \"printf\" message.\n+\n+== Trace2 Target Formats\n+\n+=== NORMAL Format\n+\n+NORMAL format is enabled when the `GIT_TR2` environment variable is\n+set.\n+\n+Events are written as lines of the form:\n+\n+------------\n+[<time> SP <filename>:<line> SP+] <event-name> [[SP] <event-message>] LF\n+------------\n+\n+`<event-name>`::\n+\n+\tis the event name.\n+\n+`<event-message>`::\n+\tis a free-form printf message intended for human consumption.\n++\n+Note that this may contain embedded LF or CRLF characters that are\n+not escaped, so the event may spill across multiple lines.\n+\n+If `GIT_TR2_BRIEF` is true, the `time`, `filename`, and `line` fields\n+are omitted.\n+\n+This target is intended to be more of a summary (like GIT_TRACE) and\n+less detailed than the other targets.  It ignores thread, region, and\n+data messages, for example.\n+\n+=== PERF Format\n+\n+PERF format is enabled when the `GIT_TR2_PERF` environment variable\n+is set.\n+\n+Events are written as lines of the form:\n+\n+------------\n+[<time> SP <filename>:<line> SP+\n+    BAR SP] d<depth> SP\n+    BAR SP <thread-name> SP+\n+    BAR SP <event-name> SP+\n+    BAR SP [r<repo-id>] SP+\n+    BAR SP [<t_abs>] SP+\n+    BAR SP [<t_rel>] SP+\n+    BAR SP [<category>] SP+\n+    BAR SP DOTS* <perf-event-message>\n+    LF\n+------------\n+\n+`<depth>`::\n+\tis the git process depth.  This is the number of parent\n+\tgit processes.  A top-level git command has depth value \"d0\".\n+\tA child of it has depth value \"d1\".  A second level child\n+\thas depth value \"d2\" and so on.\n+\n+`<thread-name>`::\n+\tis a unique name for the thread.  The primary thread\n+\tis called \"main\".  Other thread names are of the form \"th%d:%s\"\n+\tand include a unique number and the name of the thread-proc.\n+\n+`<event-name>`::\n+\tis the event name.\n+\n+`<repo-id>`::\n+\twhen present, is a number indicating the repository\n+\tin use.  A `def_repo` event is emitted when a repository is\n+\topened.  This defines the repo-id and associated worktree.\n+\tSubsequent repo-specific events will reference this repo-id.\n++\n+Currently, this is always \"r1\" for the main repository.\n+This field is in anticipation of in-proc submodules in the future.\n+\n+`<t_abs>`::\n+\twhen present, is the absolute time in seconds since the\n+\tprogram started.\n+\n+`<t_rel>`::\n+\twhen present, is time in seconds relative to the start of\n+\tthe current region.  For a thread-exit event, it is the elapsed\n+\ttime of the thread.\n+\n+`<category>`::\n+\tis present on region and data events and is used to\n+\tindicate a broad category, such as \"index\" or \"status\".\n+\n+`<perf-event-message>`::\n+\tis a free-form printf message intended for human consumption.\n+\n+------------\n+15:33:33.532712 wt-status.c:2310                  | d0 | main                     | region_enter | r1  |  0.126064 |           | status     | label:print\n+15:33:33.532712 wt-status.c:2331                  | d0 | main                     | region_leave | r1  |  0.127568 |  0.001504 | status     | label:print\n+------------\n+\n+If `GIT_TR2_PERF_BRIEF` is true, the `time`, `file`, and `line`\n+fields are omitted.\n+\n+------------\n+d0 | main                     | region_leave | r1  |  0.011717 |  0.009122 | index      | label:preload\n+------------\n+\n+The PERF target is intended for interactive performance analysis\n+during development and is quite noisy.\n+\n+=== EVENT Format\n+\n+EVENT format is enabled when the `GIT_TR2_EVENT` environment\n+variable is set.\n+\n+Each event is a JSON-object containing multiple key/value pairs\n+written as a single line and followed by a LF.\n+\n+------------\n+'{' <key> ':' <value> [',' <key> ':' <value>]* '}' LF\n+------------\n+\n+Some key/value pairs are common to all events and some are\n+event-specific.\n+\n+==== Common Key/Value Pairs\n+\n+The following key/value pairs are common to all events:\n+\n+------------\n+{\n+\t\"event\":\"version\",\n+\t\"sid\":\"1547659722619736-11614\",\n+\t\"thread\":\"main\",\n+\t\"time\":\"2019-01-16 17:28:42.620713\",\n+\t\"file\":\"common-main.c\",\n+\t\"line\":38,\n+\t...\n+}\n+------------\n+\n+`\"event\":<event>`::\n+\tis the event name.\n+\n+`\"sid\":<sid>`::\n+\tis the session-id.  This is a unique string to identify the\n+\tprocess instance to allow all events emitted by a process to\n+\tbe identified.  A session-id is used instead of a PID because\n+\tPIDs are recycled by the OS.  For child git processes, the\n+\tsession-id is prepended with the session-id of the parent git\n+\tprocess to allow parent-child relationships to be identified\n+\tduring post-processing.\n+\n+`\"thread\":<thread>`::\n+\tis the thread name.\n+\n+`\"time\":<time>`::\n+\tis the UTC time of the event.\n+\n+`\"file\":<filename>`::\n+\tis source file generating the event.\n+\n+`\"line\":<line-number>`::\n+\tis the integer source line number generating the event.\n+\n+`\"repo\":<repo-id>`::\n+\twhen present, is the integer repo-id as described previously.\n+\n+If `GIT_TR2_EVENT_BRIEF` is true, the `file` and `line` fields are omitted\n+from all events and the `time` field is only present on the \"start\" and\n+\"atexit\" events.\n+\n+==== Event-Specific Key/Value Pairs\n+\n+`\"version\"`::\n+\tThis event gives the version of the executable and the EVENT format.\n++\n+------------\n+{\n+\t\"event\":\"version\",\n+\t...\n+\t\"evt\":\"1\",\t\t       # EVENT format version\n+\t\"exe\":\"2.20.1.155.g426c96fcdb\" # git version\n+}\n+------------\n+\n+`\"start\"`::\n+\tThis event contains the complete argv received by main().\n++\n+------------\n+{\n+\t\"event\":\"start\",\n+\t...\n+\t\"argv\":[\"git\",\"version\"]\n+}\n+------------\n+\n+`\"exit\"`::\n+\tThis event is emitted when git calls `exit()`.\n++\n+------------\n+{\n+\t\"event\":\"exit\",\n+\t...\n+\t\"t_abs\":0.001227, # elapsed time in seconds\n+\t\"code\":0\t  # exit code\n+}\n+------------\n+\n+`\"atexit\"`::\n+\tThis event is emitted by the Trace2 `atexit` routine during\n+\tfinal shutdown.  It should be the last event emitted by the\n+\tprocess.\n++\n+(The elapsed time reported here is greater than the time reported in\n+the \"exit\" event because it runs after all other atexit tasks have\n+completed.)\n++\n+------------\n+{\n+\t\"event\":\"atexit\",\n+\t...\n+\t\"t_abs\":0.001227, # elapsed time in seconds\n+\t\"code\":0          # exit code\n+}\n+------------\n+\n+`\"signal\"`::\n+\tThis event is emitted when the program is terminated by a user\n+\tsignal.  Depending on the platform, the signal event may\n+\tprevent the \"atexit\" event from being generated.\n++\n+------------\n+{\n+\t\"event\":\"signal\",\n+\t...\n+\t\"t_abs\":0.001227,  # elapsed time in seconds\n+\t\"signal\":13        # SIGTERM, SIGINT, etc.\n+}\n+------------\n+\n+`\"error\"`::\n+\tThis event is emitted when one of the `error()`, `die()`,\n+\tor `usage()` functions are called.\n++\n+------------\n+{\n+\t\"event\":\"error\",\n+\t...\n+\t\"msg\":\"invalid option: --cahced\", # formatted error message\n+\t\"fmt\":\"invalid option: %s\"\t  # error format string\n+}\n+------------\n++\n+The error event may be emitted more than once.  The format string\n+allows post-processors to group errors by type without worrying\n+about specific error arguments.\n+\n+`\"cmd_path\"`::\n+\tThis event contains the discovered full path of the git\n+\texecutable (on platforms that are configured to resolve it).\n++\n+------------\n+{\n+\t\"event\":\"cmd_path\",\n+\t...\n+\t\"path\":\"C:/work/gfw/git.exe\"\n+}\n+------------\n+\n+`\"cmd_verb\"`::\n+\tThis event contains the primary verb for this git process\n+\tand the hierarchy of verbs from parent git processes.\n++\n+------------\n+{\n+\t\"event\":\"cmd_verb\",\n+\t...\n+\t\"name\":\"pack-objects\",\n+\t\"hierarchy\":\"push/pack-objects\"\n+}\n+------------\n++\n+Normally, the \"name\" field contains the canonical name of the\n+command.  When a canonical verb is not available, one of\n+these special values are used:\n++\n+------------\n+\"_query_\"            # \"git --html-path\"\n+\"_run_dashed_\"       # when \"git foo\" tries to run \"git-foo\"\n+\"_run_shell_alias_\"  # alias expansion to a shell command\n+\"_run_git_alias_\"    # alias expansion to a git command\n+\"_usage_\"            # usage error\n+------------\n+\n+`\"cmd_subverb\"`::\n+\tThis event, when present, describes the variant of the main\n+\tverb.  This event may be emitted more than once.\n++\n+------------\n+{\n+\t\"event\":\"cmd_subverb\",\n+\t...\n+\t\"name\":\"branch\"\n+}\n+------------\n++\n+The \"name\" field is an arbitrary string that makes sense in the\n+context of the primary verb. For example, checkout can checkout a\n+branch or an individual file.  And these variations typically have\n+different performance characteristics that are not comparable.\n+\n+`\"alias\"`::\n+\tThis event is present when an alias is expanded.\n++\n+------------\n+{\n+\t\"event\":\"alias\",\n+\t...\n+\t\"alias\":\"l\",\t\t # registered alias\n+\t\"argv\":[\"log\",\"--graph\"] # alias expansion\n+}\n+------------\n+\n+`\"child_start\"`::\n+\tThis event describes a child process that is about to be\n+\tspawned.\n++\n+------------\n+{\n+\t\"event\":\"child_start\",\n+\t...\n+\t\"child_id\":2,\n+\t\"child_class\":\"?\",\n+\t\"use_shell\":false,\n+\t\"argv\":[\"git\",\"rev-list\",\"--objects\",\"--stdin\",\"--not\",\"--all\",\"--quiet\"]\n+\n+\t\"hook_name\":\"<hook_name>\"  # present when child_class is \"hook\"\n+\t\"cd\":\"<path>\"\t\t   # present when cd is required\n+}\n+------------\n++\n+The \"child_id\" field can be used to match this child_start with the\n+corresponding child_exit event.\n++\n+The \"child_class\" field is a rough classification, such as \"editor\",\n+\"pager\", \"transport/*\", and \"hook\".  Unclassified children are classified\n+with \"?\".\n+\n+`\"child_exit\"`::\n+\tThis event is generated after the current process has returned\n+\tfrom the waitpid() and collected the exit information from the\n+\tchild.\n++\n+------------\n+{\n+\t\"event\":\"child_exit\",\n+\t...\n+\t\"child_id\":2,\n+\t\"pid\":14708,\t # child PID\n+\t\"code\":0,\t # child exit-code\n+\t\"t_rel\":0.110605 # observed run-time of child process\n+}\n+------------\n++\n+Note that the session-id of the child process is not available to\n+the current/spawning process, so the child's PID is reported here as\n+a hint for post-processing.  (But it is only a hint because the child\n+proces may be a shell script which doesn't have a session-id.)\n++\n+Note that the `t_rel` field contains the observed run time in seconds\n+for the child process (starting before the fork/exec/spawn and\n+stopping after the waitpid() and includes OS process creation overhead).\n+So this time will be slightly larger than the atexit time reported by\n+the child process itself.\n+\n+`\"exec\"`::\n+\tThis event is generated before git attempts to `exec()`\n+\tanother command rather than starting a child process.\n++\n+------------\n+{\n+\t\"event\":\"exec\",\n+\t...\n+\t\"exec_id\":0,\n+\t\"exe\":\"git\",\n+\t\"argv\":[\"foo\", \"bar\"]\n+}\n+------------\n++\n+The \"exec_id\" field is a command-unique id and is only useful if the\n+`exec()` fails and a corresponding exec_result event is generated.\n+\n+`\"exec_result\"`::\n+\tThis event is generated if the `exec()` fails and control\n+\treturns to the current git command.\n++\n+------------\n+{\n+\t\"event\":\"exec_result\",\n+\t...\n+\t\"exec_id\":0,\n+\t\"code\":1      # error code (errno) from exec()\n+}\n+------------\n+\n+`\"thread_start\"`::\n+\tThis event is generated when a thread is started.  It is\n+\tgenerated from *within* the new thread's thread-proc (for TLS\n+\treasons).\n++\n+------------\n+{\n+\t\"event\":\"thread_start\",\n+\t...\n+\t\"thread\":\"th02:preload_thread\" # thread name\n+}\n+------------\n+\n+`\"thread_exit\"`::\n+\tThis event is generated when a thread exits.  It is generated\n+\tfrom *within* the thread's thread-proc (for TLS reasons).\n++\n+------------\n+{\n+\t\"event\":\"thread_exit\",\n+\t...\n+\t\"thread\":\"th02:preload_thread\", # thread name\n+\t\"t_rel\":0.007328                # thread elapsed time\n+}\n+------------\n+\n+`\"def_param\"`::\n+\tThis event is generated to log a global parameter.\n++\n+------------\n+{\n+\t\"event\":\"def_param\",\n+\t...\n+\t\"param\":\"core.abbrev\",\n+\t\"value\":\"7\"\n+}\n+------------\n+\n+`\"def_repo\"`::\n+\tThis event defines a repo-id and associates it with the root\n+\tof the worktree.\n++\n+------------\n+{\n+\t\"event\":\"def_repo\",\n+\t...\n+\t\"repo\":1,\n+\t\"worktree\":\"/Users/jeffhost/work/gfw\"\n+}\n+------------\n++\n+As stated earlier, the repo-id is currently always 1, so there will\n+only be one def_repo event.  Later, if in-proc submodules are\n+supported, a def_repo event should be emitted for each submodule\n+visited.\n+\n+`\"region_enter\"`::\n+\tThis event is generated when entering a region.\n++\n+------------\n+{\n+\t\"event\":\"region_enter\",\n+\t...\n+\t\"repo\":1,                # optional\n+\t\"nesting\":1,             # current region stack depth\n+\t\"category\":\"index\",      # optional\n+\t\"label\":\"do_read_index\", # optional\n+\t\"msg\":\".git/index\"       # optional\n+}\n+------------\n++\n+The `category` field may be used in a future enhancement to\n+do category-based filtering.\n++\n+The `GIT_TR2_EVENT_NESTING` environment variable can be used to\n+filter deeply nested regions and data events.  It defaults to \"2\".\n+\n+`\"region_leave\"`::\n+\tThis event is generated when leaving a region.\n++\n+------------\n+{\n+\t\"event\":\"region_leave\",\n+\t...\n+\t\"repo\":1,                # optional\n+\t\"t_rel\":0.002876,        # time spent in region in seconds\n+\t\"nesting\":1,             # region stack depth\n+\t\"category\":\"index\",      # optional\n+\t\"label\":\"do_read_index\", # optional\n+\t\"msg\":\".git/index\"       # optional\n+}\n+------------\n+\n+`\"data\"`::\n+\tThis event is generated to log a thread- and region-local\n+\tkey/value pair.\n++\n+------------\n+{\n+\t\"event\":\"data\",\n+\t...\n+\t\"repo\":1,              # optional\n+\t\"t_abs\":0.024107,      # absolute elapsed time\n+\t\"t_rel\":0.001031,      # elapsed time in region/thread\n+\t\"nesting\":2,           # region stack depth\n+\t\"category\":\"index\",\n+\t\"key\":\"read/cache_nr\",\n+\t\"value\":\"3552\"\n+}\n+------------\n++\n+The \"value\" field may be an integer or a string.\n+\n+`\"data-json\"`::\n+\tThis event is generated to log a pre-formatted JSON string\n+\tcontaining structured data.\n++\n+------------\n+{\n+\t\"event\":\"data_json\",\n+\t...\n+\t\"repo\":1,              # optional\n+\t\"t_abs\":0.015905,\n+\t\"t_rel\":0.015905,\n+\t\"nesting\":1,\n+\t\"category\":\"process\",\n+\t\"key\":\"windows/ancestry\",\n+\t\"value\":[\"bash.exe\",\"bash.exe\"]\n+}\n+------------\n+\n+== Example Trace2 API Usage\n+\n+Here is a hypothetical usage of the Trace2 API showing the intended\n+usage (without worrying about the actual Git details).\n+\n+Initialization::\n+\n+\tInitialization happens in `main()`.  Behind the scenes, an\n+\t`atexit` and `signal` handler are registered.\n++\n+----------------\n+int main(int argc, const char **argv)\n+{\n+\tint exit_code;\n+\n+\ttrace2_initialize();\n+\ttrace2_cmd_start(argv);\n+\n+\texit_code = cmd_main(argc, argv);\n+\n+\ttrace2_cmd_exit(exit_code);\n+\n+\treturn exit_code;\n+}\n+----------------\n+\n+Command Details::\n+\n+\tAfter the basics are established, additional command\n+\tinformation can be sent to Trace2 as it is discovered.\n++\n+----------------\n+int cmd_checkout(int argc, const char **argv)\n+{\n+\ttrace2_cmd_verb(\"checkout\");\n+\ttrace2_cmd_subverb(\"branch\");\n+\ttrace2_def_repo(the_repository);\n+\n+\t// emit \"def_param\" messages for \"interesting\" config settings.\n+\ttrace2_cmd_list_config();\n+\n+\tif (do_something())\n+\t    trace2_cmd_error(\"Path '%s': cannot do something\", path);\n+\n+\treturn 0;\n+}\n+----------------\n+\n+Child Processes::\n+\n+\tWrap code spawning child processes.\n++\n+----------------\n+void run_child(...)\n+{\n+\tint child_exit_code;\n+\tstruct child_process cmd = CHILD_PROCESS_INIT;\n+\t...\n+\tcmd.trace2_child_class = \"editor\";\n+\n+\ttrace2_child_start(&cmd);\n+\tchild_exit_code = spawn_child_and_wait_for_it();\n+\ttrace2_child_exit(&cmd, child_exit_code);\n+}\n+----------------\n++\n+For example, the following fetch command spawned ssh, index-pack,\n+rev-list, and gc.  This example also shows that fetch took\n+5.199 seconds and of that 4.932 was in ssh.\n++\n+----------------\n+$ export GIT_TR2_BRIEF=1\n+$ export GIT_TR2=~/log.normal\n+$ git fetch origin\n+...\n+----------------\n++\n+----------------\n+$ cat ~/log.normal\n+version 2.20.1.vfs.1.1.47.g534dbe1ad1\n+start git fetch origin\n+worktree /Users/jeffhost/work/gfw\n+cmd_verb fetch (fetch)\n+child_start[0] ssh git@github.com ...\n+child_start[1] git index-pack ...\n+... (Trace2 events from child processes omitted)\n+child_exit[1] pid:14707 code:0 elapsed:0.076353\n+child_exit[0] pid:14706 code:0 elapsed:4.931869\n+child_start[2] git rev-list ...\n+... (Trace2 events from child process omitted)\n+child_exit[2] pid:14708 code:0 elapsed:0.110605\n+child_start[3] git gc --auto\n+... (Trace2 events from child process omitted)\n+child_exit[3] pid:14709 code:0 elapsed:0.006240\n+exit elapsed:5.198503 code:0\n+atexit elapsed:5.198541 code:0\n+----------------\n++\n+When a git process is a (direct or indirect) child of another\n+git process, it inherits Trace2 context information.  This\n+allows the child to print a verb hierarchy.  This example\n+shows gc as child[3] of fetch.  When the gc process reports\n+its verb as \"gc\", it also reports the hierarchy as \"fetch/gc\".\n+(In this example, trace2 messages from the child process is\n+indented for clarity.)\n++\n+----------------\n+$ export GIT_TR2_BRIEF=1\n+$ export GIT_TR2=~/log.normal\n+$ git fetch origin\n+...\n+----------------\n++\n+----------------\n+$ cat ~/log.normal\n+version 2.20.1.160.g5676107ecd.dirty\n+start git fetch official\n+worktree /Users/jeffhost/work/gfw\n+cmd_verb fetch (fetch)\n+...\n+child_start[3] git gc --auto\n+    version 2.20.1.160.g5676107ecd.dirty\n+    start /Users/jeffhost/work/gfw/git gc --auto\n+    worktree /Users/jeffhost/work/gfw\n+    cmd_verb gc (fetch/gc)\n+    exit elapsed:0.001959 code:0\n+    atexit elapsed:0.001997 code:0\n+child_exit[3] pid:20303 code:0 elapsed:0.007564\n+exit elapsed:3.868938 code:0\n+atexit elapsed:3.868970 code:0\n+----------------\n+\n+Regions::\n+\n+\tRegions can be use to time an interesting section of code.\n++\n+----------------\n+void wt_status_collect(struct wt_status *s)\n+{\n+\ttrace2_region_enter(\"status\", \"worktrees\", s->repo);\n+\twt_status_collect_changes_worktree(s);\n+\ttrace2_region_leave(\"status\", \"worktrees\", s->repo);\n+\n+\ttrace2_region_enter(\"status\", \"index\", s->repo);\n+\twt_status_collect_changes_index(s);\n+\ttrace2_region_leave(\"status\", \"index\", s->repo);\n+\n+\ttrace2_region_enter(\"status\", \"untracked\", s->repo);\n+\twt_status_collect_untracked(s);\n+\ttrace2_region_leave(\"status\", \"untracked\", s->repo);\n+}\n+\n+void wt_status_print(struct wt_status *s)\n+{\n+\ttrace2_region_enter(\"status\", \"print\", s->repo);\n+\tswitch (s->status_format) {\n+\t    ...\n+\t}\n+\ttrace2_region_leave(\"status\", \"print\", s->repo);\n+}\n+----------------\n++\n+In this example, scanning for untracked files ran from +0.012568 to\n++0.027149 (since the process started) and took 0.014581 seconds.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+\n+$ cat ~/log.perf\n+d0 | main                     | version      |     |           |           |            | 2.20.1.160.g5676107ecd.dirty\n+d0 | main                     | start        |     |           |           |            | git status\n+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n+d0 | main                     | cmd_verb     |     |           |           |            | status (status)\n+...\n+d0 | main                     | region_enter | r1  |  0.010988 |           | status     | label:worktrees\n+d0 | main                     | region_leave | r1  |  0.011236 |  0.000248 | status     | label:worktrees\n+d0 | main                     | region_enter | r1  |  0.011260 |           | status     | label:index\n+d0 | main                     | region_leave | r1  |  0.012542 |  0.001282 | status     | label:index\n+d0 | main                     | region_enter | r1  |  0.012568 |           | status     | label:untracked\n+d0 | main                     | region_leave | r1  |  0.027149 |  0.014581 | status     | label:untracked\n+d0 | main                     | region_enter | r1  |  0.027411 |           | status     | label:print\n+d0 | main                     | region_leave | r1  |  0.028741 |  0.001330 | status     | label:print\n+d0 | main                     | exit         |     |  0.028778 |           |            | code:0\n+d0 | main                     | atexit       |     |  0.028809 |           |            | code:0\n+----------------\n++\n+Regions may be nested.  This causes messages to be indented in the\n+PERF target, for example.\n+Elapsed times are relative to the start of the correpsonding nesting\n+level as expected.  For example, if we add region message to:\n++\n+----------------\n+static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n+\tstruct index_state *istate, const char *base, int baselen,\n+\tstruct untracked_cache_dir *untracked, int check_only,\n+\tint stop_at_first_file, const struct pathspec *pathspec)\n+{\n+\tenum path_treatment state, subdir_state, dir_state = path_none;\n+\n+\ttrace2_region_enter_printf(\"dir\", \"read_recursive\", NULL, \"%.*s\", baselen, base);\n+\t...\n+\ttrace2_region_leave_printf(\"dir\", \"read_recursive\", NULL, \"%.*s\", baselen, base);\n+\treturn dir_state;\n+}\n+----------------\n++\n+We can further investigate the time spent scanning for untracked files.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+$ cat ~/log.perf\n+d0 | main                     | version      |     |           |           |            | 2.20.1.162.gb4ccea44db.dirty\n+d0 | main                     | start        |     |           |           |            | git status\n+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n+d0 | main                     | cmd_verb     |     |           |           |            | status (status)\n+...\n+d0 | main                     | region_enter | r1  |  0.015047 |           | status     | label:untracked\n+d0 | main                     | region_enter |     |  0.015132 |           | dir        | ..label:read_recursive\n+d0 | main                     | region_enter |     |  0.016341 |           | dir        | ....label:read_recursive vcs-svn/\n+d0 | main                     | region_leave |     |  0.016422 |  0.000081 | dir        | ....label:read_recursive vcs-svn/\n+d0 | main                     | region_enter |     |  0.016446 |           | dir        | ....label:read_recursive xdiff/\n+d0 | main                     | region_leave |     |  0.016522 |  0.000076 | dir        | ....label:read_recursive xdiff/\n+d0 | main                     | region_enter |     |  0.016612 |           | dir        | ....label:read_recursive git-gui/\n+d0 | main                     | region_enter |     |  0.016698 |           | dir        | ......label:read_recursive git-gui/po/\n+d0 | main                     | region_enter |     |  0.016810 |           | dir        | ........label:read_recursive git-gui/po/glossary/\n+d0 | main                     | region_leave |     |  0.016863 |  0.000053 | dir        | ........label:read_recursive git-gui/po/glossary/\n+...\n+d0 | main                     | region_enter |     |  0.031876 |           | dir        | ....label:read_recursive builtin/\n+d0 | main                     | region_leave |     |  0.032270 |  0.000394 | dir        | ....label:read_recursive builtin/\n+d0 | main                     | region_leave |     |  0.032414 |  0.017282 | dir        | ..label:read_recursive\n+d0 | main                     | region_leave | r1  |  0.032454 |  0.017407 | status     | label:untracked\n+...\n+d0 | main                     | exit         |     |  0.034279 |           |            | code:0\n+d0 | main                     | atexit       |     |  0.034322 |           |            | code:0\n+----------------\n++\n+Trace2 regions are similar to the existing trace_performance_enter()\n+and trace_performance_leave() routines, but are thread safe and\n+maintain per-thread stacks of timers.\n+\n+Data Messages::\n+\n+\tData messages added to a region.\n++\n+----------------\n+int read_index_from(struct index_state *istate, const char *path,\n+\tconst char *gitdir)\n+{\n+\ttrace2_region_enter_printf(\"index\", \"do_read_index\", the_repository, \"%s\", path);\n+\n+\t...\n+\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/version\", istate->version);\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/cache_nr\", istate->cache_nr);\n+\n+\ttrace2_region_leave_printf(\"index\", \"do_read_index\", the_repository, \"%s\", path);\n+}\n+----------------\n++\n+This example shows that the index contained 3552 entries.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+$ cat ~/log.perf\n+d0 | main                     | version      |     |           |           |            | 2.20.1.156.gf9916ae094.dirty\n+d0 | main                     | start        |     |           |           |            | git status\n+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n+d0 | main                     | cmd_verb     |     |           |           |            | status (status)\n+d0 | main                     | region_enter | r1  |  0.001791 |           | index      | label:do_read_index .git/index\n+d0 | main                     | data         | r1  |  0.002494 |  0.000703 | index      | ..read/version:2\n+d0 | main                     | data         | r1  |  0.002520 |  0.000729 | index      | ..read/cache_nr:3552\n+d0 | main                     | region_leave | r1  |  0.002539 |  0.000748 | index      | label:do_read_index .git/index\n+...\n+----------------\n+\n+Thread Events::\n+\n+\tThread messages added to a thread-proc.\n++\n+For example, the multithreaded preload-index code can be\n+instrumented with a region around the thread pool and then\n+per-thread start and exit events within the threadproc.\n++\n+----------------\n+static void *preload_thread(void *_data)\n+{\n+\t// start the per-thread clock and emit a message.\n+\ttrace2_thread_start(\"preload_thread\");\n+\n+\t// report which chunk of the array this thread was assigned.\n+\ttrace2_data_intmax(\"index\", the_repository, \"offset\", p->offset);\n+\ttrace2_data_intmax(\"index\", the_repository, \"count\", nr);\n+\n+\tdo {\n+\t    ...\n+\t} while (--nr > 0);\n+\t...\n+\n+\t// report elapsed time taken by this thread.\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+void preload_index(struct index_state *index,\n+\tconst struct pathspec *pathspec,\n+\tunsigned int refresh_flags)\n+{\n+\ttrace2_region_enter(\"index\", \"preload\", the_repository);\n+\n+\tfor (i = 0; i < threads; i++) {\n+\t    ... /* create thread */\n+\t}\n+\n+\tfor (i = 0; i < threads; i++) {\n+\t    ... /* join thread */\n+\t}\n+\n+\ttrace2_region_leave(\"index\", \"preload\", the_repository);\n+}\n+----------------\n++\n+In this example preload_index() was executed by the `main` thread\n+and started the `preload` region.  Seven threads, named\n+`th01:preload_thread` through `th07:preload_thread`, were started.\n+Events from each thread are atomically appended to the shared target\n+stream as they occur so they may appear in random order with respect\n+other threads. Finally, the main thread waits for the threads to\n+finish and leaves the region.\n++\n+Data events are tagged with the active thread name.  They are used\n+to report the per-thread parameters.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+$ cat ~/log.perf\n+...\n+d0 | main                     | region_enter | r1  |  0.002595 |           | index      | label:preload\n+d0 | th01:preload_thread      | thread_start |     |  0.002699 |           |            |\n+d0 | th02:preload_thread      | thread_start |     |  0.002721 |           |            |\n+d0 | th01:preload_thread      | data         | r1  |  0.002736 |  0.000037 | index      | offset:0\n+d0 | th02:preload_thread      | data         | r1  |  0.002751 |  0.000030 | index      | offset:2032\n+d0 | th03:preload_thread      | thread_start |     |  0.002711 |           |            |\n+d0 | th06:preload_thread      | thread_start |     |  0.002739 |           |            |\n+d0 | th01:preload_thread      | data         | r1  |  0.002766 |  0.000067 | index      | count:508\n+d0 | th06:preload_thread      | data         | r1  |  0.002856 |  0.000117 | index      | offset:2540\n+d0 | th03:preload_thread      | data         | r1  |  0.002824 |  0.000113 | index      | offset:1016\n+d0 | th04:preload_thread      | thread_start |     |  0.002710 |           |            |\n+d0 | th02:preload_thread      | data         | r1  |  0.002779 |  0.000058 | index      | count:508\n+d0 | th06:preload_thread      | data         | r1  |  0.002966 |  0.000227 | index      | count:508\n+d0 | th07:preload_thread      | thread_start |     |  0.002741 |           |            |\n+d0 | th07:preload_thread      | data         | r1  |  0.003017 |  0.000276 | index      | offset:3048\n+d0 | th05:preload_thread      | thread_start |     |  0.002712 |           |            |\n+d0 | th05:preload_thread      | data         | r1  |  0.003067 |  0.000355 | index      | offset:1524\n+d0 | th05:preload_thread      | data         | r1  |  0.003090 |  0.000378 | index      | count:508\n+d0 | th07:preload_thread      | data         | r1  |  0.003037 |  0.000296 | index      | count:504\n+d0 | th03:preload_thread      | data         | r1  |  0.002971 |  0.000260 | index      | count:508\n+d0 | th04:preload_thread      | data         | r1  |  0.002983 |  0.000273 | index      | offset:508\n+d0 | th04:preload_thread      | data         | r1  |  0.007311 |  0.004601 | index      | count:508\n+d0 | th05:preload_thread      | thread_exit  |     |  0.008781 |  0.006069 |            |\n+d0 | th01:preload_thread      | thread_exit  |     |  0.009561 |  0.006862 |            |\n+d0 | th03:preload_thread      | thread_exit  |     |  0.009742 |  0.007031 |            |\n+d0 | th06:preload_thread      | thread_exit  |     |  0.009820 |  0.007081 |            |\n+d0 | th02:preload_thread      | thread_exit  |     |  0.010274 |  0.007553 |            |\n+d0 | th07:preload_thread      | thread_exit  |     |  0.010477 |  0.007736 |            |\n+d0 | th04:preload_thread      | thread_exit  |     |  0.011657 |  0.008947 |            |\n+d0 | main                     | region_leave | r1  |  0.011717 |  0.009122 | index      | label:preload\n+...\n+d0 | main                     | exit         |     |  0.029996 |           |            | code:0\n+d0 | main                     | atexit       |     |  0.030027 |           |            | code:0\n+----------------\n++\n+In this example, the preload region took 0.009122 seconds.  The 7 threads\n+took between 0.006069 and 0.008947 seconds to work on their portion of\n+the index.  Thread \"th01\" worked on 508 items at offset 0.  Thread \"th02\"\n+worked on 508 items at offset 2032.  Thread \"th04\" worked on 508 itemts\n+at offset 508.\n++\n+This example also shows that thread names are assigned in a racy manner\n+as each thread starts and allocates TLS storage.\n+\n+== Future Work\n+\n+=== Relationship to the Existing Trace Api (api-trace.txt)\n+\n+There are a few issues to resolve before we can completely\n+switch to Trace2.\n+\n+* Updating existing tests that assume GIT_TRACE format messages.\n+\n+* How to best handle custom GIT_TRACE_<key> messages?\n+\n+** The GIT_TRACE_<key> mechanism allows each <key> to write to a\n+different file (in addition to just stderr).\n+\n+** Do we want to maintain that ability or simply write to the existing\n+Trace2 targets (and convert <key> to a \"category\").\n+\n-- \ngitgitgadget\n\n"},{"id":"368238","messageId":"20190131141127.GA10587@szeder.dev","threadId":"50293","inReplyTo":"60b56d6a8fe9c0e37563f66809cc12c98c9eb21d.1548881779.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 01/14] trace2: Documentation/technical/api-trace2.txt","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2019-01-31T14:11:27Z","receivedAt":"2019-01-31T14:11:39Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Wed, Jan 30, 2019 at 12:56:21PM -0800, Jeff Hostetler via GitGitGadget wrote:\n> From: Jeff Hostetler <jeffhost@microsoft.com>\n\n> diff --git a/Documentation/technical/api-trace2.txt b/Documentation/technical/api-trace2.txt\n\n> +== Public API\n\n> +=== Command Detail Messages\n> +\n> +These are concerned with describing the specific Git command\n> +after the command line, config, and environment are inspected.\n> +\n> +`void trace2_cmd_verb(const char *command_verb)`::\n> +\n> +\tEmits a \"cmd_verb\" message with the canonical name of\n> +\t(usually) builtin command, for example \"status\" or \"checkout\".\n> +\n> +`void trace2_cmd_subverb(const char *command_subverb)`::\n> +\n> +\tEmits a \"cmd_subverb\" message with a qualifier name to further\n> +\tdescribe the current git command.\n\nOK, so now we know what is meant by 'verb' and 'subverb'.\n\nAlas, this explanation also clearly shows that the word 'verb' is a\npoor fit for what it tries to convey, and 'subverb' is even worse.\n\nNow, I have already noticed that contributors with @microsoft.com\nemail addresses for some reason tend to be fond of the word 'verb' ;),\nand try to use it instead of perfectly adequate and well-established\nGit terminology, see e.g. the early versions of the multi-pack-index\npatch series:\n\n  https://public-inbox.org/git/20181211015957.GR30222@szeder.dev/\n\nThose commit messages and documentations were improved after all\noccurrances of the word \"verb\" got replaced by the established Git\nterms.\n\nI'm pretty sure that this patch series would vastly benefit from\neliminating all \"verb\"s (and \"subverb\"s!) in the code, docs and commit\nmessages as well.\n\n"},{"id":"368240","messageId":"20190131143811.GB10587@szeder.dev","threadId":"50293","inReplyTo":"pull.108.v3.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 00/14] Trace2 tracing facility","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2019-01-31T14:38:11Z","receivedAt":"2019-01-31T14:38:17Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Wed, Jan 30, 2019 at 11:51:04AM -0800, Jeff Hostetler via GitGitGadget wrote:\n> V3 addresses:\n> [] incorporate most of the suggestions from clang-format.\n\n> Range-diff vs v2:\n> \n>   1:  1a90de9dab <  -:  ---------- trace2: Documentation/technical/api-trace2.txt\n>   -:  ---------- >  1:  60b56d6a8f trace2: Documentation/technical/api-trace2.txt\n>   2:  ea39b76d31 !  2:  bfe2fe0bbe trace2: create new combined trace facility\n\n>      @@ -745,7 +734,8 @@\n>       +\tint j;\n>       +\tint sum = 0;\n>       +\n>      -+\tfor_each_builtin(j, tgt_j) {\n>      ++\tfor_each_builtin(j, tgt_j)\n>      ++\t{\n>       +\t\tif (tgt_j->pfn_init())\n>       +\t\t\tsum++;\n>       +\t}\n>      @@ -763,7 +753,8 @@\n>       +\tstruct tr2_tgt *tgt_j;\n>       +\tint j;\n>       +\n>      -+\tfor_each_builtin(j, tgt_j) {\n>      ++\tfor_each_builtin(j, tgt_j)\n>      ++\t{\n>       +\t\ttgt_j->pfn_term();\n>       +\t}\n\nOur CodingGuidelines prefer the opening brace on the same line after\nthe if/for/while/struct/etc. statement, and even omitting the braces\nif the if arm or loop body consists of a single statement.  So\nunfortunately a considerable part of this range diff goes in the wrong\ndirection.\n\n"},{"id":"368242","messageId":"9b603fd7-d8f0-1ce7-7c7c-8b17484d7d0a@jeffhostetler.com","threadId":"50293","inReplyTo":"20190131143811.GB10587@szeder.dev","subject":"Re: [PATCH v3 00/14] Trace2 tracing facility","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2019-01-31T14:53:29Z","receivedAt":"2019-01-31T14:53:31Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 1/31/2019 9:38 AM, SZEDER Gábor wrote:\n> On Wed, Jan 30, 2019 at 11:51:04AM -0800, Jeff Hostetler via GitGitGadget wrote:\n>> V3 addresses:\n>> [] incorporate most of the suggestions from clang-format.\n> \n>> Range-diff vs v2:\n>>\n>>    1:  1a90de9dab <  -:  ---------- trace2: Documentation/technical/api-trace2.txt\n>>    -:  ---------- >  1:  60b56d6a8f trace2: Documentation/technical/api-trace2.txt\n>>    2:  ea39b76d31 !  2:  bfe2fe0bbe trace2: create new combined trace facility\n> \n>>       @@ -745,7 +734,8 @@\n>>        +\tint j;\n>>        +\tint sum = 0;\n>>        +\n>>       -+\tfor_each_builtin(j, tgt_j) {\n>>       ++\tfor_each_builtin(j, tgt_j)\n>>       ++\t{\n>>        +\t\tif (tgt_j->pfn_init())\n>>        +\t\t\tsum++;\n>>        +\t}\n>>       @@ -763,7 +753,8 @@\n>>        +\tstruct tr2_tgt *tgt_j;\n>>        +\tint j;\n>>        +\n>>       -+\tfor_each_builtin(j, tgt_j) {\n>>       ++\tfor_each_builtin(j, tgt_j)\n>>       ++\t{\n>>        +\t\ttgt_j->pfn_term();\n>>        +\t}\n> \n> Our CodingGuidelines prefer the opening brace on the same line after\n> the if/for/while/struct/etc. statement, and even omitting the braces\n> if the if arm or loop body consists of a single statement.  So\n> unfortunately a considerable part of this range diff goes in the wrong\n> direction.\n\nI know they do and I had them on the same line originally.\n\nClang-format was complaining about every use of the for_each_builtin\nmacro, so I changed them to be on the next line to quiet it.\n\nI hesitate to remove braces around a statement adjacent to a\nfor_each macro trick for the usual safety reasons.\n\nJeff\n\n\n"},{"id":"368255","messageId":"xmqqftt83bt9.fsf@gitster-ct.c.googlers.com","threadId":"50293","inReplyTo":"pull.108.v4.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 00/14] Trace2 tracing facility","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-01-31T18:46:10Z","receivedAt":"2019-01-31T18:46:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Jeff Hostetler via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Sorry to spam the list, but here is V4. After building V3 on 3 platforms\n> without error and submitting, the compilers on platforms 4 and 5 complained\n> about a variable declaration. (sigh) [] fix declaration after first\n> statement [] add -DNO_UNIX_SOCKETS to BASIC_CFLAGS when NO_UNIX_SOCKETS is\n> defined in the Makefile.\n\nOK.\n\nBy the way, if somebody can teach gitgitgadget the same Date: header\ntrick that is used by git-send-email, so that the messages sort\nwell, it would be very much appreciated.  Here is how I see this\nthread in my MUA:\n\n .   [1366: Jeff Hostetler via GitG] [PATCH v4 01/14] trace2: Documentat...\nR.    [  43: SZEDER Gábor           ] \n .   [4459: Jeff Hostetler via GitG] [PATCH v4 02/14] trace2: create new...\n .   [  69: Jeff Hostetler via GitG] [PATCH v4 04/14] trace2:data: add t...\n .   [ 186: Jeff Hostetler via GitG] [PATCH v4 03/14] trace2: collect pl...\n .   [  38: Jeff Hostetler via GitG] [PATCH v4 05/14] trace2:data: add e...\n .   [  25: Jeff Hostetler via GitG] [PATCH v4 06/14] trace2:data: add t...\n .   [  50: Jeff Hostetler via GitG] [PATCH v4 07/14] trace2:data: add t...\n .   [ 101: Jeff Hostetler via GitG] [PATCH v4 08/14] trace2:data: add t...\n .   [ 114: Jeff Hostetler via GitG] [PATCH v4 09/14] trace2:data: add t...\n .   [  68: Derrick Stolee via GitG] [PATCH v4 10/14] pack-objects: add ...\n .   [  42: Jeff Hostetler via GitG] [PATCH v4 11/14] trace2:data: add s...\n .   [  34: Jeff Hostetler via GitG] [PATCH v4 12/14] trace2:data: add s...\n .   [  45: Jeff Hostetler via GitG] [PATCH v4 13/14] trace2:data: add s...\n .   [1276: Jeff Hostetler via GitG] [PATCH v4 14/14] trace2: t/helper/t...\n\nand all but 4 and 3 appear in the right order owes mostly because\nthey have Date: header that increases by 1 second interval, but 3\nand 4 share the same timestamp.\n\nI can obviously cope with it, but if a tool at the sending end can\neliminate the need to cope with it in the first place, that would\nhelp not just me but others ;-)\n\n"},{"id":"368257","messageId":"xmqqbm3w3bjz.fsf@gitster-ct.c.googlers.com","threadId":"50293","inReplyTo":"9b603fd7-d8f0-1ce7-7c7c-8b17484d7d0a@jeffhostetler.com","subject":"Re: [PATCH v3 00/14] Trace2 tracing facility","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-01-31T18:51:44Z","receivedAt":"2019-01-31T18:51:49Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff Hostetler <git@jeffhostetler.com> writes:\n\n>>>       -+\tfor_each_builtin(j, tgt_j) {\n>>>       ++\tfor_each_builtin(j, tgt_j)\n>>>       ++\t{\n>>>        +\t\ttgt_j->pfn_term();\n>>>        +\t}\n>>\n>> Our CodingGuidelines prefer the opening brace on the same line after\n>> the if/for/while/struct/etc. statement, and even omitting the braces\n>> if the if arm or loop body consists of a single statement.  So\n>> unfortunately a considerable part of this range diff goes in the wrong\n>> direction.\n>\n> I know they do and I had them on the same line originally.\n>\n> Clang-format was complaining about every use of the for_each_builtin\n> macro, so I changed them to be on the next line to quiet it.\n\nWell, clang-format is wrong then ;-)\n\n> I hesitate to remove braces around a statement adjacent to a\n> for_each macro trick for the usual safety reasons.\n\nSorry, but what's \"usual safety reasons\"?  Isn't a macro that\nrequires {} in order to work correctly simply broken?\n\nI see (from a previous iteration---sorry, but I haven't caught up)\n\n#define for_each_builtin(j, tgt_j)                      \\\n        for (j = 0, tgt_j = tr2_tgt_builtins[j];        \\\n             tgt_j;                                     \\\n             j++, tgt_j = tr2_tgt_builtins[j])\n                                  \n\nand I do not think\n\n\tfor (j = 0, tgt_j = ...; tgt_j; j++, tgt_j = ...)\n\t\tstatement;\n\nis unsafe (iow, your macro is not broken).\n\nPuzzled.\n"},{"id":"368261","messageId":"0764948c-f3ac-3bd1-fd82-2bd3f59b463d@jeffhostetler.com","threadId":"50293","inReplyTo":"xmqqbm3w3bjz.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 00/14] Trace2 tracing facility","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2019-01-31T19:45:38Z","receivedAt":"2019-01-31T19:45:31Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 1/31/2019 1:51 PM, Junio C Hamano wrote:\n> Jeff Hostetler <git@jeffhostetler.com> writes:\n> \n>>>>        -+\tfor_each_builtin(j, tgt_j) {\n>>>>        ++\tfor_each_builtin(j, tgt_j)\n>>>>        ++\t{\n>>>>         +\t\ttgt_j->pfn_term();\n>>>>         +\t}\n>>>\n>>> Our CodingGuidelines prefer the opening brace on the same line after\n>>> the if/for/while/struct/etc. statement, and even omitting the braces\n>>> if the if arm or loop body consists of a single statement.  So\n>>> unfortunately a considerable part of this range diff goes in the wrong\n>>> direction.\n>>\n>> I know they do and I had them on the same line originally.\n>>\n>> Clang-format was complaining about every use of the for_each_builtin\n>> macro, so I changed them to be on the next line to quiet it.\n> \n> Well, clang-format is wrong then ;-)\n\nOk.  I had never even heard of clang-format until Josh suggested\nthat it flagged several commits in my series.  The last thing I wanted\nto do was to start hacking up its config file (in the dark) assuming\nthat is even an option.\n\nI can undo my formatting changes if we want to update the settings.\nI'll give that a try if there are no objections.\n\n> \n>> I hesitate to remove braces around a statement adjacent to a\n>> for_each macro trick for the usual safety reasons.\n> \n> Sorry, but what's \"usual safety reasons\"?  Isn't a macro that\n> requires {} in order to work correctly simply broken?\n> \n> I see (from a previous iteration---sorry, but I haven't caught up)\n> \n> #define for_each_builtin(j, tgt_j)                      \\\n>          for (j = 0, tgt_j = tr2_tgt_builtins[j];        \\\n>               tgt_j;                                     \\\n>               j++, tgt_j = tr2_tgt_builtins[j])\n>                                    \n> \n> and I do not think\n> \n> \tfor (j = 0, tgt_j = ...; tgt_j; j++, tgt_j = ...)\n> \t\tstatement;\n> \n> is unsafe (iow, your macro is not broken).\n> \n> Puzzled.\n> \n\nRight, I don't think my macro is broken either.  It is more\nmy personal paranoia about preventing accidents (like wrapping\nmacro parameters in parens when using them in the macro body).\n\nI can remove them if we want.\n\nJeff\n"},{"id":"368275","messageId":"a2b7a14d-5829-45dc-81b2-9ff9b876c017@jeffhostetler.com","threadId":"50293","inReplyTo":"0764948c-f3ac-3bd1-fd82-2bd3f59b463d@jeffhostetler.com","subject":"Re: [PATCH v3 00/14] Trace2 tracing facility","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2019-01-31T22:11:48Z","receivedAt":"2019-01-31T22:11:40Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 1/31/2019 2:45 PM, Jeff Hostetler wrote:\n> \n...\n> \n> I can undo my formatting changes if we want to update the settings.\n> I'll give that a try if there are no objections.\n> \n\nI'll push up a V5 in the morning that adds the foreach\nmacros to the .clang-format and updates the code accordingly.\nCurrently, waiting for the CI builds to finish.\n\nJeff\n\n"},{"id":"368276","messageId":"0384b5e0-e2e2-710f-2e37-1dff96aac143@jeffhostetler.com","threadId":"50293","inReplyTo":"20190131141127.GA10587@szeder.dev","subject":"Re: [PATCH v4 01/14] trace2: Documentation/technical/api-trace2.txt","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2019-01-31T22:33:29Z","receivedAt":"2019-01-31T22:33:20Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 1/31/2019 9:11 AM, SZEDER Gábor wrote:\n> On Wed, Jan 30, 2019 at 12:56:21PM -0800, Jeff Hostetler via GitGitGadget wrote:\n>> From: Jeff Hostetler <jeffhost@microsoft.com>\n> \n>> diff --git a/Documentation/technical/api-trace2.txt b/Documentation/technical/api-trace2.txt\n> \n>> +== Public API\n> \n>> +=== Command Detail Messages\n>> +\n>> +These are concerned with describing the specific Git command\n>> +after the command line, config, and environment are inspected.\n>> +\n>> +`void trace2_cmd_verb(const char *command_verb)`::\n>> +\n>> +\tEmits a \"cmd_verb\" message with the canonical name of\n>> +\t(usually) builtin command, for example \"status\" or \"checkout\".\n>> +\n>> +`void trace2_cmd_subverb(const char *command_subverb)`::\n>> +\n>> +\tEmits a \"cmd_subverb\" message with a qualifier name to further\n>> +\tdescribe the current git command.\n> \n> OK, so now we know what is meant by 'verb' and 'subverb'.\n> \n> Alas, this explanation also clearly shows that the word 'verb' is a\n> poor fit for what it tries to convey, and 'subverb' is even worse.\n\nHow can we resolve this?\n\nI've been using the term \"command\" (1) to refer to the entire lifetime\nof a git command (process/instance/invocation).  Trace2 issues a series\nof trace messages over that life of the process that all have a unique\nid.  This allows multiple concurrent processes to write to the same\nfile and yet be able to associate all of the messages from a single\ncommand instance.\n\nI've been using the term \"verb\" (2) to the action -- i.e. \"status\" in\na \"git status\" command and \"fetch\" in a \"git fetch\" command.\n\nI've been using the term \"subverb\" (3) to refer to the variant when\nthere are major differences.\n\nWhat would be better choices for these 3 concepts?\n\nWould something like \"process\", \"command\", and \"mode\" be better?\nOr maybe \"process\", \"command\", and \"command-mode\"?\n\nI'd rather not use the term \"subcommand\" because it already has mixed\nusage.\n[] See git_more_info_string[] in git.c and list_all_cmd_help()\n    in help.c where both \"command\" and \"subcommand\" are used\n    interchangeably.\n[] And yet another completely different usage in git-submodule\n    and bisect where \"subcommand\" is used to mean the 3rd token\n    on the command line after the \"command\".\n\nSuggestions?\nJeff\n\n\n"},{"id":"368278","messageId":"20190131231528.GC10587@szeder.dev","threadId":"50293","inReplyTo":"6167aba76ca43707bbba6de9f7c41a9fcb16063c.1548881779.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 03/14] trace2: collect platform-specific process information","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2019-01-31T23:15:28Z","receivedAt":"2019-01-31T23:15:35Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Wed, Jan 30, 2019 at 12:56:24PM -0800, Jeff Hostetler via GitGitGadget wrote:\n> Add optional platform-specific code to log information about\n> the current process.\n> \n> On Windows, this includes whether git.exe is running under a\n> debugger and information about the ancestors of the process.\n> \n> The purpose of this information is to help indicate if the\n> process was launched interactively or in the background by\n> an IDE, for example.\n\n> diff --git a/trace2.h b/trace2.h\n> index a0e99d9c26..cb11a46366 100644\n> --- a/trace2.h\n> +++ b/trace2.h\n> @@ -363,4 +363,18 @@ __attribute__((format (printf, 1, 2)))\n>  void trace2_printf(const char *fmt, ...);\n>  #endif\n>  \n> +/*\n> + * Optional platform-specific code to dump information about the\n> + * current and any parent process(es).  This is intended to allow\n> + * post-processors to know who spawned this git instance and anything\n> + * else the platform may be able to tell us about the current process.\n> + */\n> +#if defined(GIT_WINDOWS_NATIVE)\n> +void trace2_collect_process_info(void);\n> +#else\n> +#define trace2_collect_process_info() \\\n> +\tdo {                          \\\n> +\t} while (0)\n> +#endif\n\nPlease consider mentioning in the commit message that on other\nplatforms this is a noop.  I was scrolling through the whole patch,\nskipping over the Windows-specific parts, to see how you did it on\nLinux, only to find the above do-nothing loop.  It was anticlimactic :)\n\nWhy is it a noop on other platforms?  I suspect that (since your main\nfocus is supporting Windows devs using Git on Windows) it's along the\nlines of \"I just didn't want to bother, and left it as future work for\nanyone interested\", which a perfectly valid reason in my book.\nHowever, if you did look into it, and found some major difficulties or\ndownright showstoppers, then that might be worth mentioning.\n(Portability?  I would expect that it would need a bunch of '#elif\ndefined(...)')\n\n"},{"id":"368281","messageId":"20190131232851.GD10587@szeder.dev","threadId":"50293","inReplyTo":"8d47cd71ede3fd4e932e26756ef8f3f82b41d659.1548881779.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 14/14] trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2019-01-31T23:28:51Z","receivedAt":"2019-01-31T23:28:58Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Wed, Jan 30, 2019 at 12:56:34PM -0800, Jeff Hostetler via GitGitGadget wrote:\n> Create unit tests for Trace2.\n\n> diff --git a/t/t0212-trace2-event.sh b/t/t0212-trace2-event.sh\n> new file mode 100755\n> index 0000000000..517d5514df\n> --- /dev/null\n> +++ b/t/t0212-trace2-event.sh\n> @@ -0,0 +1,234 @@\n\n> +test_expect_success 'event stream, return code 0' '\n> +\ttest_when_finished \"rm trace.event actual expect\" &&\n> +\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 004child test-tool trace2 004child test-tool trace2 001return 0 &&\n> +\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n> +\tsed -e \"s/^|//\" >expect <<-EOF &&\n> +\t|VAR1 = {\n> +\t|  \"_SID0_\":{\n> +\t|    \"argv\":[\n> +\t|      \"_EXE_\",\n> +\t|      \"trace2\",\n> +\t|      \"004child\",\n> +\t|      \"test-tool\",\n> +\t|      \"trace2\",\n> +\t|      \"004child\",\n> +\t|      \"test-tool\",\n> +\t|      \"trace2\",\n> +\t|      \"001return\",\n> +\t|      \"0\"\n> +\t|    ],\n> +\t|    \"child\":{\n> +\t|      \"0\":{\n> +\t|        \"child_argv\":[\n> +\t|          \"_EXE_\",\n> +\t|          \"trace2\",\n> +\t|          \"004child\",\n> +\t|          \"test-tool\",\n> +\t|          \"trace2\",\n> +\t|          \"001return\",\n> +\t|          \"0\"\n> +\t|        ],\n\nJust curious about the vertically aligned '|'s: are they there to\ncircumvent 'git diff's big red whitespace warnings on these deeply\nindented lines?\n\n"},{"id":"368282","messageId":"xmqq36p81k29.fsf@gitster-ct.c.googlers.com","threadId":"50293","inReplyTo":"a2b7a14d-5829-45dc-81b2-9ff9b876c017@jeffhostetler.com","subject":"Re: [PATCH v3 00/14] Trace2 tracing facility","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-01-31T23:30:54Z","receivedAt":"2019-01-31T23:30:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff Hostetler <git@jeffhostetler.com> writes:\n\n> On 1/31/2019 2:45 PM, Jeff Hostetler wrote:\n>>\n> ...\n>>\n>> I can undo my formatting changes if we want to update the settings.\n>> I'll give that a try if there are no objections.\n>>\n>\n> I'll push up a V5 in the morning that adds the foreach\n> macros to the .clang-format and updates the code accordingly.\n\nWonderful.  Thanks for being thorough.\n\n> Currently, waiting for the CI builds to finish.\n>\n> Jeff\n"},{"id":"368284","messageId":"xmqqy370z9am.fsf@gitster-ct.c.googlers.com","threadId":"50293","inReplyTo":"0384b5e0-e2e2-710f-2e37-1dff96aac143@jeffhostetler.com","subject":"Re: [PATCH v4 01/14] trace2: Documentation/technical/api-trace2.txt","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-01-31T23:39:29Z","receivedAt":"2019-01-31T23:39:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff Hostetler <git@jeffhostetler.com> writes:\n\n> I'd rather not use the term \"subcommand\" because it already has mixed\n> usage.\n> [] See git_more_info_string[] in git.c and list_all_cmd_help()\n>    in help.c where both \"command\" and \"subcommand\" are used\n>    interchangeably.\n> [] And yet another completely different usage in git-submodule\n>    and bisect where \"subcommand\" is used to mean the 3rd token\n>    on the command line after the \"command\".\n\nTo \"git\" the command, \"submodule\" would be its direct subcommand,\nand to \"git submodule\", things like \"update\", \"init\", etc. are\nsubcommands that is one level down.  I am not sure if it bothers\nme too much that hierarchical things are not always named from\nthe top-level, requiring the third-level things to be called always\nsubsubX.  In the context of describing \"git submodule\", I would say\ncalling \"init\" subsubcommand may be technically correct, but it\nwould be too pedantic to be practical, when it is clear from the\ncontext that we are discussing \"git submodule\".\n\n"},{"id":"368285","messageId":"xmqqtvhoz96z.fsf@gitster-ct.c.googlers.com","threadId":"50293","inReplyTo":"20190131232851.GD10587@szeder.dev","subject":"Re: [PATCH v4 14/14] trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-01-31T23:41:40Z","receivedAt":"2019-01-31T23:41:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"SZEDER Gábor <szeder.dev@gmail.com> writes:\n\n> On Wed, Jan 30, 2019 at 12:56:34PM -0800, Jeff Hostetler via GitGitGadget wrote:\n>> Create unit tests for Trace2.\n>\n>> diff --git a/t/t0212-trace2-event.sh b/t/t0212-trace2-event.sh\n>> new file mode 100755\n>> index 0000000000..517d5514df\n>> --- /dev/null\n>> +++ b/t/t0212-trace2-event.sh\n>> @@ -0,0 +1,234 @@\n>\n>> +test_expect_success 'event stream, return code 0' '\n>> +\ttest_when_finished \"rm trace.event actual expect\" &&\n>> +\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 004child test-tool trace2 004child test-tool trace2 001return 0 &&\n>> +\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n>> +\tsed -e \"s/^|//\" >expect <<-EOF &&\n>> +\t|VAR1 = {\n>> +\t|  \"_SID0_\":{\n>> +\t|    \"argv\":[\n>> +\t|      \"_EXE_\",\n>> +\t|      \"trace2\",\n>> +\t|      \"004child\",\n>> +\t|      \"test-tool\",\n>> +\t|      \"trace2\",\n>> +\t|      \"004child\",\n>> +\t|      \"test-tool\",\n>> +\t|      \"trace2\",\n>> +\t|      \"001return\",\n>> +\t|      \"0\"\n>> +\t|    ],\n>> +\t|    \"child\":{\n>> +\t|      \"0\":{\n>> +\t|        \"child_argv\":[\n>> +\t|          \"_EXE_\",\n>> +\t|          \"trace2\",\n>> +\t|          \"004child\",\n>> +\t|          \"test-tool\",\n>> +\t|          \"trace2\",\n>> +\t|          \"001return\",\n>> +\t|          \"0\"\n>> +\t|        ],\n>\n> Just curious about the vertically aligned '|'s: are they there to\n> circumvent 'git diff's big red whitespace warnings on these deeply\n> indented lines?\n\nI suspect that the reason is some of the lines have 8-spaces or more\nafter '|', that can be tabified, but when tabified, <<-EOF will strip\nthat second and subsequent tabs.\n"},{"id":"368322","messageId":"75c87033-0e1f-b119-5856-44a8c91a675b@jeffhostetler.com","threadId":"50293","inReplyTo":"xmqqtvhoz96z.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v4 14/14] trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2019-02-01T11:08:59Z","receivedAt":"2019-02-01T11:09:00Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 1/31/2019 6:41 PM, Junio C Hamano wrote:\n> SZEDER Gábor <szeder.dev@gmail.com> writes:\n> \n>> On Wed, Jan 30, 2019 at 12:56:34PM -0800, Jeff Hostetler via GitGitGadget wrote:\n>>> Create unit tests for Trace2.\n>>\n>>> diff --git a/t/t0212-trace2-event.sh b/t/t0212-trace2-event.sh\n>>> new file mode 100755\n>>> index 0000000000..517d5514df\n>>> --- /dev/null\n>>> +++ b/t/t0212-trace2-event.sh\n>>> @@ -0,0 +1,234 @@\n>>\n>>> +test_expect_success 'event stream, return code 0' '\n>>> +\ttest_when_finished \"rm trace.event actual expect\" &&\n>>> +\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 004child test-tool trace2 004child test-tool trace2 001return 0 &&\n>>> +\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n>>> +\tsed -e \"s/^|//\" >expect <<-EOF &&\n>>> +\t|VAR1 = {\n>>> +\t|  \"_SID0_\":{\n>>> +\t|    \"argv\":[\n>>> +\t|      \"_EXE_\",\n>>> +\t|      \"trace2\",\n>>> +\t|      \"004child\",\n>>> +\t|      \"test-tool\",\n>>> +\t|      \"trace2\",\n>>> +\t|      \"004child\",\n>>> +\t|      \"test-tool\",\n>>> +\t|      \"trace2\",\n>>> +\t|      \"001return\",\n>>> +\t|      \"0\"\n>>> +\t|    ],\n>>> +\t|    \"child\":{\n>>> +\t|      \"0\":{\n>>> +\t|        \"child_argv\":[\n>>> +\t|          \"_EXE_\",\n>>> +\t|          \"trace2\",\n>>> +\t|          \"004child\",\n>>> +\t|          \"test-tool\",\n>>> +\t|          \"trace2\",\n>>> +\t|          \"001return\",\n>>> +\t|          \"0\"\n>>> +\t|        ],\n>>\n>> Just curious about the vertically aligned '|'s: are they there to\n>> circumvent 'git diff's big red whitespace warnings on these deeply\n>> indented lines?\n> \n> I suspect that the reason is some of the lines have 8-spaces or more\n> after '|', that can be tabified, but when tabified, <<-EOF will strip\n> that second and subsequent tabs.\n> \n\nYes, the TAB BAR prefixes are to avoid the red whitespace warnings\nand indicate the exact whitespace in actual output and avoid the\neffects of the <<-EOF.  This technique has been used before in other\ntests.  See t0019 and t1502.\n\nJeff\n\n\n"},{"id":"368323","messageId":"a1bf3e53-3f79-32f4-f349-a3165c02f595@jeffhostetler.com","threadId":"50293","inReplyTo":"20190131231528.GC10587@szeder.dev","subject":"Re: [PATCH v4 03/14] trace2: collect platform-specific process information","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2019-02-01T11:12:57Z","receivedAt":"2019-02-01T11:13:02Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 1/31/2019 6:15 PM, SZEDER Gábor wrote:\n> On Wed, Jan 30, 2019 at 12:56:24PM -0800, Jeff Hostetler via GitGitGadget wrote:\n>> Add optional platform-specific code to log information about\n>> the current process.\n>>\n>> On Windows, this includes whether git.exe is running under a\n>> debugger and information about the ancestors of the process.\n>>\n>> The purpose of this information is to help indicate if the\n>> process was launched interactively or in the background by\n>> an IDE, for example.\n> \n>> diff --git a/trace2.h b/trace2.h\n>> index a0e99d9c26..cb11a46366 100644\n>> --- a/trace2.h\n>> +++ b/trace2.h\n>> @@ -363,4 +363,18 @@ __attribute__((format (printf, 1, 2)))\n>>   void trace2_printf(const char *fmt, ...);\n>>   #endif\n>>   \n>> +/*\n>> + * Optional platform-specific code to dump information about the\n>> + * current and any parent process(es).  This is intended to allow\n>> + * post-processors to know who spawned this git instance and anything\n>> + * else the platform may be able to tell us about the current process.\n>> + */\n>> +#if defined(GIT_WINDOWS_NATIVE)\n>> +void trace2_collect_process_info(void);\n>> +#else\n>> +#define trace2_collect_process_info() \\\n>> +\tdo {                          \\\n>> +\t} while (0)\n>> +#endif\n> \n> Please consider mentioning in the commit message that on other\n> platforms this is a noop.  I was scrolling through the whole patch,\n> skipping over the Windows-specific parts, to see how you did it on\n> Linux, only to find the above do-nothing loop.  It was anticlimactic :)\n> \n> Why is it a noop on other platforms?  I suspect that (since your main\n> focus is supporting Windows devs using Git on Windows) it's along the\n> lines of \"I just didn't want to bother, and left it as future work for\n> anyone interested\", which a perfectly valid reason in my book.\n> However, if you did look into it, and found some major difficulties or\n> downright showstoppers, then that might be worth mentioning.\n> (Portability?  I would expect that it would need a bunch of '#elif\n> defined(...)')\n\nRight, my primary focus was Windows at the time and I only added\nthe Windows version.  I'll document that.\n\nThanks\nJeff\n\n\n"},{"id":"368346","messageId":"7c987cde860b783782e4ee8e4f6ebd362c0df9a2.1549043936.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 03/15] trace2: collect Windows-specific process information","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-01T17:59:01Z","receivedAt":"2019-02-01T17:59:05Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd platform-specific interface to log information about the current\nprocess.\n\nOn Windows, this interface is used to indicate whether the git process\nis running under a debugger and list names of the process ancestors.\n\nInformation for other platforms is left for a future effort.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n common-main.c                            |   1 +\n compat/win32/trace2_win32_process_info.c | 101 +++++++++++++++++++++++\n config.mak.uname                         |   2 +\n trace2.h                                 |  14 ++++\n 4 files changed, 118 insertions(+)\n create mode 100644 compat/win32/trace2_win32_process_info.c\n\ndiff --git a/common-main.c b/common-main.c\nindex 6dbdc4adf2..d484aec209 100644\n--- a/common-main.c\n+++ b/common-main.c\n@@ -37,6 +37,7 @@ int main(int argc, const char **argv)\n \n \ttrace2_initialize();\n \ttrace2_cmd_start(argv);\n+\ttrace2_collect_process_info();\n \n \tgit_resolve_executable_dir(argv[0]);\n \ndiff --git a/compat/win32/trace2_win32_process_info.c b/compat/win32/trace2_win32_process_info.c\nnew file mode 100644\nindex 0000000000..253199f812\n--- /dev/null\n+++ b/compat/win32/trace2_win32_process_info.c\n@@ -0,0 +1,101 @@\n+#include \"../../cache.h\"\n+#include \"../../json-writer.h\"\n+#include <Psapi.h>\n+#include <tlHelp32.h>\n+\n+/*\n+ * Find the process data for the given PID in the given snapshot\n+ * and update the PROCESSENTRY32 data.\n+ */\n+static int find_pid(DWORD pid, HANDLE hSnapshot, PROCESSENTRY32 *pe32)\n+{\n+\tpe32->dwSize = sizeof(PROCESSENTRY32);\n+\n+\tif (Process32First(hSnapshot, pe32)) {\n+\t\tdo {\n+\t\t\tif (pe32->th32ProcessID == pid)\n+\t\t\t\treturn 1;\n+\t\t} while (Process32Next(hSnapshot, pe32));\n+\t}\n+\treturn 0;\n+}\n+\n+/*\n+ * Accumulate JSON array:\n+ *     [\n+ *         exe-name-parent,\n+ *         exe-name-grand-parent,\n+ *         ...\n+ *     ]\n+ *\n+ * Note: we only report the filename of the process executable; the\n+ *       only way to get its full pathname is to use OpenProcess()\n+ *       and GetModuleFileNameEx() or QueryfullProcessImageName()\n+ *       and that seems rather expensive (on top of the cost of\n+ *       getting the snapshot).\n+ */\n+static void get_processes(struct json_writer *jw, HANDLE hSnapshot)\n+{\n+\tPROCESSENTRY32 pe32;\n+\tDWORD pid;\n+\n+\tpid = GetCurrentProcessId();\n+\n+\t/* We only want parent processes, so skip self. */\n+\tif (!find_pid(pid, hSnapshot, &pe32))\n+\t\treturn;\n+\tpid = pe32.th32ParentProcessID;\n+\n+\twhile (find_pid(pid, hSnapshot, &pe32)) {\n+\t\tjw_array_string(jw, pe32.szExeFile);\n+\n+\t\tpid = pe32.th32ParentProcessID;\n+\t}\n+}\n+\n+/*\n+ * Emit JSON data for the current and parent processes.  Individual\n+ * trace2 targets can decide how to actually print it.\n+ */\n+static void get_ancestry(void)\n+{\n+\tHANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);\n+\n+\tif (hSnapshot != INVALID_HANDLE_VALUE) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\t\tjw_array_begin(&jw, 0);\n+\t\tget_processes(&jw, hSnapshot);\n+\t\tjw_end(&jw);\n+\n+\t\ttrace2_data_json(\"process\", the_repository, \"windows/ancestry\",\n+\t\t\t\t &jw);\n+\n+\t\tjw_release(&jw);\n+\t\tCloseHandle(hSnapshot);\n+\t}\n+}\n+\n+/*\n+ * Is a debugger attached to the current process?\n+ *\n+ * This will catch debug runs (where the debugger started the process).\n+ * This is the normal case.  Since this code is called during our startup,\n+ * it will not report instances where a debugger is attached dynamically\n+ * to a running git process, but that is relatively rare.\n+ */\n+static void get_is_being_debugged(void)\n+{\n+\tif (IsDebuggerPresent())\n+\t\ttrace2_data_intmax(\"process\", the_repository,\n+\t\t\t\t   \"windows/debugger_present\", 1);\n+}\n+\n+void trace2_collect_process_info(void)\n+{\n+\tif (!trace2_is_enabled())\n+\t\treturn;\n+\n+\tget_is_being_debugged();\n+\tget_ancestry();\n+}\ndiff --git a/config.mak.uname b/config.mak.uname\nindex 7b36a1dfe7..a3c03ce2ae 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -391,6 +391,7 @@ ifeq ($(uname_S),Windows)\n \tBASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE\n \tCOMPAT_OBJS = compat/msvc.o compat/winansi.o \\\n \t\tcompat/win32/pthread.o compat/win32/syslog.o \\\n+\t\tcompat/win32/trace2_win32_process_info.o \\\n \t\tcompat/win32/dirent.o\n \tCOMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\\\".exe\\\"\n \tBASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE\n@@ -545,6 +546,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))\n \tCOMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32\n \tCOMPAT_CFLAGS += -DSTRIP_EXTENSION=\\\".exe\\\"\n \tCOMPAT_OBJS += compat/mingw.o compat/winansi.o \\\n+\t\tcompat/win32/trace2_win32_process_info.o \\\n \t\tcompat/win32/path-utils.o \\\n \t\tcompat/win32/pthread.o compat/win32/syslog.o \\\n \t\tcompat/win32/dirent.o\ndiff --git a/trace2.h b/trace2.h\nindex fce9891f53..ae5020d0e6 100644\n--- a/trace2.h\n+++ b/trace2.h\n@@ -368,4 +368,18 @@ void trace2_printf(const char *fmt, ...);\n /* clang-format on */\n #endif\n \n+/*\n+ * Optional platform-specific code to dump information about the\n+ * current and any parent process(es).  This is intended to allow\n+ * post-processors to know who spawned this git instance and anything\n+ * else the platform may be able to tell us about the current process.\n+ */\n+#if defined(GIT_WINDOWS_NATIVE)\n+void trace2_collect_process_info(void);\n+#else\n+#define trace2_collect_process_info() \\\n+\tdo {                          \\\n+\t} while (0)\n+#endif\n+\n #endif /* TRACE2_H */\n-- \ngitgitgadget\n\n"},{"id":"368347","messageId":"cd860799f88ba9832d7b15822dcc866a7395d697.1549043936.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 04/15] trace2:data: add trace2 regions to wt-status","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-01T17:59:02Z","receivedAt":"2019-02-01T17:59:07Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2_region_enter() and trace2_region_leave() calls around the\nvarious phases of a status scan.  This gives elapsed time for each\nphase in the GIT_TR2_PERF and GIT_TR2_EVENT trace target.\n\nAlso, these Trace2 calls now use s->repo rather than the_repository.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n wt-status.c | 24 ++++++++++++++++++++++--\n 1 file changed, 22 insertions(+), 2 deletions(-)\n\ndiff --git a/wt-status.c b/wt-status.c\nindex 0fe3bcd4cd..434636850e 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -748,12 +748,23 @@ static int has_unmerged(struct wt_status *s)\n \n void wt_status_collect(struct wt_status *s)\n {\n+\ttrace2_region_enter(\"status\", \"worktrees\", s->repo);\n \twt_status_collect_changes_worktree(s);\n-\tif (s->is_initial)\n+\ttrace2_region_leave(\"status\", \"worktrees\", s->repo);\n+\n+\tif (s->is_initial) {\n+\t\ttrace2_region_enter(\"status\", \"initial\", s->repo);\n \t\twt_status_collect_changes_initial(s);\n-\telse\n+\t\ttrace2_region_leave(\"status\", \"initial\", s->repo);\n+\t} else {\n+\t\ttrace2_region_enter(\"status\", \"index\", s->repo);\n \t\twt_status_collect_changes_index(s);\n+\t\ttrace2_region_leave(\"status\", \"index\", s->repo);\n+\t}\n+\n+\ttrace2_region_enter(\"status\", \"untracked\", s->repo);\n \twt_status_collect_untracked(s);\n+\ttrace2_region_leave(\"status\", \"untracked\", s->repo);\n \n \twt_status_get_state(s->repo, &s->state, s->branch && !strcmp(s->branch, \"HEAD\"));\n \tif (s->state.merge_in_progress && !has_unmerged(s))\n@@ -2291,6 +2302,13 @@ static void wt_porcelain_v2_print(struct wt_status *s)\n \n void wt_status_print(struct wt_status *s)\n {\n+\ttrace2_data_intmax(\"status\", s->repo, \"count/changed\", s->change.nr);\n+\ttrace2_data_intmax(\"status\", s->repo, \"count/untracked\",\n+\t\t\t   s->untracked.nr);\n+\ttrace2_data_intmax(\"status\", s->repo, \"count/ignored\", s->ignored.nr);\n+\n+\ttrace2_region_enter(\"status\", \"print\", s->repo);\n+\n \tswitch (s->status_format) {\n \tcase STATUS_FORMAT_SHORT:\n \t\twt_shortstatus_print(s);\n@@ -2309,6 +2327,8 @@ void wt_status_print(struct wt_status *s)\n \t\twt_longstatus_print(s);\n \t\tbreak;\n \t}\n+\n+\ttrace2_region_leave(\"status\", \"print\", s->repo);\n }\n \n /**\n-- \ngitgitgadget\n\n"},{"id":"368348","messageId":"4c006f4995e8c4c1356d9d93b6352455069e4b6d.1549043936.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 01/15] trace2: Documentation/technical/api-trace2.txt","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-01T17:58:59Z","receivedAt":"2019-02-01T17:59:07Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreated design document for Trace2 feature.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Documentation/technical/api-trace2.txt | 1347 ++++++++++++++++++++++++\n 1 file changed, 1347 insertions(+)\n create mode 100644 Documentation/technical/api-trace2.txt\n\ndiff --git a/Documentation/technical/api-trace2.txt b/Documentation/technical/api-trace2.txt\nnew file mode 100644\nindex 0000000000..6ee574a86e\n--- /dev/null\n+++ b/Documentation/technical/api-trace2.txt\n@@ -0,0 +1,1347 @@\n+= Trace2 API\n+\n+The Trace2 API can be used to print debug, performance, and telemetry\n+information to stderr or a file.  The Trace2 feature is inactive unless\n+explicitly enabled by enabling one or more Trace2 Targets.\n+\n+The Trace2 API is intended to replace the existing (Trace1)\n+printf-style tracing provided by the existing `GIT_TRACE` and\n+`GIT_TRACE_PERFORMANCE` facilities.  During initial implementation,\n+Trace2 and Trace1 may operate in parallel.\n+\n+The Trace2 API defines a set of high-level messages with known fields,\n+such as (`start`: `argv`) and (`exit`: {`exit-code`, `elapsed-time`}).\n+\n+Trace2 instrumentation throughout the Git code base sends Trace2\n+messages to the enabled Trace2 Targets.  Targets transform these\n+messages content into purpose-specific formats and write events to\n+their data streams.  In this manner, the Trace2 API can drive\n+many different types of analysis.\n+\n+Targets are defined using a VTable allowing easy extension to other\n+formats in the future.  This might be used to define a binary format,\n+for example.\n+\n+== Trace2 Targets\n+\n+Trace2 defines the following set of Trace2 Targets.\n+Format details are given in a later section.\n+\n+`GIT_TR2` (NORMAL)::\n+\n+\ta simple printf format like GIT_TRACE.\n++\n+------------\n+$ export GIT_TR2=~/log.normal\n+$ git version\n+git version 2.20.1.155.g426c96fcdb\n+------------\n++\n+------------\n+$ cat ~/log.normal\n+12:28:42.620009 common-main.c:38                  version 2.20.1.155.g426c96fcdb\n+12:28:42.620989 common-main.c:39                  start git version\n+12:28:42.621101 git.c:432                         cmd_name version (version)\n+12:28:42.621215 git.c:662                         exit elapsed:0.001227 code:0\n+12:28:42.621250 trace2/tr2_tgt_normal.c:124       atexit elapsed:0.001265 code:0\n+------------\n+\n+`GIT_TR2_PERF` (PERF)::\n+\n+\ta column-based format to replace GIT_TRACE_PERFORMANCE suitable for\n+\tdevelopment and testing, possibly to complement tools like gprof.\n++\n+------------\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git version\n+git version 2.20.1.155.g426c96fcdb\n+------------\n++\n+------------\n+$ cat ~/log.perf\n+12:28:42.620675 common-main.c:38                  | d0 | main                     | version      |     |           |           |            | 2.20.1.155.g426c96fcdb\n+12:28:42.621001 common-main.c:39                  | d0 | main                     | start        |     |           |           |            | git version\n+12:28:42.621111 git.c:432                         | d0 | main                     | cmd_name     |     |           |           |            | version (version)\n+12:28:42.621225 git.c:662                         | d0 | main                     | exit         |     |  0.001227 |           |            | code:0\n+12:28:42.621259 trace2/tr2_tgt_perf.c:211         | d0 | main                     | atexit       |     |  0.001265 |           |            | code:0\n+------------\n+\n+`GIT_TR2_EVENT` (EVENT)::\n+\n+\ta JSON-based format of event data suitable for telemetry analysis.\n++\n+------------\n+$ export GIT_TR2_EVENT=~/log.event\n+$ git version\n+git version 2.20.1.155.g426c96fcdb\n+------------\n++\n+------------\n+$ cat ~/log.event\n+{\"event\":\"version\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.620713\",\"file\":\"common-main.c\",\"line\":38,\"evt\":\"1\",\"exe\":\"2.20.1.155.g426c96fcdb\"}\n+{\"event\":\"start\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621027\",\"file\":\"common-main.c\",\"line\":39,\"argv\":[\"git\",\"version\"]}\n+{\"event\":\"cmd_name\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621122\",\"file\":\"git.c\",\"line\":432,\"name\":\"version\",\"hierarchy\":\"version\"}\n+{\"event\":\"exit\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621236\",\"file\":\"git.c\",\"line\":662,\"t_abs\":0.001227,\"code\":0}\n+{\"event\":\"atexit\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621268\",\"file\":\"trace2/tr2_tgt_event.c\",\"line\":163,\"t_abs\":0.001265,\"code\":0}\n+------------\n+\n+== Enabling a Target\n+\n+A Trace2 Target is enabled when the corresponding environment variable\n+(`GIT_TR2`, `GIT_TR2_PERF`, or `GIT_TR2_EVENT`) is set.  The following\n+values are recognized.\n+\n+`0`::\n+`false`::\n+\n+\tDisables the target.\n+\n+`1`::\n+`true`::\n+\n+\tEnables the target and writes stream to `STDERR`.\n+\n+`[2-9]`::\n+\n+\tEnables the target and writes to the already opened file descriptor.\n+\n+`<absolute-pathname>`::\n+\n+\tEnables the target, opens and writes to the file in append mode.\n+\n+`af_unix:<absolute-pathname>`::\n+\n+\tEnables the target, opens and writes to a Unix Domain Socket\n+\t(on platforms that support them).\n+\n+== Trace2 API\n+\n+All public Trace2 functions and macros are defined in `trace2.h` and\n+`trace2.c`.  All public symbols are prefixed with `trace2_`.\n+\n+There are no public Trace2 data structures.\n+\n+The Trace2 code also defines a set of private functions and data types\n+in the `trace2/` directory.  These symbols are prefixed with `tr2_`\n+and should only be used by functions in `trace2.c`.\n+\n+== Conventions for Public Functions and Macros\n+\n+The functions defined by the Trace2 API are declared and documented\n+in `trace2.h`.  It defines the API functions and wrapper macros for\n+Trace2.\n+\n+Some functions have a `_fl()` suffix to indicate that they take `file`\n+and `line-number` arguments.\n+\n+Some functions have a `_va_fl()` suffix to indicate that they also\n+take a `va_list` argument.\n+\n+Some functions have a `_printf_fl()` suffix to indicate that they also\n+take a varargs argument.\n+\n+There are CPP wrapper macros and ifdefs to hide most of these details.\n+See `trace2.h` for more details.  The following discussion will only\n+describe the simplified forms.\n+\n+== Public API\n+\n+All Trace2 API functions send a messsage to all of the active\n+Trace2 Targets.  This section describes the set of available\n+messages.\n+\n+It helps to divide these functions into groups for discussion\n+purposes.\n+\n+=== Basic Command Messages\n+\n+These are concerned with the lifetime of the overall git process.\n+\n+`void trace2_initialize()`::\n+\n+\tDetermines if any Trace2 Targets should be enabled and\n+\tinitializes the Trace2 facility.  This includes starting the\n+\telapsed time clocks and thread local storage (TLS).\n++\n+This function emits a \"version\" message containing the version of git\n+and the Trace2 protocol.\n++\n+This function should be called from `main()` as early as possible in\n+the life of the process.\n+\n+`int trace2_is_enabled()`::\n+\n+\tReturns 1 if Trace2 is enabled (at least one target is\n+\tactive).\n+\n+`void trace2_cmd_start(int argc, const char **argv)`::\n+\n+\tEmits a \"start\" message containing the process command line\n+\targuments.\n+\n+`int trace2_cmd_exit(int exit_code)`::\n+\n+\tEmits an \"exit\" message containing the process exit-code and\n+\telapsed time.\n++\n+Returns the exit-code.\n+\n+`void trace2_cmd_error(const char *fmt, va_list ap)`::\n+\n+\tEmits an \"error\" message containing a formatted error message.\n+\n+`void trace2_cmd_path(const char *pathname)`::\n+\n+\tEmits a \"cmd_path\" message with the full pathname of the\n+\tcurrent process.\n+\n+=== Command Detail Messages\n+\n+These are concerned with describing the specific Git command\n+after the command line, config, and environment are inspected.\n+\n+`void trace2_cmd_name(const char *name)`::\n+\n+\tEmits a \"cmd_name\" message with the canonical name of the\n+\tcommand, for example \"status\" or \"checkout\".\n+\n+`void trace2_cmd_mode(const char *mode)`::\n+\n+\tEmits a \"cmd_mode\" message with a qualifier name to further\n+\tdescribe the current git command.\n++\n+This message is intended to be used with git commands having multiple\n+major modes.  For example, a \"checkout\" command can checkout a new\n+branch or it can checkout a single file, so the checkout code could\n+emit a cmd_mode message of \"branch\" or \"file\".\n+\n+`void trace2_cmd_alias(const char *alias, const char **argv_expansion)`::\n+\n+\tEmits an \"alias\" message containing the alias used and the\n+\targument expansion.\n+\n+`void trace2_def_param(const char *parameter, const char *value)`::\n+\n+\tEmits a \"def_param\" message containing a key/value pair.\n++\n+This message is intended to report some global aspect of the current\n+command, such as a configuration setting or command line switch that\n+significantly affects program performance or behavior, such as\n+`core.abbrev`, `status.showUntrackedFiles`, or `--no-ahead-behind`.\n+\n+`void trace2_cmd_list_config()`::\n+\n+\tEmits a \"def_param\" messages for \"important\" configuration\n+\tsettings.\n++\n+The environment variable `GIT_TR2_CONFIG_PARAMS` can be set to a\n+list of patterns of important configuration settings, for example:\n+`core.*,remote.*.url`.  This function will iterate over all config\n+settings and emit a \"def_param\" message for each match.\n+\n+`void trace2_cmd_set_config(const char *key, const char *value)`::\n+\n+\tEmits a \"def_param\" message for a specific configuration\n+\tsetting IFF it matches the `GIT_TR2_CONFIG_PARAMS` pattern.\n++\n+This is used to hook into `git_config_set()` and catch any\n+configuration changes and update a value previously reported by\n+`trace2_cmd_list_config()`.\n+\n+`void trace2_def_repo(struct repository *repo)`::\n+\n+\tRegisters a repository with the Trace2 layer.  Assigns a\n+\tunique \"repo-id\" to `repo->trace2_repo_id`.\n++\n+Emits a \"worktree\" messages containing the repo-id and the worktree\n+pathname.\n++\n+Region and data messages (described later) may refer to this repo-id.\n++\n+The main/top-level repository will have repo-id value 1 (aka \"r1\").\n++\n+The repo-id field is in anticipation of future in-proc submodule\n+repositories.\n+\n+=== Child Process Messages\n+\n+These are concerned with the various spawned child processes,\n+including shell scripts, git commands, editors, pagers, and hooks.\n+\n+`void trace2_child_start(struct child_process *cmd)`::\n+\n+\tEmits a \"child_start\" message containing the \"child-id\",\n+\t\"child-argv\", and \"child-classification\".\n++\n+Before calling this, set `cmd->trace2_child_class` to a name\n+describing the type of child process, for example \"editor\".\n++\n+This function assigns a unique \"child-id\" to `cmd->trace2_child_id`.\n+This field is used later during the \"child_exit\" message to associate\n+it with the \"child_start\" message.\n++\n+This function should be called before spawning the child process.\n+\n+`void trace2_child_exit(struct child_proess *cmd, int child_exit_code)`::\n+\n+\tEmits a \"child_exit\" message containing the \"child-id\",\n+\tthe child's elapsed time and exit-code.\n++\n+The reported elapsed time includes the process creation overhead and\n+time spend waiting for it to exit, so it may be slightly longer than\n+the time reported by the child itself.\n++\n+This function should be called after reaping the child process.\n+\n+`int trace2_exec(const char *exe, const char **argv)`::\n+\n+\tEmits a \"exec\" message containing the \"exec-id\" and the\n+\targv of the new process.\n++\n+This function should be called before calling one of the `exec()`\n+variants, such as `execvp()`.\n++\n+This function returns a unique \"exec-id\".  This value is used later\n+if the exec() fails and a \"exec-result\" message is necessary.\n+\n+`void trace2_exec_result(int exec_id, int error_code)`::\n+\n+\tEmits a \"exec_result\" message containing the \"exec-id\"\n+\tand the error code.\n++\n+On Unix-based systems, `exec()` does not return if successful.\n+This message is used to indicate that the `exec()` failed and\n+that the current program is continuing.\n+\n+=== Git Thread Messages\n+\n+These messages are concerned with Git thread usage.\n+\n+`void trace2_thread_start(const char *thread_name)`::\n+\n+\tEmits a \"thread_start\" message.\n++\n+The `thread_name` field should be a descriptive name, such as the\n+unique name of the thread-proc.  A unique \"thread-id\" will be added\n+to the name to uniquely identify thread instances.\n++\n+Region and data messages (described later) may refer to this thread\n+name.\n++\n+This function must be called by the thread-proc of the new thread\n+(so that TLS data is properly initialized) and not by the caller\n+of `pthread_create()`.\n+\n+`void trace2_thread_exit()`::\n+\n+\tEmits a \"thread_exit\" message containing the thread name\n+\tand the thread elapsed time.\n++\n+This function must be called by the thread-proc before it returns\n+(so that the coorect TLS data is used and cleaned up.  It should\n+not be called by the caller of `pthread_join()`.\n+\n+=== Region and Data Messages\n+\n+These are concerned with recording performance data\n+over regions or spans of code.\n+\n+`void trace2_region_enter(const char *category, const char *label, const struct repository *repo)`::\n+   \n+`void trace2_region_enter_printf(const char *category, const char *label, const struct repository *repo, const char *fmt, ...)`::\n+\n+`void trace2_region_enter_printf_va(const char *category, const char *label, const struct repository *repo, const char *fmt, va_list ap)`::\n+\n+\tEmits a thread-relative \"region_enter\" message with optional\n+\tprintf string.\n++\n+This function pushes a new region nesting stack level on the current\n+thread and starts a clock for the new stack frame.\n++\n+The `category` field is an arbitrary category name used to classify\n+regions by feature area, such as \"status\" or \"index\".  At this time\n+it is only just printed along with the rest of the message.  It may\n+be used in the future to filter messages.\n++\n+The `label` field is an arbitrary label used to describe the activity\n+being started, such as \"read_recursive\" or \"do_read_index\".\n++\n+The `repo` field, if set, will be used to get the \"repo-id\", so that\n+recursive oerations can be attributed to the correct repository.\n+\n+`void trace2_region_leave(const char *category, const char *label, const struct repository *repo)`::\n+\n+`void trace2_region_leave_printf(const char *category, const char *label, const struct repository *repo, const char *fmt, ...)`::\n+\n+`void trace2_region_leave_printf_va(const char *category, const char *label, const struct repository *repo, const char *fmt, va_list ap)`::\n+\n+\tEmits a thread-relative \"region_leave\" message with optional\n+\tprintf string.\n++\n+This function pops the region nesting stack on the current thread\n+and reports the elapsed time of the stack frame.\n++\n+The `category`, `label`, and `repo` fields are the same as above.\n+The `category` and `label` do not need to match the correpsonding\n+\"region_enter\" message, but it makes the data stream easier to\n+understand.\n+\n+`void trace2_data_string(const char *category, const struct repository *repo, const char *key, const char * value)`::\n+\n+`void trace2_data_intmax(const char *category, const struct repository *repo, const char *key, intmax value)`::\n+\n+`void trace2_data_json(const char *category, const struct repository *repo, const char *key, const struct json_writer *jw)`::\n+\n+\tEmits a region- and thread-relative \"data\" or \"data_json\" message.\n++\n+This is a key/value pair message containing information about the\n+current thread, region stack, and repository.  This could be used\n+to print the number of files in a directory during a multi-threaded\n+recursive tree walk.\n+\n+`void trace2_printf(const char *fmt, ...)`::\n+\n+`void trace2_printf_va(const char *fmt, va_list ap)`::\n+\n+\tEmits a region- and thread-relative \"printf\" message.\n+\n+== Trace2 Target Formats\n+\n+=== NORMAL Format\n+\n+NORMAL format is enabled when the `GIT_TR2` environment variable is\n+set.\n+\n+Events are written as lines of the form:\n+\n+------------\n+[<time> SP <filename>:<line> SP+] <event-name> [[SP] <event-message>] LF\n+------------\n+\n+`<event-name>`::\n+\n+\tis the event name.\n+\n+`<event-message>`::\n+\tis a free-form printf message intended for human consumption.\n++\n+Note that this may contain embedded LF or CRLF characters that are\n+not escaped, so the event may spill across multiple lines.\n+\n+If `GIT_TR2_BRIEF` is true, the `time`, `filename`, and `line` fields\n+are omitted.\n+\n+This target is intended to be more of a summary (like GIT_TRACE) and\n+less detailed than the other targets.  It ignores thread, region, and\n+data messages, for example.\n+\n+=== PERF Format\n+\n+PERF format is enabled when the `GIT_TR2_PERF` environment variable\n+is set.\n+\n+Events are written as lines of the form:\n+\n+------------\n+[<time> SP <filename>:<line> SP+\n+    BAR SP] d<depth> SP\n+    BAR SP <thread-name> SP+\n+    BAR SP <event-name> SP+\n+    BAR SP [r<repo-id>] SP+\n+    BAR SP [<t_abs>] SP+\n+    BAR SP [<t_rel>] SP+\n+    BAR SP [<category>] SP+\n+    BAR SP DOTS* <perf-event-message>\n+    LF\n+------------\n+\n+`<depth>`::\n+\tis the git process depth.  This is the number of parent\n+\tgit processes.  A top-level git command has depth value \"d0\".\n+\tA child of it has depth value \"d1\".  A second level child\n+\thas depth value \"d2\" and so on.\n+\n+`<thread-name>`::\n+\tis a unique name for the thread.  The primary thread\n+\tis called \"main\".  Other thread names are of the form \"th%d:%s\"\n+\tand include a unique number and the name of the thread-proc.\n+\n+`<event-name>`::\n+\tis the event name.\n+\n+`<repo-id>`::\n+\twhen present, is a number indicating the repository\n+\tin use.  A `def_repo` event is emitted when a repository is\n+\topened.  This defines the repo-id and associated worktree.\n+\tSubsequent repo-specific events will reference this repo-id.\n++\n+Currently, this is always \"r1\" for the main repository.\n+This field is in anticipation of in-proc submodules in the future.\n+\n+`<t_abs>`::\n+\twhen present, is the absolute time in seconds since the\n+\tprogram started.\n+\n+`<t_rel>`::\n+\twhen present, is time in seconds relative to the start of\n+\tthe current region.  For a thread-exit event, it is the elapsed\n+\ttime of the thread.\n+\n+`<category>`::\n+\tis present on region and data events and is used to\n+\tindicate a broad category, such as \"index\" or \"status\".\n+\n+`<perf-event-message>`::\n+\tis a free-form printf message intended for human consumption.\n+\n+------------\n+15:33:33.532712 wt-status.c:2310                  | d0 | main                     | region_enter | r1  |  0.126064 |           | status     | label:print\n+15:33:33.532712 wt-status.c:2331                  | d0 | main                     | region_leave | r1  |  0.127568 |  0.001504 | status     | label:print\n+------------\n+\n+If `GIT_TR2_PERF_BRIEF` is true, the `time`, `file`, and `line`\n+fields are omitted.\n+\n+------------\n+d0 | main                     | region_leave | r1  |  0.011717 |  0.009122 | index      | label:preload\n+------------\n+\n+The PERF target is intended for interactive performance analysis\n+during development and is quite noisy.\n+\n+=== EVENT Format\n+\n+EVENT format is enabled when the `GIT_TR2_EVENT` environment\n+variable is set.\n+\n+Each event is a JSON-object containing multiple key/value pairs\n+written as a single line and followed by a LF.\n+\n+------------\n+'{' <key> ':' <value> [',' <key> ':' <value>]* '}' LF\n+------------\n+\n+Some key/value pairs are common to all events and some are\n+event-specific.\n+\n+==== Common Key/Value Pairs\n+\n+The following key/value pairs are common to all events:\n+\n+------------\n+{\n+\t\"event\":\"version\",\n+\t\"sid\":\"1547659722619736-11614\",\n+\t\"thread\":\"main\",\n+\t\"time\":\"2019-01-16 17:28:42.620713\",\n+\t\"file\":\"common-main.c\",\n+\t\"line\":38,\n+\t...\n+}\n+------------\n+\n+`\"event\":<event>`::\n+\tis the event name.\n+\n+`\"sid\":<sid>`::\n+\tis the session-id.  This is a unique string to identify the\n+\tprocess instance to allow all events emitted by a process to\n+\tbe identified.  A session-id is used instead of a PID because\n+\tPIDs are recycled by the OS.  For child git processes, the\n+\tsession-id is prepended with the session-id of the parent git\n+\tprocess to allow parent-child relationships to be identified\n+\tduring post-processing.\n+\n+`\"thread\":<thread>`::\n+\tis the thread name.\n+\n+`\"time\":<time>`::\n+\tis the UTC time of the event.\n+\n+`\"file\":<filename>`::\n+\tis source file generating the event.\n+\n+`\"line\":<line-number>`::\n+\tis the integer source line number generating the event.\n+\n+`\"repo\":<repo-id>`::\n+\twhen present, is the integer repo-id as described previously.\n+\n+If `GIT_TR2_EVENT_BRIEF` is true, the `file` and `line` fields are omitted\n+from all events and the `time` field is only present on the \"start\" and\n+\"atexit\" events.\n+\n+==== Event-Specific Key/Value Pairs\n+\n+`\"version\"`::\n+\tThis event gives the version of the executable and the EVENT format.\n++\n+------------\n+{\n+\t\"event\":\"version\",\n+\t...\n+\t\"evt\":\"1\",\t\t       # EVENT format version\n+\t\"exe\":\"2.20.1.155.g426c96fcdb\" # git version\n+}\n+------------\n+\n+`\"start\"`::\n+\tThis event contains the complete argv received by main().\n++\n+------------\n+{\n+\t\"event\":\"start\",\n+\t...\n+\t\"argv\":[\"git\",\"version\"]\n+}\n+------------\n+\n+`\"exit\"`::\n+\tThis event is emitted when git calls `exit()`.\n++\n+------------\n+{\n+\t\"event\":\"exit\",\n+\t...\n+\t\"t_abs\":0.001227, # elapsed time in seconds\n+\t\"code\":0\t  # exit code\n+}\n+------------\n+\n+`\"atexit\"`::\n+\tThis event is emitted by the Trace2 `atexit` routine during\n+\tfinal shutdown.  It should be the last event emitted by the\n+\tprocess.\n++\n+(The elapsed time reported here is greater than the time reported in\n+the \"exit\" event because it runs after all other atexit tasks have\n+completed.)\n++\n+------------\n+{\n+\t\"event\":\"atexit\",\n+\t...\n+\t\"t_abs\":0.001227, # elapsed time in seconds\n+\t\"code\":0          # exit code\n+}\n+------------\n+\n+`\"signal\"`::\n+\tThis event is emitted when the program is terminated by a user\n+\tsignal.  Depending on the platform, the signal event may\n+\tprevent the \"atexit\" event from being generated.\n++\n+------------\n+{\n+\t\"event\":\"signal\",\n+\t...\n+\t\"t_abs\":0.001227,  # elapsed time in seconds\n+\t\"signal\":13        # SIGTERM, SIGINT, etc.\n+}\n+------------\n+\n+`\"error\"`::\n+\tThis event is emitted when one of the `error()`, `die()`,\n+\tor `usage()` functions are called.\n++\n+------------\n+{\n+\t\"event\":\"error\",\n+\t...\n+\t\"msg\":\"invalid option: --cahced\", # formatted error message\n+\t\"fmt\":\"invalid option: %s\"\t  # error format string\n+}\n+------------\n++\n+The error event may be emitted more than once.  The format string\n+allows post-processors to group errors by type without worrying\n+about specific error arguments.\n+\n+`\"cmd_path\"`::\n+\tThis event contains the discovered full path of the git\n+\texecutable (on platforms that are configured to resolve it).\n++\n+------------\n+{\n+\t\"event\":\"cmd_path\",\n+\t...\n+\t\"path\":\"C:/work/gfw/git.exe\"\n+}\n+------------\n+\n+`\"cmd_name\"`::\n+\tThis event contains the command name for this git process\n+\tand the hierarchy of commands from parent git processes.\n++\n+------------\n+{\n+\t\"event\":\"cmd_name\",\n+\t...\n+\t\"name\":\"pack-objects\",\n+\t\"hierarchy\":\"push/pack-objects\"\n+}\n+------------\n++\n+Normally, the \"name\" field contains the canonical name of the\n+command.  When a canonical name is not available, one of\n+these special values are used:\n++\n+------------\n+\"_query_\"            # \"git --html-path\"\n+\"_run_dashed_\"       # when \"git foo\" tries to run \"git-foo\"\n+\"_run_shell_alias_\"  # alias expansion to a shell command\n+\"_run_git_alias_\"    # alias expansion to a git command\n+\"_usage_\"            # usage error\n+------------\n+\n+`\"cmd_mode\"`::\n+\tThis event, when present, describes the command variant This\n+\tevent may be emitted more than once.\n++\n+------------\n+{\n+\t\"event\":\"cmd_mode\",\n+\t...\n+\t\"name\":\"branch\"\n+}\n+------------\n++\n+The \"name\" field is an arbitrary string to describe the command mode.\n+For example, checkout can checkout a branch or an individual file.\n+And these variations typically have different performance\n+characteristics that are not comparable.\n+\n+`\"alias\"`::\n+\tThis event is present when an alias is expanded.\n++\n+------------\n+{\n+\t\"event\":\"alias\",\n+\t...\n+\t\"alias\":\"l\",\t\t # registered alias\n+\t\"argv\":[\"log\",\"--graph\"] # alias expansion\n+}\n+------------\n+\n+`\"child_start\"`::\n+\tThis event describes a child process that is about to be\n+\tspawned.\n++\n+------------\n+{\n+\t\"event\":\"child_start\",\n+\t...\n+\t\"child_id\":2,\n+\t\"child_class\":\"?\",\n+\t\"use_shell\":false,\n+\t\"argv\":[\"git\",\"rev-list\",\"--objects\",\"--stdin\",\"--not\",\"--all\",\"--quiet\"]\n+\n+\t\"hook_name\":\"<hook_name>\"  # present when child_class is \"hook\"\n+\t\"cd\":\"<path>\"\t\t   # present when cd is required\n+}\n+------------\n++\n+The \"child_id\" field can be used to match this child_start with the\n+corresponding child_exit event.\n++\n+The \"child_class\" field is a rough classification, such as \"editor\",\n+\"pager\", \"transport/*\", and \"hook\".  Unclassified children are classified\n+with \"?\".\n+\n+`\"child_exit\"`::\n+\tThis event is generated after the current process has returned\n+\tfrom the waitpid() and collected the exit information from the\n+\tchild.\n++\n+------------\n+{\n+\t\"event\":\"child_exit\",\n+\t...\n+\t\"child_id\":2,\n+\t\"pid\":14708,\t # child PID\n+\t\"code\":0,\t # child exit-code\n+\t\"t_rel\":0.110605 # observed run-time of child process\n+}\n+------------\n++\n+Note that the session-id of the child process is not available to\n+the current/spawning process, so the child's PID is reported here as\n+a hint for post-processing.  (But it is only a hint because the child\n+proces may be a shell script which doesn't have a session-id.)\n++\n+Note that the `t_rel` field contains the observed run time in seconds\n+for the child process (starting before the fork/exec/spawn and\n+stopping after the waitpid() and includes OS process creation overhead).\n+So this time will be slightly larger than the atexit time reported by\n+the child process itself.\n+\n+`\"exec\"`::\n+\tThis event is generated before git attempts to `exec()`\n+\tanother command rather than starting a child process.\n++\n+------------\n+{\n+\t\"event\":\"exec\",\n+\t...\n+\t\"exec_id\":0,\n+\t\"exe\":\"git\",\n+\t\"argv\":[\"foo\", \"bar\"]\n+}\n+------------\n++\n+The \"exec_id\" field is a command-unique id and is only useful if the\n+`exec()` fails and a corresponding exec_result event is generated.\n+\n+`\"exec_result\"`::\n+\tThis event is generated if the `exec()` fails and control\n+\treturns to the current git command.\n++\n+------------\n+{\n+\t\"event\":\"exec_result\",\n+\t...\n+\t\"exec_id\":0,\n+\t\"code\":1      # error code (errno) from exec()\n+}\n+------------\n+\n+`\"thread_start\"`::\n+\tThis event is generated when a thread is started.  It is\n+\tgenerated from *within* the new thread's thread-proc (for TLS\n+\treasons).\n++\n+------------\n+{\n+\t\"event\":\"thread_start\",\n+\t...\n+\t\"thread\":\"th02:preload_thread\" # thread name\n+}\n+------------\n+\n+`\"thread_exit\"`::\n+\tThis event is generated when a thread exits.  It is generated\n+\tfrom *within* the thread's thread-proc (for TLS reasons).\n++\n+------------\n+{\n+\t\"event\":\"thread_exit\",\n+\t...\n+\t\"thread\":\"th02:preload_thread\", # thread name\n+\t\"t_rel\":0.007328                # thread elapsed time\n+}\n+------------\n+\n+`\"def_param\"`::\n+\tThis event is generated to log a global parameter.\n++\n+------------\n+{\n+\t\"event\":\"def_param\",\n+\t...\n+\t\"param\":\"core.abbrev\",\n+\t\"value\":\"7\"\n+}\n+------------\n+\n+`\"def_repo\"`::\n+\tThis event defines a repo-id and associates it with the root\n+\tof the worktree.\n++\n+------------\n+{\n+\t\"event\":\"def_repo\",\n+\t...\n+\t\"repo\":1,\n+\t\"worktree\":\"/Users/jeffhost/work/gfw\"\n+}\n+------------\n++\n+As stated earlier, the repo-id is currently always 1, so there will\n+only be one def_repo event.  Later, if in-proc submodules are\n+supported, a def_repo event should be emitted for each submodule\n+visited.\n+\n+`\"region_enter\"`::\n+\tThis event is generated when entering a region.\n++\n+------------\n+{\n+\t\"event\":\"region_enter\",\n+\t...\n+\t\"repo\":1,                # optional\n+\t\"nesting\":1,             # current region stack depth\n+\t\"category\":\"index\",      # optional\n+\t\"label\":\"do_read_index\", # optional\n+\t\"msg\":\".git/index\"       # optional\n+}\n+------------\n++\n+The `category` field may be used in a future enhancement to\n+do category-based filtering.\n++\n+The `GIT_TR2_EVENT_NESTING` environment variable can be used to\n+filter deeply nested regions and data events.  It defaults to \"2\".\n+\n+`\"region_leave\"`::\n+\tThis event is generated when leaving a region.\n++\n+------------\n+{\n+\t\"event\":\"region_leave\",\n+\t...\n+\t\"repo\":1,                # optional\n+\t\"t_rel\":0.002876,        # time spent in region in seconds\n+\t\"nesting\":1,             # region stack depth\n+\t\"category\":\"index\",      # optional\n+\t\"label\":\"do_read_index\", # optional\n+\t\"msg\":\".git/index\"       # optional\n+}\n+------------\n+\n+`\"data\"`::\n+\tThis event is generated to log a thread- and region-local\n+\tkey/value pair.\n++\n+------------\n+{\n+\t\"event\":\"data\",\n+\t...\n+\t\"repo\":1,              # optional\n+\t\"t_abs\":0.024107,      # absolute elapsed time\n+\t\"t_rel\":0.001031,      # elapsed time in region/thread\n+\t\"nesting\":2,           # region stack depth\n+\t\"category\":\"index\",\n+\t\"key\":\"read/cache_nr\",\n+\t\"value\":\"3552\"\n+}\n+------------\n++\n+The \"value\" field may be an integer or a string.\n+\n+`\"data-json\"`::\n+\tThis event is generated to log a pre-formatted JSON string\n+\tcontaining structured data.\n++\n+------------\n+{\n+\t\"event\":\"data_json\",\n+\t...\n+\t\"repo\":1,              # optional\n+\t\"t_abs\":0.015905,\n+\t\"t_rel\":0.015905,\n+\t\"nesting\":1,\n+\t\"category\":\"process\",\n+\t\"key\":\"windows/ancestry\",\n+\t\"value\":[\"bash.exe\",\"bash.exe\"]\n+}\n+------------\n+\n+== Example Trace2 API Usage\n+\n+Here is a hypothetical usage of the Trace2 API showing the intended\n+usage (without worrying about the actual Git details).\n+\n+Initialization::\n+\n+\tInitialization happens in `main()`.  Behind the scenes, an\n+\t`atexit` and `signal` handler are registered.\n++\n+----------------\n+int main(int argc, const char **argv)\n+{\n+\tint exit_code;\n+\n+\ttrace2_initialize();\n+\ttrace2_cmd_start(argv);\n+\n+\texit_code = cmd_main(argc, argv);\n+\n+\ttrace2_cmd_exit(exit_code);\n+\n+\treturn exit_code;\n+}\n+----------------\n+\n+Command Details::\n+\n+\tAfter the basics are established, additional command\n+\tinformation can be sent to Trace2 as it is discovered.\n++\n+----------------\n+int cmd_checkout(int argc, const char **argv)\n+{\n+\ttrace2_cmd_name(\"checkout\");\n+\ttrace2_cmd_mode(\"branch\");\n+\ttrace2_def_repo(the_repository);\n+\n+\t// emit \"def_param\" messages for \"interesting\" config settings.\n+\ttrace2_cmd_list_config();\n+\n+\tif (do_something())\n+\t    trace2_cmd_error(\"Path '%s': cannot do something\", path);\n+\n+\treturn 0;\n+}\n+----------------\n+\n+Child Processes::\n+\n+\tWrap code spawning child processes.\n++\n+----------------\n+void run_child(...)\n+{\n+\tint child_exit_code;\n+\tstruct child_process cmd = CHILD_PROCESS_INIT;\n+\t...\n+\tcmd.trace2_child_class = \"editor\";\n+\n+\ttrace2_child_start(&cmd);\n+\tchild_exit_code = spawn_child_and_wait_for_it();\n+\ttrace2_child_exit(&cmd, child_exit_code);\n+}\n+----------------\n++\n+For example, the following fetch command spawned ssh, index-pack,\n+rev-list, and gc.  This example also shows that fetch took\n+5.199 seconds and of that 4.932 was in ssh.\n++\n+----------------\n+$ export GIT_TR2_BRIEF=1\n+$ export GIT_TR2=~/log.normal\n+$ git fetch origin\n+...\n+----------------\n++\n+----------------\n+$ cat ~/log.normal\n+version 2.20.1.vfs.1.1.47.g534dbe1ad1\n+start git fetch origin\n+worktree /Users/jeffhost/work/gfw\n+cmd_name fetch (fetch)\n+child_start[0] ssh git@github.com ...\n+child_start[1] git index-pack ...\n+... (Trace2 events from child processes omitted)\n+child_exit[1] pid:14707 code:0 elapsed:0.076353\n+child_exit[0] pid:14706 code:0 elapsed:4.931869\n+child_start[2] git rev-list ...\n+... (Trace2 events from child process omitted)\n+child_exit[2] pid:14708 code:0 elapsed:0.110605\n+child_start[3] git gc --auto\n+... (Trace2 events from child process omitted)\n+child_exit[3] pid:14709 code:0 elapsed:0.006240\n+exit elapsed:5.198503 code:0\n+atexit elapsed:5.198541 code:0\n+----------------\n++\n+When a git process is a (direct or indirect) child of another\n+git process, it inherits Trace2 context information.  This\n+allows the child to print the command hierarchy.  This example\n+shows gc as child[3] of fetch.  When the gc process reports\n+its name as \"gc\", it also reports the hierarchy as \"fetch/gc\".\n+(In this example, trace2 messages from the child process is\n+indented for clarity.)\n++\n+----------------\n+$ export GIT_TR2_BRIEF=1\n+$ export GIT_TR2=~/log.normal\n+$ git fetch origin\n+...\n+----------------\n++\n+----------------\n+$ cat ~/log.normal\n+version 2.20.1.160.g5676107ecd.dirty\n+start git fetch official\n+worktree /Users/jeffhost/work/gfw\n+cmd_name fetch (fetch)\n+...\n+child_start[3] git gc --auto\n+    version 2.20.1.160.g5676107ecd.dirty\n+    start /Users/jeffhost/work/gfw/git gc --auto\n+    worktree /Users/jeffhost/work/gfw\n+    cmd_name gc (fetch/gc)\n+    exit elapsed:0.001959 code:0\n+    atexit elapsed:0.001997 code:0\n+child_exit[3] pid:20303 code:0 elapsed:0.007564\n+exit elapsed:3.868938 code:0\n+atexit elapsed:3.868970 code:0\n+----------------\n+\n+Regions::\n+\n+\tRegions can be use to time an interesting section of code.\n++\n+----------------\n+void wt_status_collect(struct wt_status *s)\n+{\n+\ttrace2_region_enter(\"status\", \"worktrees\", s->repo);\n+\twt_status_collect_changes_worktree(s);\n+\ttrace2_region_leave(\"status\", \"worktrees\", s->repo);\n+\n+\ttrace2_region_enter(\"status\", \"index\", s->repo);\n+\twt_status_collect_changes_index(s);\n+\ttrace2_region_leave(\"status\", \"index\", s->repo);\n+\n+\ttrace2_region_enter(\"status\", \"untracked\", s->repo);\n+\twt_status_collect_untracked(s);\n+\ttrace2_region_leave(\"status\", \"untracked\", s->repo);\n+}\n+\n+void wt_status_print(struct wt_status *s)\n+{\n+\ttrace2_region_enter(\"status\", \"print\", s->repo);\n+\tswitch (s->status_format) {\n+\t    ...\n+\t}\n+\ttrace2_region_leave(\"status\", \"print\", s->repo);\n+}\n+----------------\n++\n+In this example, scanning for untracked files ran from +0.012568 to\n++0.027149 (since the process started) and took 0.014581 seconds.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+\n+$ cat ~/log.perf\n+d0 | main                     | version      |     |           |           |            | 2.20.1.160.g5676107ecd.dirty\n+d0 | main                     | start        |     |           |           |            | git status\n+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n+d0 | main                     | cmd_name     |     |           |           |            | status (status)\n+...\n+d0 | main                     | region_enter | r1  |  0.010988 |           | status     | label:worktrees\n+d0 | main                     | region_leave | r1  |  0.011236 |  0.000248 | status     | label:worktrees\n+d0 | main                     | region_enter | r1  |  0.011260 |           | status     | label:index\n+d0 | main                     | region_leave | r1  |  0.012542 |  0.001282 | status     | label:index\n+d0 | main                     | region_enter | r1  |  0.012568 |           | status     | label:untracked\n+d0 | main                     | region_leave | r1  |  0.027149 |  0.014581 | status     | label:untracked\n+d0 | main                     | region_enter | r1  |  0.027411 |           | status     | label:print\n+d0 | main                     | region_leave | r1  |  0.028741 |  0.001330 | status     | label:print\n+d0 | main                     | exit         |     |  0.028778 |           |            | code:0\n+d0 | main                     | atexit       |     |  0.028809 |           |            | code:0\n+----------------\n++\n+Regions may be nested.  This causes messages to be indented in the\n+PERF target, for example.\n+Elapsed times are relative to the start of the correpsonding nesting\n+level as expected.  For example, if we add region message to:\n++\n+----------------\n+static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n+\tstruct index_state *istate, const char *base, int baselen,\n+\tstruct untracked_cache_dir *untracked, int check_only,\n+\tint stop_at_first_file, const struct pathspec *pathspec)\n+{\n+\tenum path_treatment state, subdir_state, dir_state = path_none;\n+\n+\ttrace2_region_enter_printf(\"dir\", \"read_recursive\", NULL, \"%.*s\", baselen, base);\n+\t...\n+\ttrace2_region_leave_printf(\"dir\", \"read_recursive\", NULL, \"%.*s\", baselen, base);\n+\treturn dir_state;\n+}\n+----------------\n++\n+We can further investigate the time spent scanning for untracked files.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+$ cat ~/log.perf\n+d0 | main                     | version      |     |           |           |            | 2.20.1.162.gb4ccea44db.dirty\n+d0 | main                     | start        |     |           |           |            | git status\n+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n+d0 | main                     | cmd_name     |     |           |           |            | status (status)\n+...\n+d0 | main                     | region_enter | r1  |  0.015047 |           | status     | label:untracked\n+d0 | main                     | region_enter |     |  0.015132 |           | dir        | ..label:read_recursive\n+d0 | main                     | region_enter |     |  0.016341 |           | dir        | ....label:read_recursive vcs-svn/\n+d0 | main                     | region_leave |     |  0.016422 |  0.000081 | dir        | ....label:read_recursive vcs-svn/\n+d0 | main                     | region_enter |     |  0.016446 |           | dir        | ....label:read_recursive xdiff/\n+d0 | main                     | region_leave |     |  0.016522 |  0.000076 | dir        | ....label:read_recursive xdiff/\n+d0 | main                     | region_enter |     |  0.016612 |           | dir        | ....label:read_recursive git-gui/\n+d0 | main                     | region_enter |     |  0.016698 |           | dir        | ......label:read_recursive git-gui/po/\n+d0 | main                     | region_enter |     |  0.016810 |           | dir        | ........label:read_recursive git-gui/po/glossary/\n+d0 | main                     | region_leave |     |  0.016863 |  0.000053 | dir        | ........label:read_recursive git-gui/po/glossary/\n+...\n+d0 | main                     | region_enter |     |  0.031876 |           | dir        | ....label:read_recursive builtin/\n+d0 | main                     | region_leave |     |  0.032270 |  0.000394 | dir        | ....label:read_recursive builtin/\n+d0 | main                     | region_leave |     |  0.032414 |  0.017282 | dir        | ..label:read_recursive\n+d0 | main                     | region_leave | r1  |  0.032454 |  0.017407 | status     | label:untracked\n+...\n+d0 | main                     | exit         |     |  0.034279 |           |            | code:0\n+d0 | main                     | atexit       |     |  0.034322 |           |            | code:0\n+----------------\n++\n+Trace2 regions are similar to the existing trace_performance_enter()\n+and trace_performance_leave() routines, but are thread safe and\n+maintain per-thread stacks of timers.\n+\n+Data Messages::\n+\n+\tData messages added to a region.\n++\n+----------------\n+int read_index_from(struct index_state *istate, const char *path,\n+\tconst char *gitdir)\n+{\n+\ttrace2_region_enter_printf(\"index\", \"do_read_index\", the_repository, \"%s\", path);\n+\n+\t...\n+\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/version\", istate->version);\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/cache_nr\", istate->cache_nr);\n+\n+\ttrace2_region_leave_printf(\"index\", \"do_read_index\", the_repository, \"%s\", path);\n+}\n+----------------\n++\n+This example shows that the index contained 3552 entries.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+$ cat ~/log.perf\n+d0 | main                     | version      |     |           |           |            | 2.20.1.156.gf9916ae094.dirty\n+d0 | main                     | start        |     |           |           |            | git status\n+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n+d0 | main                     | cmd_name     |     |           |           |            | status (status)\n+d0 | main                     | region_enter | r1  |  0.001791 |           | index      | label:do_read_index .git/index\n+d0 | main                     | data         | r1  |  0.002494 |  0.000703 | index      | ..read/version:2\n+d0 | main                     | data         | r1  |  0.002520 |  0.000729 | index      | ..read/cache_nr:3552\n+d0 | main                     | region_leave | r1  |  0.002539 |  0.000748 | index      | label:do_read_index .git/index\n+...\n+----------------\n+\n+Thread Events::\n+\n+\tThread messages added to a thread-proc.\n++\n+For example, the multithreaded preload-index code can be\n+instrumented with a region around the thread pool and then\n+per-thread start and exit events within the threadproc.\n++\n+----------------\n+static void *preload_thread(void *_data)\n+{\n+\t// start the per-thread clock and emit a message.\n+\ttrace2_thread_start(\"preload_thread\");\n+\n+\t// report which chunk of the array this thread was assigned.\n+\ttrace2_data_intmax(\"index\", the_repository, \"offset\", p->offset);\n+\ttrace2_data_intmax(\"index\", the_repository, \"count\", nr);\n+\n+\tdo {\n+\t    ...\n+\t} while (--nr > 0);\n+\t...\n+\n+\t// report elapsed time taken by this thread.\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+void preload_index(struct index_state *index,\n+\tconst struct pathspec *pathspec,\n+\tunsigned int refresh_flags)\n+{\n+\ttrace2_region_enter(\"index\", \"preload\", the_repository);\n+\n+\tfor (i = 0; i < threads; i++) {\n+\t    ... /* create thread */\n+\t}\n+\n+\tfor (i = 0; i < threads; i++) {\n+\t    ... /* join thread */\n+\t}\n+\n+\ttrace2_region_leave(\"index\", \"preload\", the_repository);\n+}\n+----------------\n++\n+In this example preload_index() was executed by the `main` thread\n+and started the `preload` region.  Seven threads, named\n+`th01:preload_thread` through `th07:preload_thread`, were started.\n+Events from each thread are atomically appended to the shared target\n+stream as they occur so they may appear in random order with respect\n+other threads. Finally, the main thread waits for the threads to\n+finish and leaves the region.\n++\n+Data events are tagged with the active thread name.  They are used\n+to report the per-thread parameters.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+$ cat ~/log.perf\n+...\n+d0 | main                     | region_enter | r1  |  0.002595 |           | index      | label:preload\n+d0 | th01:preload_thread      | thread_start |     |  0.002699 |           |            |\n+d0 | th02:preload_thread      | thread_start |     |  0.002721 |           |            |\n+d0 | th01:preload_thread      | data         | r1  |  0.002736 |  0.000037 | index      | offset:0\n+d0 | th02:preload_thread      | data         | r1  |  0.002751 |  0.000030 | index      | offset:2032\n+d0 | th03:preload_thread      | thread_start |     |  0.002711 |           |            |\n+d0 | th06:preload_thread      | thread_start |     |  0.002739 |           |            |\n+d0 | th01:preload_thread      | data         | r1  |  0.002766 |  0.000067 | index      | count:508\n+d0 | th06:preload_thread      | data         | r1  |  0.002856 |  0.000117 | index      | offset:2540\n+d0 | th03:preload_thread      | data         | r1  |  0.002824 |  0.000113 | index      | offset:1016\n+d0 | th04:preload_thread      | thread_start |     |  0.002710 |           |            |\n+d0 | th02:preload_thread      | data         | r1  |  0.002779 |  0.000058 | index      | count:508\n+d0 | th06:preload_thread      | data         | r1  |  0.002966 |  0.000227 | index      | count:508\n+d0 | th07:preload_thread      | thread_start |     |  0.002741 |           |            |\n+d0 | th07:preload_thread      | data         | r1  |  0.003017 |  0.000276 | index      | offset:3048\n+d0 | th05:preload_thread      | thread_start |     |  0.002712 |           |            |\n+d0 | th05:preload_thread      | data         | r1  |  0.003067 |  0.000355 | index      | offset:1524\n+d0 | th05:preload_thread      | data         | r1  |  0.003090 |  0.000378 | index      | count:508\n+d0 | th07:preload_thread      | data         | r1  |  0.003037 |  0.000296 | index      | count:504\n+d0 | th03:preload_thread      | data         | r1  |  0.002971 |  0.000260 | index      | count:508\n+d0 | th04:preload_thread      | data         | r1  |  0.002983 |  0.000273 | index      | offset:508\n+d0 | th04:preload_thread      | data         | r1  |  0.007311 |  0.004601 | index      | count:508\n+d0 | th05:preload_thread      | thread_exit  |     |  0.008781 |  0.006069 |            |\n+d0 | th01:preload_thread      | thread_exit  |     |  0.009561 |  0.006862 |            |\n+d0 | th03:preload_thread      | thread_exit  |     |  0.009742 |  0.007031 |            |\n+d0 | th06:preload_thread      | thread_exit  |     |  0.009820 |  0.007081 |            |\n+d0 | th02:preload_thread      | thread_exit  |     |  0.010274 |  0.007553 |            |\n+d0 | th07:preload_thread      | thread_exit  |     |  0.010477 |  0.007736 |            |\n+d0 | th04:preload_thread      | thread_exit  |     |  0.011657 |  0.008947 |            |\n+d0 | main                     | region_leave | r1  |  0.011717 |  0.009122 | index      | label:preload\n+...\n+d0 | main                     | exit         |     |  0.029996 |           |            | code:0\n+d0 | main                     | atexit       |     |  0.030027 |           |            | code:0\n+----------------\n++\n+In this example, the preload region took 0.009122 seconds.  The 7 threads\n+took between 0.006069 and 0.008947 seconds to work on their portion of\n+the index.  Thread \"th01\" worked on 508 items at offset 0.  Thread \"th02\"\n+worked on 508 items at offset 2032.  Thread \"th04\" worked on 508 itemts\n+at offset 508.\n++\n+This example also shows that thread names are assigned in a racy manner\n+as each thread starts and allocates TLS storage.\n+\n+== Future Work\n+\n+=== Relationship to the Existing Trace Api (api-trace.txt)\n+\n+There are a few issues to resolve before we can completely\n+switch to Trace2.\n+\n+* Updating existing tests that assume GIT_TRACE format messages.\n+\n+* How to best handle custom GIT_TRACE_<key> messages?\n+\n+** The GIT_TRACE_<key> mechanism allows each <key> to write to a\n+different file (in addition to just stderr).\n+\n+** Do we want to maintain that ability or simply write to the existing\n+Trace2 targets (and convert <key> to a \"category\").\n+\n-- \ngitgitgadget\n\n"},{"id":"368349","messageId":"c869de80636bd42a504fd5e898ad0449673b850b.1549043936.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 06/15] trace2:data: add trace2 sub-process classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-01T17:59:03Z","receivedAt":"2019-02-01T17:59:10Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 classification for long-running processes\nstarted in sub-process.c\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n sub-process.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/sub-process.c b/sub-process.c\nindex 8d2a1707cf..3f4af93555 100644\n--- a/sub-process.c\n+++ b/sub-process.c\n@@ -88,6 +88,7 @@ int subprocess_start(struct hashmap *hashmap, struct subprocess_entry *entry, co\n \tprocess->out = -1;\n \tprocess->clean_on_exit = 1;\n \tprocess->clean_on_exit_handler = subprocess_exit_handler;\n+\tprocess->trace2_child_class = \"subprocess\";\n \n \terr = start_command(process);\n \tif (err) {\n-- \ngitgitgadget\n\n"},{"id":"368350","messageId":"c2c1ea5ce35bef92770d6c46b850f3fbc607da67.1549043936.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 08/15] trace2:data: add trace2 hook classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-01T17:59:05Z","receivedAt":"2019-02-01T17:59:10Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nClassify certain child processes as hooks.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/am.c           | 1 +\n builtin/receive-pack.c | 4 ++++\n builtin/worktree.c     | 1 +\n sequencer.c            | 2 ++\n transport.c            | 1 +\n 5 files changed, 9 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 95370313b6..a81e61c1bc 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -468,6 +468,7 @@ static int run_post_rewrite_hook(const struct am_state *state)\n \n \tcp.in = xopen(am_path(state, \"rewritten\"), O_RDONLY);\n \tcp.stdout_to_stderr = 1;\n+\tcp.trace2_hook_name = \"post-rewrite\";\n \n \tret = run_command(&cp);\n \ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 33187bd8e9..6eef8575b9 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -694,6 +694,8 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,\n \tproc.argv = argv;\n \tproc.in = -1;\n \tproc.stdout_to_stderr = 1;\n+\tproc.trace2_hook_name = hook_name;\n+\n \tif (feed_state->push_options) {\n \t\tint i;\n \t\tfor (i = 0; i < feed_state->push_options->nr; i++)\n@@ -807,6 +809,7 @@ static int run_update_hook(struct command *cmd)\n \tproc.stdout_to_stderr = 1;\n \tproc.err = use_sideband ? -1 : 0;\n \tproc.argv = argv;\n+\tproc.trace2_hook_name = \"update\";\n \n \tcode = start_command(&proc);\n \tif (code)\n@@ -1190,6 +1193,7 @@ static void run_update_post_hook(struct command *commands)\n \tproc.no_stdin = 1;\n \tproc.stdout_to_stderr = 1;\n \tproc.err = use_sideband ? -1 : 0;\n+\tproc.trace2_hook_name = \"post-update\";\n \n \tif (!start_command(&proc)) {\n \t\tif (use_sideband)\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 3f9907fcc9..6cc094a453 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -402,6 +402,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\t\tcp.dir = path;\n \t\t\tcp.env = env;\n \t\t\tcp.argv = NULL;\n+\t\t\tcp.trace2_hook_name = \"post-checkout\";\n \t\t\targv_array_pushl(&cp.args, absolute_path(hook),\n \t\t\t\t\t oid_to_hex(&null_oid),\n \t\t\t\t\t oid_to_hex(&commit->object.oid),\ndiff --git a/sequencer.c b/sequencer.c\nindex 213815dbfc..c1e36196a1 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1103,6 +1103,7 @@ static int run_rewrite_hook(const struct object_id *oldoid,\n \tproc.argv = argv;\n \tproc.in = -1;\n \tproc.stdout_to_stderr = 1;\n+\tproc.trace2_hook_name = \"post-rewrite\";\n \n \tcode = start_command(&proc);\n \tif (code)\n@@ -3784,6 +3785,7 @@ static int pick_commits(struct repository *r,\n \t\t\t\thook.in = open(rebase_path_rewritten_list(),\n \t\t\t\t\tO_RDONLY);\n \t\t\t\thook.stdout_to_stderr = 1;\n+\t\t\t\thook.trace2_hook_name = \"post-rewrite\";\n \t\t\t\targv_array_push(&hook.args, post_rewrite_hook);\n \t\t\t\targv_array_push(&hook.args, \"rebase\");\n \t\t\t\t/* we don't care if this hook failed */\ndiff --git a/transport.c b/transport.c\nindex 99678153c1..ed5a733c4a 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1061,6 +1061,7 @@ static int run_pre_push_hook(struct transport *transport,\n \n \tproc.argv = argv;\n \tproc.in = -1;\n+\tproc.trace2_hook_name = \"pre-push\";\n \n \tif (start_command(&proc)) {\n \t\tfinish_command(&proc);\n-- \ngitgitgadget\n\n"},{"id":"368351","messageId":"57b23542b3812222e7e36c63da8d85f257de5c6c.1549043936.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 09/15] trace2:data: add trace2 instrumentation to index read/write","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-01T17:59:06Z","receivedAt":"2019-02-01T17:59:11Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 events to measure reading and writing the index.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n read-cache.c | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 50 insertions(+), 1 deletion(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex bfff271a3d..240531d70d 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -2232,6 +2232,16 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)\n \t\tload_index_extensions(&p);\n \t}\n \tmunmap((void *)mmap, mmap_size);\n+\n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/version\",\n+\t\t\t   istate->version);\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/cache_nr\",\n+\t\t\t   istate->cache_nr);\n+\n \treturn istate->cache_nr;\n \n unmap:\n@@ -2263,9 +2273,17 @@ int read_index_from(struct index_state *istate, const char *path,\n \tif (istate->initialized)\n \t\treturn istate->cache_nr;\n \n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_region_enter_printf(\"index\", \"do_read_index\", the_repository,\n+\t\t\t\t   \"%s\", path);\n \ttrace_performance_enter();\n \tret = do_read_index(istate, path, 0);\n \ttrace_performance_leave(\"read cache %s\", path);\n+\ttrace2_region_leave_printf(\"index\", \"do_read_index\", the_repository,\n+\t\t\t\t   \"%s\", path);\n \n \tsplit_index = istate->split_index;\n \tif (!split_index || is_null_oid(&split_index->base_oid)) {\n@@ -2281,7 +2299,11 @@ int read_index_from(struct index_state *istate, const char *path,\n \n \tbase_oid_hex = oid_to_hex(&split_index->base_oid);\n \tbase_path = xstrfmt(\"%s/sharedindex.%s\", gitdir, base_oid_hex);\n+\ttrace2_region_enter_printf(\"index\", \"shared/do_read_index\",\n+\t\t\t\t   the_repository, \"%s\", base_path);\n \tret = do_read_index(split_index->base, base_path, 1);\n+\ttrace2_region_leave_printf(\"index\", \"shared/do_read_index\",\n+\t\t\t\t   the_repository, \"%s\", base_path);\n \tif (!oideq(&split_index->base_oid, &split_index->base->oid))\n \t\tdie(_(\"broken index, expect %s in %s, got %s\"),\n \t\t    base_oid_hex, base_path,\n@@ -2988,6 +3010,16 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \tistate->timestamp.sec = (unsigned int)st.st_mtime;\n \tistate->timestamp.nsec = ST_MTIME_NSEC(st);\n \ttrace_performance_since(start, \"write index, changed mask = %x\", istate->cache_changed);\n+\n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_data_intmax(\"index\", the_repository, \"write/version\",\n+\t\t\t   istate->version);\n+\ttrace2_data_intmax(\"index\", the_repository, \"write/cache_nr\",\n+\t\t\t   istate->cache_nr);\n+\n \treturn 0;\n }\n \n@@ -3007,7 +3039,18 @@ static int commit_locked_index(struct lock_file *lk)\n static int do_write_locked_index(struct index_state *istate, struct lock_file *lock,\n \t\t\t\t unsigned flags)\n {\n-\tint ret = do_write_index(istate, lock->tempfile, 0);\n+\tint ret;\n+\n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_region_enter_printf(\"index\", \"do_write_index\", the_repository,\n+\t\t\t\t   \"%s\", lock->tempfile->filename.buf);\n+\tret = do_write_index(istate, lock->tempfile, 0);\n+\ttrace2_region_leave_printf(\"index\", \"do_write_index\", the_repository,\n+\t\t\t\t   \"%s\", lock->tempfile->filename.buf);\n+\n \tif (ret)\n \t\treturn ret;\n \tif (flags & COMMIT_LOCK)\n@@ -3092,7 +3135,13 @@ static int write_shared_index(struct index_state *istate,\n \tint ret;\n \n \tmove_cache_to_base_index(istate);\n+\n+\ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n+\t\t\t\t   the_repository, \"%s\", (*temp)->filename.buf);\n \tret = do_write_index(si->base, *temp, 1);\n+\ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n+\t\t\t\t   the_repository, \"%s\", (*temp)->filename.buf);\n+\n \tif (ret)\n \t\treturn ret;\n \tret = adjust_shared_perm(get_tempfile_path(*temp));\n-- \ngitgitgadget\n\n"},{"id":"368352","messageId":"6a82426a83328cf30f6eec436bb365119dee078c.1549043937.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 11/15] trace2:data: add subverb to checkout command","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-01T17:59:08Z","receivedAt":"2019-02-01T17:59:13Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/checkout.c | 7 +++++++\n 1 file changed, 7 insertions(+)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 6fadf412e8..f911c88bb4 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -262,6 +262,8 @@ static int checkout_paths(const struct checkout_opts *opts,\n \tstruct lock_file lock_file = LOCK_INIT;\n \tint nr_checkouts = 0;\n \n+\ttrace2_cmd_mode(opts->patch_mode ? \"patch\" : \"path\");\n+\n \tif (opts->track != BRANCH_TRACK_UNSPECIFIED)\n \t\tdie(_(\"'%s' cannot be used with updating paths\"), \"--track\");\n \n@@ -952,6 +954,9 @@ static int switch_branches(const struct checkout_opts *opts,\n \tvoid *path_to_free;\n \tstruct object_id rev;\n \tint flag, writeout_error = 0;\n+\n+\ttrace2_cmd_mode(\"branch\");\n+\n \tmemset(&old_branch_info, 0, sizeof(old_branch_info));\n \told_branch_info.path = path_to_free = resolve_refdup(\"HEAD\", 0, &rev, &flag);\n \tif (old_branch_info.path)\n@@ -1189,6 +1194,8 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)\n \tint status;\n \tstruct strbuf branch_ref = STRBUF_INIT;\n \n+\ttrace2_cmd_mode(\"unborn\");\n+\n \tif (!opts->new_branch)\n \t\tdie(_(\"You are on a branch yet to be born\"));\n \tstrbuf_addf(&branch_ref, \"refs/heads/%s\", opts->new_branch);\n-- \ngitgitgadget\n\n"},{"id":"368353","messageId":"6120ce1bbe739653eb1e19fc550ea0a2db320b66.1549043936.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 02/15] trace2: create new combined trace facility","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-01T17:59:00Z","receivedAt":"2019-02-01T17:59:15Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate a new unified tracing facility for git.  The eventual intent is to\nreplace the current trace_printf* and trace_performance* routines with a\nunified set of git_trace2* routines.\n\nIn addition to the usual printf-style API, trace2 provides higer-level\nevent verbs with fixed-fields allowing structured data to be written.\nThis makes post-processing and analysis easier for external tools.\n\nTrace2 defines 3 output targets.  These are set using the environment\nvariables \"GIT_TR2\", \"GIT_TR2_PERF\", and \"GIT_TR2_EVENT\".  These may be\nset to \"1\" or to an absolute pathname (just like the current GIT_TRACE).\n\n* GIT_TR2 is intended to be a replacement for GIT_TRACE and logs command\n  summary data.\n\n* GIT_TR2_PERF is intended as a replacement for GIT_TRACE_PERFORMANCE.\n  It extends the output with columns for the command process, thread,\n  repo, absolute and relative elapsed times.  It reports events for\n  child process start/stop, thread start/stop, and per-thread function\n  nesting.\n\n* GIT_TR2_EVENT is a new structured format. It writes event data as a\n  series of JSON records.\n\nCalls to trace2 functions log to any of the 3 output targets enabled\nwithout the need to call different trace_printf* or trace_performance*\nroutines.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                      |  14 +-\n builtin/submodule--helper.c   |   9 +-\n cache.h                       |   1 +\n common-main.c                 |  12 +-\n compat/mingw.c                |  11 +-\n compat/mingw.h                |   3 +-\n config.c                      |   2 +\n exec-cmd.c                    |   2 +\n git-compat-util.h             |   7 +\n git.c                         |  65 +++\n remote-curl.c                 |   7 +\n repository.c                  |   2 +\n repository.h                  |   3 +\n run-command.c                 |  59 ++-\n run-command.h                 |  13 +-\n sh-i18n--envsubst.c           |   3 +\n submodule.c                   |  11 +-\n t/helper/test-parse-options.c |   3 +\n t/helper/test-tool.c          |   3 +\n t/t0001-init.sh               |   1 +\n trace2.c                      | 762 ++++++++++++++++++++++++++++++++++\n trace2.h                      | 371 +++++++++++++++++\n trace2/tr2_cfg.c              |  90 ++++\n trace2/tr2_cfg.h              |  19 +\n trace2/tr2_cmd_name.c         |  30 ++\n trace2/tr2_cmd_name.h         |  24 ++\n trace2/tr2_dst.c              | 197 +++++++++\n trace2/tr2_dst.h              |  36 ++\n trace2/tr2_sid.c              |  67 +++\n trace2/tr2_sid.h              |  18 +\n trace2/tr2_tbuf.c             |  32 ++\n trace2/tr2_tbuf.h             |  23 +\n trace2/tr2_tgt.h              | 133 ++++++\n trace2/tr2_tgt_event.c        | 589 ++++++++++++++++++++++++++\n trace2/tr2_tgt_normal.c       | 324 +++++++++++++++\n trace2/tr2_tgt_perf.c         | 535 ++++++++++++++++++++++++\n trace2/tr2_tls.c              | 164 ++++++++\n trace2/tr2_tls.h              |  97 +++++\n usage.c                       |  31 ++\n 39 files changed, 3755 insertions(+), 18 deletions(-)\n create mode 100644 trace2.c\n create mode 100644 trace2.h\n create mode 100644 trace2/tr2_cfg.c\n create mode 100644 trace2/tr2_cfg.h\n create mode 100644 trace2/tr2_cmd_name.c\n create mode 100644 trace2/tr2_cmd_name.h\n create mode 100644 trace2/tr2_dst.c\n create mode 100644 trace2/tr2_dst.h\n create mode 100644 trace2/tr2_sid.c\n create mode 100644 trace2/tr2_sid.h\n create mode 100644 trace2/tr2_tbuf.c\n create mode 100644 trace2/tr2_tbuf.h\n create mode 100644 trace2/tr2_tgt.h\n create mode 100644 trace2/tr2_tgt_event.c\n create mode 100644 trace2/tr2_tgt_normal.c\n create mode 100644 trace2/tr2_tgt_perf.c\n create mode 100644 trace2/tr2_tls.c\n create mode 100644 trace2/tr2_tls.h\n\ndiff --git a/Makefile b/Makefile\nindex 6e8d017e8e..2ab7b73000 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1005,6 +1005,16 @@ LIB_OBJS += tempfile.o\n LIB_OBJS += thread-utils.o\n LIB_OBJS += tmp-objdir.o\n LIB_OBJS += trace.o\n+LIB_OBJS += trace2.o\n+LIB_OBJS += trace2/tr2_cfg.o\n+LIB_OBJS += trace2/tr2_cmd_name.o\n+LIB_OBJS += trace2/tr2_dst.o\n+LIB_OBJS += trace2/tr2_sid.o\n+LIB_OBJS += trace2/tr2_tbuf.o\n+LIB_OBJS += trace2/tr2_tgt_event.o\n+LIB_OBJS += trace2/tr2_tgt_normal.o\n+LIB_OBJS += trace2/tr2_tgt_perf.o\n+LIB_OBJS += trace2/tr2_tls.o\n LIB_OBJS += trailer.o\n LIB_OBJS += transport.o\n LIB_OBJS += transport-helper.o\n@@ -1581,7 +1591,9 @@ ifdef NO_INET_PTON\n \tLIB_OBJS += compat/inet_pton.o\n \tBASIC_CFLAGS += -DNO_INET_PTON\n endif\n-ifndef NO_UNIX_SOCKETS\n+ifdef NO_UNIX_SOCKETS\n+\tBASIC_CFLAGS += -DNO_UNIX_SOCKETS\n+else\n \tLIB_OBJS += unix-socket.o\n \tPROGRAM_OBJS += credential-cache.o\n \tPROGRAM_OBJS += credential-cache--daemon.o\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 0e140f176c..f10dc5134a 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -1815,11 +1815,10 @@ static int update_submodules(struct submodule_update_clone *suc)\n {\n \tint i;\n \n-\trun_processes_parallel(suc->max_jobs,\n-\t\t\t       update_clone_get_next_task,\n-\t\t\t       update_clone_start_failure,\n-\t\t\t       update_clone_task_finished,\n-\t\t\t       suc);\n+\trun_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task,\n+\t\t\t\t   update_clone_start_failure,\n+\t\t\t\t   update_clone_task_finished, suc, \"submodule\",\n+\t\t\t\t   \"parallel/update\");\n \n \t/*\n \t * We saved the output and put it out all at once now.\ndiff --git a/cache.h b/cache.h\nindex 009e8b3b15..3d12ffe01f 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -9,6 +9,7 @@\n #include \"gettext.h\"\n #include \"convert.h\"\n #include \"trace.h\"\n+#include \"trace2.h\"\n #include \"string-list.h\"\n #include \"pack-revindex.h\"\n #include \"hash.h\"\ndiff --git a/common-main.c b/common-main.c\nindex 3728f66b4c..6dbdc4adf2 100644\n--- a/common-main.c\n+++ b/common-main.c\n@@ -25,12 +25,18 @@ static void restore_sigpipe_to_default(void)\n \n int main(int argc, const char **argv)\n {\n+\tint result;\n+\n \t/*\n \t * Always open file descriptors 0/1/2 to avoid clobbering files\n \t * in die().  It also avoids messing up when the pipes are dup'ed\n \t * onto stdin/stdout/stderr in the child processes we spawn.\n \t */\n \tsanitize_stdfds();\n+\trestore_sigpipe_to_default();\n+\n+\ttrace2_initialize();\n+\ttrace2_cmd_start(argv);\n \n \tgit_resolve_executable_dir(argv[0]);\n \n@@ -40,7 +46,9 @@ int main(int argc, const char **argv)\n \n \tattr_start();\n \n-\trestore_sigpipe_to_default();\n+\tresult = cmd_main(argc, argv);\n+\n+\ttrace2_cmd_exit(result);\n \n-\treturn cmd_main(argc, argv);\n+\treturn result;\n }\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex b459e1a291..111fe68baf 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -1476,19 +1476,23 @@ static int try_shell_exec(const char *cmd, char *const *argv)\n \t\treturn 0;\n \tprog = path_lookup(interpr, 1);\n \tif (prog) {\n+\t\tint exec_id;\n \t\tint argc = 0;\n \t\tconst char **argv2;\n \t\twhile (argv[argc]) argc++;\n \t\tALLOC_ARRAY(argv2, argc + 1);\n \t\targv2[0] = (char *)cmd;\t/* full path to the script file */\n \t\tmemcpy(&argv2[1], &argv[1], sizeof(*argv) * argc);\n+\t\texec_id = trace2_exec(prog, argv2);\n \t\tpid = mingw_spawnv(prog, argv2, 1);\n \t\tif (pid >= 0) {\n \t\t\tint status;\n \t\t\tif (waitpid(pid, &status, 0) < 0)\n \t\t\t\tstatus = 255;\n+\t\t\ttrace2_exec_result(exec_id, status);\n \t\t\texit(status);\n \t\t}\n+\t\ttrace2_exec_result(exec_id, -1);\n \t\tpid = 1;\t/* indicate that we tried but failed */\n \t\tfree(prog);\n \t\tfree(argv2);\n@@ -1501,12 +1505,17 @@ int mingw_execv(const char *cmd, char *const *argv)\n \t/* check if git_command is a shell script */\n \tif (!try_shell_exec(cmd, argv)) {\n \t\tint pid, status;\n+\t\tint exec_id;\n \n+\t\texec_id = trace2_exec(cmd, (const char **)argv);\n \t\tpid = mingw_spawnv(cmd, (const char **)argv, 0);\n-\t\tif (pid < 0)\n+\t\tif (pid < 0) {\n+\t\t\ttrace2_exec_result(exec_id, -1);\n \t\t\treturn -1;\n+\t\t}\n \t\tif (waitpid(pid, &status, 0) < 0)\n \t\t\tstatus = 255;\n+\t\ttrace2_exec_result(exec_id, status);\n \t\texit(status);\n \t}\n \treturn -1;\ndiff --git a/compat/mingw.h b/compat/mingw.h\nindex 30d9fb3e36..4d73f8aa9d 100644\n--- a/compat/mingw.h\n+++ b/compat/mingw.h\n@@ -147,8 +147,7 @@ static inline int fcntl(int fd, int cmd, ...)\n \terrno = EINVAL;\n \treturn -1;\n }\n-/* bash cannot reliably detect negative return codes as failure */\n-#define exit(code) exit((code) & 0xff)\n+\n #define sigemptyset(x) (void)0\n static inline int sigaddset(sigset_t *set, int signum)\n { return 0; }\ndiff --git a/config.c b/config.c\nindex ff521eb27a..4beeb7795a 100644\n--- a/config.c\n+++ b/config.c\n@@ -2656,6 +2656,8 @@ int git_config_set_gently(const char *key, const char *value)\n void git_config_set(const char *key, const char *value)\n {\n \tgit_config_set_multivar(key, value, NULL, 0);\n+\n+\ttrace2_cmd_set_config(key, value);\n }\n \n /*\ndiff --git a/exec-cmd.c b/exec-cmd.c\nindex 4f81f44310..7deeab3039 100644\n--- a/exec-cmd.c\n+++ b/exec-cmd.c\n@@ -209,6 +209,8 @@ static int git_get_exec_path(struct strbuf *buf, const char *argv0)\n \t\treturn -1;\n \t}\n \n+\ttrace2_cmd_path(buf->buf);\n+\n \treturn 0;\n }\n \ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 29a19902aa..1d0aedc11f 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -1251,6 +1251,13 @@ static inline int is_missing_file_error(int errno_)\n \n extern int cmd_main(int, const char **);\n \n+/*\n+ * Intercept all calls to exit() and route them to trace2 to\n+ * optionally emit a message before calling the real exit().\n+ */\n+int trace2_cmd_exit_fl(const char *file, int line, int code);\n+#define exit(code) exit(trace2_cmd_exit_fl(__FILE__, __LINE__, (code)))\n+\n /*\n  * You can mark a stack variable with UNLEAK(var) to avoid it being\n  * reported as a leak by tools like LSAN or valgrind. The argument\ndiff --git a/git.c b/git.c\nindex 0ce0e13f0f..67ba8de9d1 100644\n--- a/git.c\n+++ b/git.c\n@@ -147,16 +147,20 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\t\tgit_set_exec_path(cmd + 1);\n \t\t\telse {\n \t\t\t\tputs(git_exec_path());\n+\t\t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\t\texit(0);\n \t\t\t}\n \t\t} else if (!strcmp(cmd, \"--html-path\")) {\n \t\t\tputs(system_path(GIT_HTML_PATH));\n+\t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"--man-path\")) {\n \t\t\tputs(system_path(GIT_MAN_PATH));\n+\t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"--info-path\")) {\n \t\t\tputs(system_path(GIT_INFO_PATH));\n+\t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"-p\") || !strcmp(cmd, \"--paginate\")) {\n \t\t\tuse_pager = 1;\n@@ -285,6 +289,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\t(*argv)++;\n \t\t\t(*argc)--;\n \t\t} else if (skip_prefix(cmd, \"--list-cmds=\", &cmd)) {\n+\t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\tif (!strcmp(cmd, \"parseopt\")) {\n \t\t\t\tstruct string_list list = STRING_LIST_INIT_DUP;\n \t\t\t\tint i;\n@@ -332,9 +337,14 @@ static int handle_alias(int *argcp, const char ***argv)\n \t\t\tcommit_pager_choice();\n \n \t\t\tchild.use_shell = 1;\n+\t\t\tchild.trace2_child_class = \"shell_alias\";\n \t\t\targv_array_push(&child.args, alias_string + 1);\n \t\t\targv_array_pushv(&child.args, (*argv) + 1);\n \n+\t\t\ttrace2_cmd_alias(alias_command, child.args.argv);\n+\t\t\ttrace2_cmd_list_config();\n+\t\t\ttrace2_cmd_name(\"_run_shell_alias_\");\n+\n \t\t\tret = run_command(&child);\n \t\t\tif (ret >= 0)   /* normal exit */\n \t\t\t\texit(ret);\n@@ -369,6 +379,9 @@ static int handle_alias(int *argcp, const char ***argv)\n \t\t/* insert after command name */\n \t\tmemcpy(new_argv + count, *argv + 1, sizeof(char *) * *argcp);\n \n+\t\ttrace2_cmd_alias(alias_command, new_argv);\n+\t\ttrace2_cmd_list_config();\n+\n \t\t*argv = new_argv;\n \t\t*argcp += count - 1;\n \n@@ -417,6 +430,8 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)\n \t\tsetup_work_tree();\n \n \ttrace_argv_printf(argv, \"trace: built-in: git\");\n+\ttrace2_cmd_name(p->cmd);\n+\ttrace2_cmd_list_config();\n \n \tvalidate_cache_entries(&the_index);\n \tstatus = p->fn(argc, argv, prefix);\n@@ -666,7 +681,14 @@ static void execv_dashed_external(const char **argv)\n \tcmd.clean_on_exit = 1;\n \tcmd.wait_after_clean = 1;\n \tcmd.silent_exec_failure = 1;\n+\tcmd.trace2_child_class = \"dashed\";\n \n+\ttrace2_cmd_name(\"_run_dashed_\");\n+\n+\t/*\n+\t * The code in run_command() logs trace2 child_start/child_exit\n+\t * events, so we do not need to report exec/exec_result events here.\n+\t */\n \ttrace_argv_printf(cmd.args.argv, \"trace: exec:\");\n \n \t/*\n@@ -676,6 +698,12 @@ static void execv_dashed_external(const char **argv)\n \t * the program.\n \t */\n \tstatus = run_command(&cmd);\n+\n+\t/*\n+\t * If the child process ran and we are now going to exit, emit a\n+\t * generic string as our trace2 command verb to indicate that we\n+\t * launched a dashed command.\n+\t */\n \tif (status >= 0)\n \t\texit(status);\n \telse if (errno != ENOENT)\n@@ -701,6 +729,43 @@ static int run_argv(int *argcp, const char ***argv)\n \t\tif (!done_alias)\n \t\t\thandle_builtin(*argcp, *argv);\n \n+#if 0 // TODO In GFW, need to amend a7924b655e940b06cb547c235d6bed9767929673 to include trace2_ and _tr2 lines.\n+\t\telse if (get_builtin(**argv)) {\n+\t\t\tstruct argv_array args = ARGV_ARRAY_INIT;\n+\t\t\tint i;\n+\n+\t\t\t/*\n+\t\t\t * The current process is committed to launching a\n+\t\t\t * child process to run the command named in (**argv)\n+\t\t\t * and exiting.  Log a generic string as the trace2\n+\t\t\t * command verb to indicate this.  Note that the child\n+\t\t\t * process will log the actual verb when it runs.\n+\t\t\t */\n+\t\t\ttrace2_cmd_name(\"_run_git_alias_\");\n+\n+\t\t\tif (get_super_prefix())\n+\t\t\t\tdie(\"%s doesn't support --super-prefix\", **argv);\n+\n+\t\t\tcommit_pager_choice();\n+\n+\t\t\targv_array_push(&args, \"git\");\n+\t\t\tfor (i = 0; i < *argcp; i++)\n+\t\t\t\targv_array_push(&args, (*argv)[i]);\n+\n+\t\t\ttrace_argv_printf(args.argv, \"trace: exec:\");\n+\n+\t\t\t/*\n+\t\t\t * if we fail because the command is not found, it is\n+\t\t\t * OK to return. Otherwise, we just pass along the status code.\n+\t\t\t */\n+\t\t\ti = run_command_v_opt_tr2(args.argv, RUN_SILENT_EXEC_FAILURE |\n+\t\t\t\t\t\t  RUN_CLEAN_ON_EXIT, \"git_alias\");\n+\t\t\tif (i >= 0 || errno != ENOENT)\n+\t\t\t\texit(i);\n+\t\t\tdie(\"could not execute builtin %s\", **argv);\n+\t\t}\n+#endif // a7924b655e940b06cb547c235d6bed9767929673\n+\n \t\t/* .. then try the external ones */\n \t\texecv_dashed_external(*argv);\n \ndiff --git a/remote-curl.c b/remote-curl.c\nindex 6ff9c66b90..ef7f4903c9 100644\n--- a/remote-curl.c\n+++ b/remote-curl.c\n@@ -1360,6 +1360,13 @@ int cmd_main(int argc, const char **argv)\n \tstring_list_init(&options.deepen_not, 1);\n \tstring_list_init(&options.push_options, 1);\n \n+\t/*\n+\t * Just report \"remote-curl\" here (folding all the various aliases\n+\t * (\"git-remote-http\", \"git-remote-https\", and etc.) here since they\n+\t * are all just copies of the same actual executable.\n+\t */\n+\ttrace2_cmd_name(\"remote-curl\");\n+\n \tremote = remote_get(argv[1]);\n \n \tif (argc > 2) {\ndiff --git a/repository.c b/repository.c\nindex 20c509a922..b8da4d0bbf 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -119,6 +119,8 @@ static int repo_init_gitdir(struct repository *repo, const char *gitdir)\n void repo_set_worktree(struct repository *repo, const char *path)\n {\n \trepo->worktree = real_pathdup(path, 1);\n+\n+\ttrace2_def_repo(repo);\n }\n \n static int read_and_verify_repository_format(struct repository_format *format,\ndiff --git a/repository.h b/repository.h\nindex 0e482b7d49..4e2ad6e0be 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -90,6 +90,9 @@ struct repository {\n \t/* Repository's current hash algorithm, as serialized on disk. */\n \tconst struct git_hash_algo *hash_algo;\n \n+\t/* A unique-id for tracing purposes. */\n+\tint trace2_repo_id;\n+\n \t/* Configurations */\n \n \t/* Indicate if a repository has a different 'commondir' from 'gitdir' */\ndiff --git a/run-command.c b/run-command.c\nindex 3db26b7b0e..3449db319b 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -219,9 +219,29 @@ static int exists_in_PATH(const char *file)\n \n int sane_execvp(const char *file, char * const argv[])\n {\n+#ifndef GIT_WINDOWS_NATIVE\n+\t/*\n+\t * execvp() doesn't return, so we all we can do is tell trace2\n+\t * what we are about to do and let it leave a hint in the log\n+\t * (unless of course the execvp() fails).\n+\t *\n+\t * we skip this for Windows because the compat layer already\n+\t * has to emulate the execvp() call anyway.\n+\t */\n+\tint exec_id = trace2_exec(file, (const char **)argv);\n+#endif\n+\n \tif (!execvp(file, argv))\n \t\treturn 0; /* cannot happen ;-) */\n \n+#ifndef GIT_WINDOWS_NATIVE\n+\t{\n+\t\tint ec = errno;\n+\t\ttrace2_exec_result(exec_id, ec);\n+\t\terrno = ec;\n+\t}\n+#endif\n+\n \t/*\n \t * When a command can't be found because one of the directories\n \t * listed in $PATH is unsearchable, execvp reports EACCES, but\n@@ -712,6 +732,7 @@ int start_command(struct child_process *cmd)\n \t\tcmd->err = fderr[0];\n \t}\n \n+\ttrace2_child_start(cmd);\n \ttrace_run_command(cmd);\n \n \tfflush(NULL);\n@@ -926,6 +947,8 @@ int start_command(struct child_process *cmd)\n #endif\n \n \tif (cmd->pid < 0) {\n+\t\ttrace2_child_exit(cmd, -1);\n+\n \t\tif (need_in)\n \t\t\tclose_pair(fdin);\n \t\telse if (cmd->in)\n@@ -964,13 +987,16 @@ int start_command(struct child_process *cmd)\n int finish_command(struct child_process *cmd)\n {\n \tint ret = wait_or_whine(cmd->pid, cmd->argv[0], 0);\n+\ttrace2_child_exit(cmd, ret);\n \tchild_process_clear(cmd);\n \treturn ret;\n }\n \n int finish_command_in_signal(struct child_process *cmd)\n {\n-\treturn wait_or_whine(cmd->pid, cmd->argv[0], 1);\n+\tint ret = wait_or_whine(cmd->pid, cmd->argv[0], 1);\n+\ttrace2_child_exit(cmd, ret);\n+\treturn ret;\n }\n \n \n@@ -992,7 +1018,18 @@ int run_command_v_opt(const char **argv, int opt)\n \treturn run_command_v_opt_cd_env(argv, opt, NULL, NULL);\n }\n \n+int run_command_v_opt_tr2(const char **argv, int opt, const char *tr2_class)\n+{\n+\treturn run_command_v_opt_cd_env_tr2(argv, opt, NULL, NULL, tr2_class);\n+}\n+\n int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env)\n+{\n+\treturn run_command_v_opt_cd_env_tr2(argv, opt, dir, env, NULL);\n+}\n+\n+int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,\n+\t\t\t\t const char *const *env, const char *tr2_class)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \tcmd.argv = argv;\n@@ -1004,6 +1041,7 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const\n \tcmd.clean_on_exit = opt & RUN_CLEAN_ON_EXIT ? 1 : 0;\n \tcmd.dir = dir;\n \tcmd.env = env;\n+\tcmd.trace2_child_class = tr2_class;\n \treturn run_command(&cmd);\n }\n \n@@ -1319,6 +1357,7 @@ int run_hook_ve(const char *const *env, const char *name, va_list args)\n \thook.env = env;\n \thook.no_stdin = 1;\n \thook.stdout_to_stderr = 1;\n+\thook.trace2_hook_name = name;\n \n \treturn run_command(&hook);\n }\n@@ -1807,3 +1846,21 @@ int run_processes_parallel(int n,\n \tpp_cleanup(&pp);\n \treturn 0;\n }\n+\n+int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task,\n+\t\t\t       start_failure_fn start_failure,\n+\t\t\t       task_finished_fn task_finished, void *pp_cb,\n+\t\t\t       const char *tr2_category, const char *tr2_label)\n+{\n+\tint result;\n+\n+\ttrace2_region_enter_printf(tr2_category, tr2_label, NULL, \"max:%d\",\n+\t\t\t\t   ((n < 1) ? online_cpus() : n));\n+\n+\tresult = run_processes_parallel(n, get_next_task, start_failure,\n+\t\t\t\t\ttask_finished, pp_cb);\n+\n+\ttrace2_region_leave(tr2_category, tr2_label, NULL);\n+\n+\treturn result;\n+}\ndiff --git a/run-command.h b/run-command.h\nindex 68f5369fc2..a6950691c0 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -10,6 +10,12 @@ struct child_process {\n \tstruct argv_array args;\n \tstruct argv_array env_array;\n \tpid_t pid;\n+\n+\tint trace2_child_id;\n+\tuint64_t trace2_child_us_start;\n+\tconst char *trace2_child_class;\n+\tconst char *trace2_hook_name;\n+\n \t/*\n \t * Using .in, .out, .err:\n \t * - Specify 0 for no redirections (child inherits stdin, stdout,\n@@ -73,12 +79,14 @@ extern int run_hook_ve(const char *const *env, const char *name, va_list args);\n #define RUN_USING_SHELL 16\n #define RUN_CLEAN_ON_EXIT 32\n int run_command_v_opt(const char **argv, int opt);\n-\n+int run_command_v_opt_tr2(const char **argv, int opt, const char *tr2_class);\n /*\n  * env (the environment) is to be formatted like environ: \"VAR=VALUE\".\n  * To unset an environment variable use just \"VAR\".\n  */\n int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env);\n+int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,\n+\t\t\t\t const char *const *env, const char *tr2_class);\n \n /**\n  * Execute the given command, sending \"in\" to its stdin, and capturing its\n@@ -220,5 +228,8 @@ int run_processes_parallel(int n,\n \t\t\t   start_failure_fn,\n \t\t\t   task_finished_fn,\n \t\t\t   void *pp_cb);\n+int run_processes_parallel_tr2(int n, get_next_task_fn, start_failure_fn,\n+\t\t\t       task_finished_fn, void *pp_cb,\n+\t\t\t       const char *tr2_category, const char *tr2_label);\n \n #endif\ndiff --git a/sh-i18n--envsubst.c b/sh-i18n--envsubst.c\nindex 09c6b445b9..cecfdd36c7 100644\n--- a/sh-i18n--envsubst.c\n+++ b/sh-i18n--envsubst.c\n@@ -14,6 +14,7 @@\n  */\n \n #include \"git-compat-util.h\"\n+#include \"trace2.h\"\n \n /* Substitution of environment variables in shell format strings.\n    Copyright (C) 2003-2007 Free Software Foundation, Inc.\n@@ -67,6 +68,8 @@ cmd_main (int argc, const char *argv[])\n   /* Default values for command line options.  */\n   /* unsigned short int show_variables = 0; */\n \n+  trace2_cmd_name(\"sh-i18n--envsubst\");\n+\n   switch (argc)\n \t{\n \tcase 1:\ndiff --git a/submodule.c b/submodule.c\nindex 7b5cea8522..a01c9dccf6 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1568,11 +1568,12 @@ int fetch_populated_submodules(struct repository *r,\n \n \tcalculate_changed_submodule_paths(r, &spf.changed_submodule_names);\n \tstring_list_sort(&spf.changed_submodule_names);\n-\trun_processes_parallel(max_parallel_jobs,\n-\t\t\t       get_next_submodule,\n-\t\t\t       fetch_start_failure,\n-\t\t\t       fetch_finish,\n-\t\t\t       &spf);\n+\trun_processes_parallel_tr2(max_parallel_jobs,\n+\t\t\t\t   get_next_submodule,\n+\t\t\t\t   fetch_start_failure,\n+\t\t\t\t   fetch_finish,\n+\t\t\t\t   &spf,\n+\t\t\t\t   \"submodule\", \"parallel/fetch\");\n \n \targv_array_clear(&spf.args);\n out:\ndiff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c\nindex 47fee660b8..cc88fba057 100644\n--- a/t/helper/test-parse-options.c\n+++ b/t/helper/test-parse-options.c\n@@ -2,6 +2,7 @@\n #include \"cache.h\"\n #include \"parse-options.h\"\n #include \"string-list.h\"\n+#include \"trace2.h\"\n \n static int boolean = 0;\n static int integer = 0;\n@@ -153,6 +154,8 @@ int cmd__parse_options(int argc, const char **argv)\n \tint i;\n \tint ret = 0;\n \n+\ttrace2_cmd_name(\"_parse_\");\n+\n \targc = parse_options(argc, (const char **)argv, prefix, options, usage, 0);\n \n \tif (length_cb.called) {\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 5b137874e1..4dbfff7dee 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -1,5 +1,6 @@\n #include \"git-compat-util.h\"\n #include \"test-tool.h\"\n+#include \"trace2.h\"\n \n struct test_cmd {\n \tconst char *name;\n@@ -80,6 +81,8 @@ int cmd_main(int argc, const char **argv)\n \t\tif (!strcmp(cmds[i].name, argv[1])) {\n \t\t\targv++;\n \t\t\targc--;\n+\t\t\ttrace2_cmd_name(cmds[i].name);\n+\t\t\ttrace2_cmd_list_config();\n \t\t\treturn cmds[i].fn(argc, argv);\n \t\t}\n \t}\ndiff --git a/t/t0001-init.sh b/t/t0001-init.sh\nindex 42a263cada..5e27604b24 100755\n--- a/t/t0001-init.sh\n+++ b/t/t0001-init.sh\n@@ -93,6 +93,7 @@ test_expect_success 'No extra GIT_* on alias scripts' '\n \t\tsed -n \\\n \t\t\t-e \"/^GIT_PREFIX=/d\" \\\n \t\t\t-e \"/^GIT_TEXTDOMAINDIR=/d\" \\\n+\t\t\t-e \"/^GIT_TR2_PARENT/d\" \\\n \t\t\t-e \"/^GIT_/s/=.*//p\" |\n \t\tsort\n \tEOF\ndiff --git a/trace2.c b/trace2.c\nnew file mode 100644\nindex 0000000000..d289965060\n--- /dev/null\n+++ b/trace2.c\n@@ -0,0 +1,762 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"json-writer.h\"\n+#include \"quote.h\"\n+#include \"run-command.h\"\n+#include \"sigchain.h\"\n+#include \"thread-utils.h\"\n+#include \"version.h\"\n+#include \"trace2/tr2_cfg.h\"\n+#include \"trace2/tr2_cmd_name.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_sid.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+static int trace2_enabled;\n+\n+static int tr2_next_child_id; /* modify under lock */\n+static int tr2_next_exec_id; /* modify under lock */\n+static int tr2_next_repo_id = 1; /* modify under lock. zero is reserved */\n+\n+/*\n+ * A table of the builtin TRACE2 targets.  Each of these may be independently\n+ * enabled or disabled.  Each TRACE2 API method will try to write an event to\n+ * *each* of the enabled targets.\n+ */\n+/* clang-format off */\n+static struct tr2_tgt *tr2_tgt_builtins[] =\n+{\n+\t&tr2_tgt_normal,\n+\t&tr2_tgt_perf,\n+\t&tr2_tgt_event,\n+\tNULL\n+};\n+/* clang-format on */\n+\n+/* clang-format off */\n+#define for_each_builtin(j, tgt_j)\t\t\t\\\n+\tfor (j = 0, tgt_j = tr2_tgt_builtins[j];\t\\\n+\t     tgt_j;\t\t\t\t\t\\\n+\t     j++, tgt_j = tr2_tgt_builtins[j])\n+/* clang-format on */\n+\n+/* clang-format off */\n+#define for_each_wanted_builtin(j, tgt_j)            \\\n+\tfor_each_builtin(j, tgt_j)                   \\\n+\t\tif (tr2_dst_trace_want(tgt_j->pdst))\n+/* clang-format on */\n+\n+/*\n+ * Force (rather than lazily) initialize any of the requested\n+ * builtin TRACE2 targets at startup (and before we've seen an\n+ * actual TRACE2 event call) so we can see if we need to setup\n+ * the TR2 and TLS machinery.\n+ *\n+ * Return the number of builtin targets enabled.\n+ */\n+static int tr2_tgt_want_builtins(void)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tint sum = 0;\n+\n+\tfor_each_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_init())\n+\t\t\tsum++;\n+\n+\treturn sum;\n+}\n+\n+/*\n+ * Properly terminate each builtin target.  Give each target\n+ * a chance to write a summary event and/or flush if necessary\n+ * and then close the fd.\n+ */\n+static void tr2_tgt_disable_builtins(void)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tfor_each_builtin (j, tgt_j)\n+\t\ttgt_j->pfn_term();\n+}\n+\n+static int tr2main_exit_code;\n+\n+/*\n+ * Our atexit routine should run after everything has finished.\n+ *\n+ * Note that events generated here might not actually appear if\n+ * we are writing to fd 1 or 2 and our atexit routine runs after\n+ * the pager's atexit routine (since it closes them to shutdown\n+ * the pipes).\n+ */\n+static void tr2main_atexit_handler(void)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Clear any unbalanced regions so that our atexit message\n+\t * does not appear nested.  This improves the appearance of\n+\t * the trace output if someone calls die(), for example.\n+\t */\n+\ttr2tls_pop_unwind_self();\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_atexit)\n+\t\t\ttgt_j->pfn_atexit(us_elapsed_absolute,\n+\t\t\t\t\t  tr2main_exit_code);\n+\n+\ttr2_tgt_disable_builtins();\n+\n+\ttr2tls_release();\n+\ttr2_sid_release();\n+\ttr2_cmd_name_release();\n+\ttr2_cfg_free_patterns();\n+\n+\ttrace2_enabled = 0;\n+}\n+\n+static void tr2main_signal_handler(int signo)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_signal)\n+\t\t\ttgt_j->pfn_signal(us_elapsed_absolute, signo);\n+\n+\tsigchain_pop(signo);\n+\traise(signo);\n+}\n+\n+void trace2_initialize_fl(const char *file, int line)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (trace2_enabled)\n+\t\treturn;\n+\n+\tif (!tr2_tgt_want_builtins())\n+\t\treturn;\n+\ttrace2_enabled = 1;\n+\n+\ttr2_sid_get();\n+\n+\tatexit(tr2main_atexit_handler);\n+\tsigchain_push(SIGPIPE, tr2main_signal_handler);\n+\ttr2tls_init();\n+\n+\t/*\n+\t * Emit 'version' message on each active builtin target.\n+\t */\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_version_fl)\n+\t\t\ttgt_j->pfn_version_fl(file, line);\n+}\n+\n+int trace2_is_enabled(void)\n+{\n+\treturn trace2_enabled;\n+}\n+\n+void trace2_cmd_start_fl(const char *file, int line, const char **argv)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_start_fl)\n+\t\t\ttgt_j->pfn_start_fl(file, line, argv);\n+}\n+\n+int trace2_cmd_exit_fl(const char *file, int line, int code)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tcode &= 0xff;\n+\n+\tif (!trace2_enabled)\n+\t\treturn code;\n+\n+\ttr2main_exit_code = code;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_exit_fl)\n+\t\t\ttgt_j->pfn_exit_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t   code);\n+\n+\treturn code;\n+}\n+\n+void trace2_cmd_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t    va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\t/*\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy (because an 'ap' can only be walked once).\n+\t */\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_error_va_fl)\n+\t\t\ttgt_j->pfn_error_va_fl(file, line, fmt, ap);\n+}\n+\n+void trace2_cmd_path_fl(const char *file, int line, const char *pathname)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_command_path_fl)\n+\t\t\ttgt_j->pfn_command_path_fl(file, line, pathname);\n+}\n+\n+void trace2_cmd_name_fl(const char *file, int line, const char *name)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tconst char *hierarchy;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\ttr2_cmd_name_append_hierarchy(name);\n+\thierarchy = tr2_cmd_name_get_hierarchy();\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_command_name_fl)\n+\t\t\ttgt_j->pfn_command_name_fl(file, line, name,\n+\t\t\t\t\t\t   hierarchy);\n+}\n+\n+void trace2_cmd_mode_fl(const char *file, int line, const char *mode)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_command_mode_fl)\n+\t\t\ttgt_j->pfn_command_mode_fl(file, line, mode);\n+}\n+\n+void trace2_cmd_alias_fl(const char *file, int line, const char *alias,\n+\t\t\t const char **argv)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_alias_fl)\n+\t\t\ttgt_j->pfn_alias_fl(file, line, alias, argv);\n+}\n+\n+void trace2_cmd_list_config_fl(const char *file, int line)\n+{\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\ttr2_cfg_list_config_fl(file, line);\n+}\n+\n+void trace2_cmd_set_config_fl(const char *file, int line, const char *key,\n+\t\t\t      const char *value)\n+{\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\ttr2_cfg_set_fl(file, line, key, value);\n+}\n+\n+void trace2_child_start_fl(const char *file, int line,\n+\t\t\t   struct child_process *cmd)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tcmd->trace2_child_id = tr2tls_locked_increment(&tr2_next_child_id);\n+\tcmd->trace2_child_us_start = us_now;\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_child_start_fl)\n+\t\t\ttgt_j->pfn_child_start_fl(file, line,\n+\t\t\t\t\t\t  us_elapsed_absolute, cmd);\n+}\n+\n+void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd,\n+\t\t\t  int child_exit_code)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_child;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tif (cmd->trace2_child_us_start)\n+\t\tus_elapsed_child = us_now - cmd->trace2_child_us_start;\n+\telse\n+\t\tus_elapsed_child = 0;\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_child_exit_fl)\n+\t\t\ttgt_j->pfn_child_exit_fl(file, line,\n+\t\t\t\t\t\t us_elapsed_absolute,\n+\t\t\t\t\t\t cmd->trace2_child_id, cmd->pid,\n+\t\t\t\t\t\t child_exit_code,\n+\t\t\t\t\t\t us_elapsed_child);\n+}\n+\n+int trace2_exec_fl(const char *file, int line, const char *exe,\n+\t\t   const char **argv)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tint exec_id;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn -1;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\texec_id = tr2tls_locked_increment(&tr2_next_exec_id);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_exec_fl)\n+\t\t\ttgt_j->pfn_exec_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t   exec_id, exe, argv);\n+\n+\treturn exec_id;\n+}\n+\n+void trace2_exec_result_fl(const char *file, int line, int exec_id, int code)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_exec_result_fl)\n+\t\t\ttgt_j->pfn_exec_result_fl(\n+\t\t\t\tfile, line, us_elapsed_absolute, exec_id, code);\n+}\n+\n+void trace2_thread_start_fl(const char *file, int line, const char *thread_name)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tif (tr2tls_is_main_thread()) {\n+\t\t/*\n+\t\t * We should only be called from the new thread's thread-proc,\n+\t\t * so this is technically a bug.  But in those cases where the\n+\t\t * main thread also runs the thread-proc function (or when we\n+\t\t * are built with threading disabled), we need to allow it.\n+\t\t *\n+\t\t * Convert this call to a region-enter so the nesting looks\n+\t\t * correct.\n+\t\t */\n+\t\ttrace2_region_enter_printf_fl(file, line, NULL, NULL, NULL,\n+\t\t\t\t\t      \"thread-proc on main: %s\",\n+\t\t\t\t\t      thread_name);\n+\t\treturn;\n+\t}\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\ttr2tls_create_self(thread_name);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_thread_start_fl)\n+\t\t\ttgt_j->pfn_thread_start_fl(file, line,\n+\t\t\t\t\t\t   us_elapsed_absolute);\n+}\n+\n+void trace2_thread_exit_fl(const char *file, int line)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_thread;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tif (tr2tls_is_main_thread()) {\n+\t\t/*\n+\t\t * We should only be called from the exiting thread's\n+\t\t * thread-proc, so this is technically a bug.  But in\n+\t\t * those cases where the main thread also runs the\n+\t\t * thread-proc function (or when we are built with\n+\t\t * threading disabled), we need to allow it.\n+\t\t *\n+\t\t * Convert this call to a region-leave so the nesting\n+\t\t * looks correct.\n+\t\t */\n+\t\ttrace2_region_leave_printf_fl(file, line, NULL, NULL, NULL,\n+\t\t\t\t\t      \"thread-proc on main\");\n+\t\treturn;\n+\t}\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Clear any unbalanced regions and then get the relative time\n+\t * for the outer-most region (which we pushed when the thread\n+\t * started).  This gives us the run time of the thread.\n+\t */\n+\ttr2tls_pop_unwind_self();\n+\tus_elapsed_thread = tr2tls_region_elasped_self(us_now);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_thread_exit_fl)\n+\t\t\ttgt_j->pfn_thread_exit_fl(file, line,\n+\t\t\t\t\t\t  us_elapsed_absolute,\n+\t\t\t\t\t\t  us_elapsed_thread);\n+\n+\ttr2tls_unset_self();\n+}\n+\n+void trace2_def_param_fl(const char *file, int line, const char *param,\n+\t\t\t const char *value)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_param_fl)\n+\t\t\ttgt_j->pfn_param_fl(file, line, param, value);\n+}\n+\n+void trace2_def_repo_fl(const char *file, int line, struct repository *repo)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tif (repo->trace2_repo_id)\n+\t\treturn;\n+\n+\trepo->trace2_repo_id = tr2tls_locked_increment(&tr2_next_repo_id);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_repo_fl)\n+\t\t\ttgt_j->pfn_repo_fl(file, line, repo);\n+}\n+\n+void trace2_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category, const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Print the region-enter message at the current nesting\n+\t * (indentation) level and then push a new level.\n+\t *\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy.\n+\t */\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_region_enter_printf_va_fl)\n+\t\t\ttgt_j->pfn_region_enter_printf_va_fl(\n+\t\t\t\tfile, line, us_elapsed_absolute, category,\n+\t\t\t\tlabel, repo, fmt, ap);\n+\n+\ttr2tls_push_self(us_now);\n+}\n+\n+void trace2_region_enter_fl(const char *file, int line, const char *category,\n+\t\t\t    const char *label, const struct repository *repo)\n+{\n+\ttrace2_region_enter_printf_va_fl(file, line, category, label, repo,\n+\t\t\t\t\t NULL, NULL);\n+}\n+\n+void trace2_region_enter_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category, const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_enter_printf_va_fl(file, line, category, label, repo, fmt,\n+\t\t\t\t\t ap);\n+\tva_end(ap);\n+}\n+\n+#ifndef HAVE_VARIADIC_MACROS\n+void trace2_region_enter_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo, const char *fmt,\n+\t\t\t\t...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_enter_printf_va_fl(NULL, 0, category, label, repo, fmt,\n+\t\t\t\t\t ap);\n+\tva_end(ap);\n+}\n+#endif\n+\n+void trace2_region_leave_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category, const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_region;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Get the elapsed time in the current region before we\n+\t * pop it off the stack.  Pop the stack.  And then print\n+\t * the perf message at the new (shallower) level so that\n+\t * it lines up with the corresponding push/enter.\n+\t */\n+\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n+\n+\ttr2tls_pop_self();\n+\n+\t/*\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy.\n+\t */\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_region_leave_printf_va_fl)\n+\t\t\ttgt_j->pfn_region_leave_printf_va_fl(\n+\t\t\t\tfile, line, us_elapsed_absolute,\n+\t\t\t\tus_elapsed_region, category, label, repo, fmt,\n+\t\t\t\tap);\n+}\n+\n+void trace2_region_leave_fl(const char *file, int line, const char *category,\n+\t\t\t    const char *label, const struct repository *repo)\n+{\n+\ttrace2_region_leave_printf_va_fl(file, line, category, label, repo,\n+\t\t\t\t\t NULL, NULL);\n+}\n+\n+void trace2_region_leave_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category, const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_leave_printf_va_fl(file, line, category, label, repo, fmt,\n+\t\t\t\t\t ap);\n+\tva_end(ap);\n+}\n+\n+#ifndef HAVE_VARIADIC_MACROS\n+void trace2_region_leave_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo, const char *fmt,\n+\t\t\t\t...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_leave_printf_va_fl(NULL, 0, category, label, repo, fmt,\n+\t\t\t\t\t ap);\n+\tva_end(ap);\n+}\n+#endif\n+\n+void trace2_data_string_fl(const char *file, int line, const char *category,\n+\t\t\t   const struct repository *repo, const char *key,\n+\t\t\t   const char *value)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_region;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_data_fl)\n+\t\t\ttgt_j->pfn_data_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t   us_elapsed_region, category, repo,\n+\t\t\t\t\t   key, value);\n+}\n+\n+void trace2_data_intmax_fl(const char *file, int line, const char *category,\n+\t\t\t   const struct repository *repo, const char *key,\n+\t\t\t   intmax_t value)\n+{\n+\tstruct strbuf buf_string = STRBUF_INIT;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tstrbuf_addf(&buf_string, \"%\" PRIdMAX, value);\n+\ttrace2_data_string_fl(file, line, category, repo, key, buf_string.buf);\n+\tstrbuf_release(&buf_string);\n+}\n+\n+void trace2_data_json_fl(const char *file, int line, const char *category,\n+\t\t\t const struct repository *repo, const char *key,\n+\t\t\t const struct json_writer *value)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_region;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_data_fl)\n+\t\t\ttgt_j->pfn_data_json_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t\tus_elapsed_region, category,\n+\t\t\t\t\t\trepo, key, value);\n+}\n+\n+void trace2_printf_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy.\n+\t */\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_printf_va_fl)\n+\t\t\ttgt_j->pfn_printf_va_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t\tfmt, ap);\n+}\n+\n+void trace2_printf_fl(const char *file, int line, const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_printf_va_fl(file, line, fmt, ap);\n+\tva_end(ap);\n+}\n+\n+#ifndef HAVE_VARIADIC_MACROS\n+void trace2_printf(const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_printf_va_fl(NULL, 0, fmt, ap);\n+\tva_end(ap);\n+}\n+#endif\ndiff --git a/trace2.h b/trace2.h\nnew file mode 100644\nindex 0000000000..fce9891f53\n--- /dev/null\n+++ b/trace2.h\n@@ -0,0 +1,371 @@\n+#ifndef TRACE2_H\n+#define TRACE2_H\n+\n+struct child_process;\n+struct repository;\n+struct json_writer;\n+\n+/*\n+ * The public TRACE2 routines are grouped into the following groups:\n+ *\n+ * [] trace2_initialize -- initialization.\n+ * [] trace2_cmd_*      -- emit command/control messages.\n+ * [] trace2_child*     -- emit child start/stop messages.\n+ * [] trace2_exec*      -- emit exec start/stop messages.\n+ * [] trace2_thread*    -- emit thread start/stop messages.\n+ * [] trace2_def*       -- emit definition/parameter mesasges.\n+ * [] trace2_region*    -- emit region nesting messages.\n+ * [] trace2_data*      -- emit region/thread/repo data messages.\n+ * [] trace2_printf*    -- legacy trace[1] messages.\n+ */\n+\n+/*\n+ * Initialize TRACE2 tracing facility if any of the builtin TRACE2\n+ * targets are enabled in the environment.  Emits a 'version' event.\n+ *\n+ * Cleanup/Termination is handled automatically by a registered\n+ * atexit() routine.\n+ */\n+void trace2_initialize_fl(const char *file, int line);\n+\n+#define trace2_initialize() trace2_initialize_fl(__FILE__, __LINE__)\n+\n+/*\n+ * Return true if trace2 is enabled.\n+ */\n+int trace2_is_enabled(void);\n+\n+/*\n+ * Emit a 'start' event with the original (unmodified) argv.\n+ */\n+void trace2_cmd_start_fl(const char *file, int line, const char **argv);\n+\n+#define trace2_cmd_start(argv) trace2_cmd_start_fl(__FILE__, __LINE__, (argv))\n+\n+/*\n+ * Emit an 'exit' event.\n+ *\n+ * Write the exit-code that will be passed to exit() or returned\n+ * from main().\n+ *\n+ * Use this prior to actually calling exit().\n+ * See \"#define exit()\" in git-compat-util.h\n+ */\n+int trace2_cmd_exit_fl(const char *file, int line, int code);\n+\n+#define trace2_cmd_exit(code) (trace2_cmd_exit_fl(__FILE__, __LINE__, (code)))\n+\n+/*\n+ * Emit an 'error' event.\n+ *\n+ * Write an error message to the TRACE2 targets.\n+ */\n+void trace2_cmd_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t    va_list ap);\n+\n+#define trace2_cmd_error_va(fmt, ap) \\\n+\ttrace2_cmd_error_va_fl(__FILE__, __LINE__, (fmt), (ap))\n+\n+/*\n+ * Emit a 'pathname' event with the canonical pathname of the current process\n+ * This gives post-processors a simple field to identify the command without\n+ * having to parse the argv.  For example, to distinguish invocations from\n+ * installed versus debug executables.\n+ */\n+void trace2_cmd_path_fl(const char *file, int line, const char *pathname);\n+\n+#define trace2_cmd_path(p) trace2_cmd_path_fl(__FILE__, __LINE__, (p))\n+\n+/*\n+ * Emit a 'cmd_name' event with the canonical name of the command.\n+ * This gives post-processors a simple field to identify the command\n+ * without having to parse the argv.\n+ */\n+void trace2_cmd_name_fl(const char *file, int line, const char *name);\n+\n+#define trace2_cmd_name(v) trace2_cmd_name_fl(__FILE__, __LINE__, (v))\n+\n+/*\n+ * Emit a 'cmd_mode' event to further describe the command being run.\n+ * For example, \"checkout\" can checkout a single file or can checkout a\n+ * different branch.  This gives post-processors a simple field to compare\n+ * equivalent commands without having to parse the argv.\n+ */\n+void trace2_cmd_mode_fl(const char *file, int line, const char *mode);\n+\n+#define trace2_cmd_mode(sv) trace2_cmd_mode_fl(__FILE__, __LINE__, (sv))\n+\n+/*\n+ * Emit an 'alias' expansion event.\n+ */\n+void trace2_cmd_alias_fl(const char *file, int line, const char *alias,\n+\t\t\t const char **argv);\n+\n+#define trace2_cmd_alias(alias, argv) \\\n+\ttrace2_cmd_alias_fl(__FILE__, __LINE__, (alias), (argv))\n+\n+/*\n+ * Emit one or more 'def_param' events for \"interesting\" configuration\n+ * settings.\n+ *\n+ * The environment variable \"GIT_TR2_CONFIG_PARAMS\" can be set to a\n+ * list of patterns considered important.  For example:\n+ *\n+ *    GIT_TR2_CONFIG_PARAMS=\"core.*,remote.*.url\"\n+ *\n+ * Note: this routine does a read-only iteration on the config data\n+ * (using read_early_config()), so it must not be called until enough\n+ * of the process environment has been established.  This includes the\n+ * location of the git and worktree directories, expansion of any \"-c\"\n+ * and \"-C\" command line options, and etc.\n+ */\n+void trace2_cmd_list_config_fl(const char *file, int line);\n+\n+#define trace2_cmd_list_config() trace2_cmd_list_config_fl(__FILE__, __LINE__)\n+\n+/*\n+ * Emit a \"def_param\" event for the given config key/value pair IF\n+ * we consider the key to be \"interesting\".\n+ *\n+ * Use this for new/updated config settings created/updated after\n+ * trace2_cmd_list_config() is called.\n+ */\n+void trace2_cmd_set_config_fl(const char *file, int line, const char *key,\n+\t\t\t      const char *value);\n+\n+#define trace2_cmd_set_config(k, v) \\\n+\ttrace2_cmd_set_config_fl(__FILE__, __LINE__, (k), (v))\n+\n+/*\n+ * Emit a 'child_start' event prior to spawning a child process.\n+ *\n+ * Before calling optionally set \"cmd->trace2_child_class\" to a string\n+ * describing the type of the child process.  For example, \"editor\" or\n+ * \"pager\".\n+ */\n+void trace2_child_start_fl(const char *file, int line,\n+\t\t\t   struct child_process *cmd);\n+\n+#define trace2_child_start(cmd) trace2_child_start_fl(__FILE__, __LINE__, (cmd))\n+\n+/*\n+ * Emit a 'child_exit' event after the child process completes.\n+ */\n+void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd,\n+\t\t\t  int child_exit_code);\n+\n+#define trace2_child_exit(cmd, code) \\\n+\ttrace2_child_exit_fl(__FILE__, __LINE__, (cmd), (code))\n+\n+/*\n+ * Emit an 'exec' event prior to calling one of exec(), execv(),\n+ * execvp(), and etc.  On Unix-derived systems, this will be the\n+ * last event emitted for the current process, unless the exec\n+ * fails.  On Windows, exec() behaves like 'child_start' and a\n+ * waitpid(), so additional events may be emitted.\n+ *\n+ * Returns the \"exec_id\".\n+ */\n+int trace2_exec_fl(const char *file, int line, const char *exe,\n+\t\t   const char **argv);\n+\n+#define trace2_exec(exe, argv) trace2_exec_fl(__FILE__, __LINE__, (exe), (argv))\n+\n+/*\n+ * Emit an 'exec_result' when possible.  On Unix-derived systems,\n+ * this should be called after exec() returns (which only happens\n+ * when there is an error starting the new process).  On Windows,\n+ * this should be called after the waitpid().\n+ *\n+ * The \"exec_id\" should be the value returned from trace2_exec().\n+ */\n+void trace2_exec_result_fl(const char *file, int line, int exec_id, int code);\n+\n+#define trace2_exec_result(id, code) \\\n+\ttrace2_exec_result_fl(__FILE__, __LINE__, (id), (code))\n+\n+/*\n+ * Emit a 'thread_start' event.  This must be called from inside the\n+ * thread-proc to set up the trace2 TLS data for the thread.\n+ *\n+ * Thread names should be descriptive, like \"preload_index\".\n+ * Thread names will be decorated with an instance number automatically.\n+ */\n+void trace2_thread_start_fl(const char *file, int line,\n+\t\t\t    const char *thread_name);\n+\n+#define trace2_thread_start(thread_name) \\\n+\ttrace2_thread_start_fl(__FILE__, __LINE__, (thread_name))\n+\n+/*\n+ * Emit a 'thread_exit' event.  This must be called from inside the\n+ * thread-proc to report thread-specific data and cleanup TLS data\n+ * for the thread.\n+ */\n+void trace2_thread_exit_fl(const char *file, int line);\n+\n+#define trace2_thread_exit() trace2_thread_exit_fl(__FILE__, __LINE__)\n+\n+/*\n+ * Emit a 'param' event.\n+ *\n+ * Write a \"<param> = <value>\" pair describing some aspect of the\n+ * run such as an important configuration setting or command line\n+ * option that significantly changes command behavior.\n+ */\n+void trace2_def_param_fl(const char *file, int line, const char *param,\n+\t\t\t const char *value);\n+\n+#define trace2_def_param(param, value) \\\n+\ttrace2_def_param_fl(__FILE__, __LINE__, (param), (value))\n+\n+/*\n+ * Tell trace2 about a newly instantiated repo object and assign\n+ * a trace2-repo-id to be used in subsequent activity events.\n+ *\n+ * Emits a 'worktree' event for this repo instance.\n+ */\n+void trace2_def_repo_fl(const char *file, int line, struct repository *repo);\n+\n+#define trace2_def_repo(repo) trace2_def_repo_fl(__FILE__, __LINE__, repo)\n+\n+/*\n+ * Emit a 'region_enter' event for <category>.<label> with optional\n+ * repo-id and printf message.\n+ *\n+ * Enter a new nesting level on the current thread and remember the\n+ * current time.  This controls the indenting of all subsequent events\n+ * on this thread.\n+ */\n+void trace2_region_enter_fl(const char *file, int line, const char *category,\n+\t\t\t    const char *label, const struct repository *repo);\n+\n+#define trace2_region_enter(category, label, repo) \\\n+\ttrace2_region_enter_fl(__FILE__, __LINE__, (category), (label), (repo))\n+\n+void trace2_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category, const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap);\n+\n+#define trace2_region_enter_printf_va(category, label, repo, fmt, ap)    \\\n+\ttrace2_region_enter_printf_va_fl(__FILE__, __LINE__, (category), \\\n+\t\t\t\t\t (label), (repo), (fmt), (ap))\n+\n+void trace2_region_enter_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category, const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...);\n+\n+#ifdef HAVE_VARIADIC_MACROS\n+#define trace2_region_enter_printf(category, label, repo, ...)                 \\\n+\ttrace2_region_enter_printf_fl(__FILE__, __LINE__, (category), (label), \\\n+\t\t\t\t      (repo), __VA_ARGS__)\n+#else\n+/* clang-format off */\n+__attribute__((format (region_enter_printf, 4, 5)))\n+void trace2_region_enter_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo, const char *fmt,\n+\t\t\t\t...);\n+/* clang-format on */\n+#endif\n+\n+/*\n+ * Emit a 'region_leave' event for <category>.<label> with optional\n+ * repo-id and printf message.\n+ *\n+ * Leave current nesting level and report the elapsed time spent\n+ * in this nesting level.\n+ */\n+void trace2_region_leave_fl(const char *file, int line, const char *category,\n+\t\t\t    const char *label, const struct repository *repo);\n+\n+#define trace2_region_leave(category, label, repo) \\\n+\ttrace2_region_leave_fl(__FILE__, __LINE__, (category), (label), (repo))\n+\n+void trace2_region_leave_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category, const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap);\n+\n+#define trace2_region_leave_printf_va(category, label, repo, fmt, ap)    \\\n+\ttrace2_region_leave_printf_va_fl(__FILE__, __LINE__, (category), \\\n+\t\t\t\t\t (label), (repo), (fmt), (ap))\n+\n+void trace2_region_leave_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category, const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...);\n+\n+#ifdef HAVE_VARIADIC_MACROS\n+#define trace2_region_leave_printf(category, label, repo, ...)                 \\\n+\ttrace2_region_leave_printf_fl(__FILE__, __LINE__, (category), (label), \\\n+\t\t\t\t      (repo), __VA_ARGS__)\n+#else\n+/* clang-format off */\n+__attribute__((format (region_leave_printf, 4, 5)))\n+void trace2_region_leave_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo, const char *fmt,\n+\t\t\t\t...);\n+/* clang-format on */\n+#endif\n+\n+/*\n+ * Emit a key-value pair 'data' event of the form <category>.<key> = <value>.\n+ * This event implicitly contains information about thread, nesting region,\n+ * and optional repo-id.\n+ *\n+ * On event-based TRACE2 targets, this generates a 'data' event suitable\n+ * for post-processing.  On printf-based TRACE2 targets, this is converted\n+ * into a fixed-format printf message.\n+ */\n+void trace2_data_string_fl(const char *file, int line, const char *category,\n+\t\t\t   const struct repository *repo, const char *key,\n+\t\t\t   const char *value);\n+\n+#define trace2_data_string(category, repo, key, value)                       \\\n+\ttrace2_data_string_fl(__FILE__, __LINE__, (category), (repo), (key), \\\n+\t\t\t      (value))\n+\n+void trace2_data_intmax_fl(const char *file, int line, const char *category,\n+\t\t\t   const struct repository *repo, const char *key,\n+\t\t\t   intmax_t value);\n+\n+#define trace2_data_intmax(category, repo, key, value)                       \\\n+\ttrace2_data_intmax_fl(__FILE__, __LINE__, (category), (repo), (key), \\\n+\t\t\t      (value))\n+\n+void trace2_data_json_fl(const char *file, int line, const char *category,\n+\t\t\t const struct repository *repo, const char *key,\n+\t\t\t const struct json_writer *jw);\n+\n+#define trace2_data_json(category, repo, key, value)                       \\\n+\ttrace2_data_json_fl(__FILE__, __LINE__, (category), (repo), (key), \\\n+\t\t\t    (value))\n+\n+/*\n+ * Emit a 'printf' event.\n+ *\n+ * Write an arbitrary formatted message to the TRACE2 targets.  These\n+ * text messages should be considered as human-readable strings without\n+ * any formatting guidelines.  Post-processors may choose to ignore\n+ * them.\n+ */\n+void trace2_printf_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t va_list ap);\n+\n+#define trace2_printf_va(fmt, ap) \\\n+\ttrace2_printf_va_fl(__FILE__, __LINE__, (fmt), (ap))\n+\n+void trace2_printf_fl(const char *file, int line, const char *fmt, ...);\n+\n+#ifdef HAVE_VARIADIC_MACROS\n+#define trace2_printf(...) trace2_printf_fl(__FILE__, __LINE__, __VA_ARGS__)\n+#else\n+/* clang-format off */\n+__attribute__((format (printf, 1, 2)))\n+void trace2_printf(const char *fmt, ...);\n+/* clang-format on */\n+#endif\n+\n+#endif /* TRACE2_H */\ndiff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c\nnew file mode 100644\nindex 0000000000..b329921ac5\n--- /dev/null\n+++ b/trace2/tr2_cfg.c\n@@ -0,0 +1,90 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"tr2_cfg.h\"\n+\n+#define TR2_ENVVAR_CFG_PARAM \"GIT_TR2_CONFIG_PARAMS\"\n+\n+static struct strbuf **tr2_cfg_patterns;\n+static int tr2_cfg_count_patterns;\n+static int tr2_cfg_loaded;\n+\n+/*\n+ * Parse a string containing a comma-delimited list of config keys\n+ * or wildcard patterns into a list of strbufs.\n+ */\n+static int tr2_cfg_load_patterns(void)\n+{\n+\tstruct strbuf **s;\n+\tconst char *envvar;\n+\n+\tif (tr2_cfg_loaded)\n+\t\treturn tr2_cfg_count_patterns;\n+\ttr2_cfg_loaded = 1;\n+\n+\tenvvar = getenv(TR2_ENVVAR_CFG_PARAM);\n+\tif (!envvar || !*envvar)\n+\t\treturn tr2_cfg_count_patterns;\n+\n+\ttr2_cfg_patterns = strbuf_split_buf(envvar, strlen(envvar), ',', -1);\n+\tfor (s = tr2_cfg_patterns; *s; s++) {\n+\t\tstruct strbuf *buf = *s;\n+\n+\t\tif (buf->len && buf->buf[buf->len - 1] == ',')\n+\t\t\tstrbuf_setlen(buf, buf->len - 1);\n+\t\tstrbuf_trim_trailing_newline(*s);\n+\t\tstrbuf_trim(*s);\n+\t}\n+\n+\ttr2_cfg_count_patterns = s - tr2_cfg_patterns;\n+\treturn tr2_cfg_count_patterns;\n+}\n+\n+void tr2_cfg_free_patterns(void)\n+{\n+\tif (tr2_cfg_patterns)\n+\t\tstrbuf_list_free(tr2_cfg_patterns);\n+\ttr2_cfg_count_patterns = 0;\n+\ttr2_cfg_loaded = 0;\n+}\n+\n+struct tr2_cfg_data {\n+\tconst char *file;\n+\tint line;\n+};\n+\n+/*\n+ * See if the given config key matches any of our patterns of interest.\n+ */\n+static int tr2_cfg_cb(const char *key, const char *value, void *d)\n+{\n+\tstruct strbuf **s;\n+\tstruct tr2_cfg_data *data = (struct tr2_cfg_data *)d;\n+\n+\tfor (s = tr2_cfg_patterns; *s; s++) {\n+\t\tstruct strbuf *buf = *s;\n+\t\tint wm = wildmatch(buf->buf, key, WM_CASEFOLD);\n+\t\tif (wm == WM_MATCH) {\n+\t\t\ttrace2_def_param_fl(data->file, data->line, key, value);\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void tr2_cfg_list_config_fl(const char *file, int line)\n+{\n+\tstruct tr2_cfg_data data = { file, line };\n+\n+\tif (tr2_cfg_load_patterns() > 0)\n+\t\tread_early_config(tr2_cfg_cb, &data);\n+}\n+\n+void tr2_cfg_set_fl(const char *file, int line, const char *key,\n+\t\t    const char *value)\n+{\n+\tstruct tr2_cfg_data data = { file, line };\n+\n+\tif (tr2_cfg_load_patterns() > 0)\n+\t\ttr2_cfg_cb(key, value, &data);\n+}\ndiff --git a/trace2/tr2_cfg.h b/trace2/tr2_cfg.h\nnew file mode 100644\nindex 0000000000..d9c98f64dd\n--- /dev/null\n+++ b/trace2/tr2_cfg.h\n@@ -0,0 +1,19 @@\n+#ifndef TR2_CFG_H\n+#define TR2_CFG_H\n+\n+/*\n+ * Iterate over all config settings and emit 'def_param' events for the\n+ * \"interesting\" ones to TRACE2.\n+ */\n+void tr2_cfg_list_config_fl(const char *file, int line);\n+\n+/*\n+ * Emit a \"def_param\" event for the given key/value pair IF we consider\n+ * the key to be \"interesting\".\n+ */\n+void tr2_cfg_set_fl(const char *file, int line, const char *key,\n+\t\t    const char *value);\n+\n+void tr2_cfg_free_patterns(void);\n+\n+#endif /* TR2_CFG_H */\ndiff --git a/trace2/tr2_cmd_name.c b/trace2/tr2_cmd_name.c\nnew file mode 100644\nindex 0000000000..e999592b4c\n--- /dev/null\n+++ b/trace2/tr2_cmd_name.c\n@@ -0,0 +1,30 @@\n+#include \"cache.h\"\n+#include \"trace2/tr2_cmd_name.h\"\n+\n+#define TR2_ENVVAR_PARENT_NAME \"GIT_TR2_PARENT_NAME\"\n+\n+static struct strbuf tr2cmdname_hierarchy = STRBUF_INIT;\n+\n+void tr2_cmd_name_append_hierarchy(const char *name)\n+{\n+\tconst char *parent_name = getenv(TR2_ENVVAR_PARENT_NAME);\n+\n+\tstrbuf_reset(&tr2cmdname_hierarchy);\n+\tif (parent_name && *parent_name) {\n+\t\tstrbuf_addstr(&tr2cmdname_hierarchy, parent_name);\n+\t\tstrbuf_addch(&tr2cmdname_hierarchy, '/');\n+\t}\n+\tstrbuf_addstr(&tr2cmdname_hierarchy, name);\n+\n+\tsetenv(TR2_ENVVAR_PARENT_NAME, tr2cmdname_hierarchy.buf, 1);\n+}\n+\n+const char *tr2_cmd_name_get_hierarchy(void)\n+{\n+\treturn tr2cmdname_hierarchy.buf;\n+}\n+\n+void tr2_cmd_name_release(void)\n+{\n+\tstrbuf_release(&tr2cmdname_hierarchy);\n+}\ndiff --git a/trace2/tr2_cmd_name.h b/trace2/tr2_cmd_name.h\nnew file mode 100644\nindex 0000000000..ab70b67a8e\n--- /dev/null\n+++ b/trace2/tr2_cmd_name.h\n@@ -0,0 +1,24 @@\n+#ifndef TR2_CMD_NAME_H\n+#define TR2_CMD_NAME_H\n+\n+/*\n+ * Append the current command name to the list being maintained\n+ * in the environment.\n+ *\n+ * The hierarchy for a top-level git command is just the current\n+ * command name.  For a child git process, the hierarchy includes the\n+ * names of the parent processes.\n+ *\n+ * The hierarchy for the current process will be exported to the\n+ * environment and inherited by child processes.\n+ */\n+void tr2_cmd_name_append_hierarchy(const char *name);\n+\n+/*\n+ * Get the command name hierarchy for the current process.\n+ */\n+const char *tr2_cmd_name_get_hierarchy(void);\n+\n+void tr2_cmd_name_release(void);\n+\n+#endif /* TR2_CMD_NAME_H */\ndiff --git a/trace2/tr2_dst.c b/trace2/tr2_dst.c\nnew file mode 100644\nindex 0000000000..b4f2b40e61\n--- /dev/null\n+++ b/trace2/tr2_dst.c\n@@ -0,0 +1,197 @@\n+#include \"cache.h\"\n+#include \"trace2/tr2_dst.h\"\n+\n+/*\n+ * If a Trace2 target cannot be opened for writing, we should issue a\n+ * warning to stderr, but this is very annoying if the target is a pipe\n+ * or socket and beyond the user's control -- especially since every\n+ * git command (and sub-command) will print the message.  So we silently\n+ * eat these warnings and just discard the trace data.\n+ *\n+ * Enable the following environment variable to see these warnings.\n+ */\n+#define TR2_ENVVAR_DST_DEBUG \"GIT_TR2_DST_DEBUG\"\n+\n+static int tr2_dst_want_warning(void)\n+{\n+\tstatic int tr2env_dst_debug = -1;\n+\n+\tif (tr2env_dst_debug == -1) {\n+\t\tconst char *env_value = getenv(TR2_ENVVAR_DST_DEBUG);\n+\t\tif (!env_value || !*env_value)\n+\t\t\ttr2env_dst_debug = 0;\n+\t\telse\n+\t\t\ttr2env_dst_debug = atoi(env_value) > 0;\n+\t}\n+\n+\treturn tr2env_dst_debug;\n+}\n+\n+void tr2_dst_trace_disable(struct tr2_dst *dst)\n+{\n+\tif (dst->need_close)\n+\t\tclose(dst->fd);\n+\tdst->fd = 0;\n+\tdst->initialized = 1;\n+\tdst->need_close = 0;\n+}\n+\n+static int tr2_dst_try_path(struct tr2_dst *dst, const char *tgt_value)\n+{\n+\tint fd = open(tgt_value, O_WRONLY | O_APPEND | O_CREAT, 0666);\n+\tif (fd == -1) {\n+\t\tif (tr2_dst_want_warning())\n+\t\t\twarning(\"trace2: could not open '%s' for '%s' tracing: %s\",\n+\t\t\t\ttgt_value, dst->env_var_name, strerror(errno));\n+\n+\t\ttr2_dst_trace_disable(dst);\n+\t\treturn 0;\n+\t}\n+\n+\tdst->fd = fd;\n+\tdst->need_close = 1;\n+\tdst->initialized = 1;\n+\n+\treturn dst->fd;\n+}\n+\n+#ifndef NO_UNIX_SOCKETS\n+#define PREFIX_AF_UNIX \"af_unix:\"\n+#define PREFIX_AF_UNIX_LEN (8)\n+\n+static int tr2_dst_try_unix_domain_socket(struct tr2_dst *dst,\n+\t\t\t\t\t  const char *tgt_value)\n+{\n+\tint fd;\n+\tstruct sockaddr_un sa;\n+\tconst char *path = tgt_value + PREFIX_AF_UNIX_LEN;\n+\tint path_len = strlen(path);\n+\n+\tif (!is_absolute_path(path) || path_len >= sizeof(sa.sun_path)) {\n+\t\tif (tr2_dst_want_warning())\n+\t\t\twarning(\"trace2: invalid AF_UNIX path '%s' for '%s' tracing\",\n+\t\t\t\tpath, dst->env_var_name);\n+\n+\t\ttr2_dst_trace_disable(dst);\n+\t\treturn 0;\n+\t}\n+\n+\tsa.sun_family = AF_UNIX;\n+\tstrlcpy(sa.sun_path, path, sizeof(sa.sun_path));\n+\tif ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1 ||\n+\t    connect(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {\n+\t\tif (tr2_dst_want_warning())\n+\t\t\twarning(\"trace2: could not connect to socket '%s' for '%s' tracing: %s\",\n+\t\t\t\tpath, dst->env_var_name, strerror(errno));\n+\n+\t\ttr2_dst_trace_disable(dst);\n+\t\treturn 0;\n+\t}\n+\n+\tdst->fd = fd;\n+\tdst->need_close = 1;\n+\tdst->initialized = 1;\n+\n+\treturn dst->fd;\n+}\n+#endif\n+\n+static void tr2_dst_malformed_warning(struct tr2_dst *dst,\n+\t\t\t\t      const char *tgt_value)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf, \"trace2: unknown trace value for '%s': '%s'\",\n+\t\t    dst->env_var_name, tgt_value);\n+\tstrbuf_addstr(\n+\t\t&buf,\n+\t\t\"\\n         If you want to trace into a file, then please set it\"\n+\t\t\"\\n         to an absolute pathname.\");\n+#ifndef NO_UNIX_SOCKETS\n+\tstrbuf_addstr(\n+\t\t&buf,\n+\t\t\"\\n         If you want to trace to a unix domain socket, prefix\"\n+\t\t\"\\n         the absolute pathname with \\\"af_unix:\\\".\");\n+#endif\n+\n+\twarning(\"%s\", buf.buf);\n+\n+\tstrbuf_release(&buf);\n+}\n+\n+int tr2_dst_get_trace_fd(struct tr2_dst *dst)\n+{\n+\tconst char *tgt_value;\n+\n+\t/* don't open twice */\n+\tif (dst->initialized)\n+\t\treturn dst->fd;\n+\n+\tdst->initialized = 1;\n+\n+\ttgt_value = getenv(dst->env_var_name);\n+\n+\tif (!tgt_value || !strcmp(tgt_value, \"\") || !strcmp(tgt_value, \"0\") ||\n+\t    !strcasecmp(tgt_value, \"false\")) {\n+\t\tdst->fd = 0;\n+\t\treturn dst->fd;\n+\t}\n+\n+\tif (!strcmp(tgt_value, \"1\") || !strcasecmp(tgt_value, \"true\")) {\n+\t\tdst->fd = STDERR_FILENO;\n+\t\treturn dst->fd;\n+\t}\n+\n+\tif (strlen(tgt_value) == 1 && isdigit(*tgt_value)) {\n+\t\tdst->fd = atoi(tgt_value);\n+\t\treturn dst->fd;\n+\t}\n+\n+\tif (is_absolute_path(tgt_value))\n+\t\treturn tr2_dst_try_path(dst, tgt_value);\n+\n+#ifndef NO_UNIX_SOCKETS\n+\tif (!strncmp(tgt_value, PREFIX_AF_UNIX, PREFIX_AF_UNIX_LEN))\n+\t\treturn tr2_dst_try_unix_domain_socket(dst, tgt_value);\n+#endif\n+\n+\t/* Always warn about malformed values. */\n+\ttr2_dst_malformed_warning(dst, tgt_value);\n+\ttr2_dst_trace_disable(dst);\n+\treturn 0;\n+}\n+\n+int tr2_dst_trace_want(struct tr2_dst *dst)\n+{\n+\treturn !!tr2_dst_get_trace_fd(dst);\n+}\n+\n+void tr2_dst_write_line(struct tr2_dst *dst, struct strbuf *buf_line)\n+{\n+\tint fd = tr2_dst_get_trace_fd(dst);\n+\n+\tstrbuf_complete_line(buf_line); /* ensure final NL on buffer */\n+\n+\t/*\n+\t * We do not use write_in_full() because we do not want\n+\t * a short-write to try again.  We are using O_APPEND mode\n+\t * files and the kernel handles the atomic seek+write. If\n+\t * another thread or git process is concurrently writing to\n+\t * this fd or file, our remainder-write may not be contiguous\n+\t * with our initial write of this message.  And that will\n+\t * confuse readers.  So just don't bother.\n+\t *\n+\t * It is assumed that TRACE2 messages are short enough that\n+\t * the system can write them in 1 attempt and we won't see\n+\t * a short-write.\n+\t *\n+\t * If we get an IO error, just close the trace dst.\n+\t */\n+\tif (write(fd, buf_line->buf, buf_line->len) >= 0)\n+\t\treturn;\n+\n+\tif (tr2_dst_want_warning())\n+\t\twarning(\"unable to write trace to '%s': %s\", dst->env_var_name,\n+\t\t\tstrerror(errno));\n+\ttr2_dst_trace_disable(dst);\n+}\ndiff --git a/trace2/tr2_dst.h b/trace2/tr2_dst.h\nnew file mode 100644\nindex 0000000000..9a64f05b02\n--- /dev/null\n+++ b/trace2/tr2_dst.h\n@@ -0,0 +1,36 @@\n+#ifndef TR2_DST_H\n+#define TR2_DST_H\n+\n+struct strbuf;\n+\n+struct tr2_dst {\n+\tconst char *const env_var_name;\n+\tint fd;\n+\tunsigned int initialized : 1;\n+\tunsigned int need_close : 1;\n+};\n+\n+/*\n+ * Disable TRACE2 on the destination.  In TRACE2 a destination (DST)\n+ * wraps a file descriptor; it is associated with a TARGET which\n+ * defines the formatting.\n+ */\n+void tr2_dst_trace_disable(struct tr2_dst *dst);\n+\n+/*\n+ * Return the file descriptor for the DST.\n+ * If 0, the dst is closed or disabled.\n+ */\n+int tr2_dst_get_trace_fd(struct tr2_dst *dst);\n+\n+/*\n+ * Return true if the DST is opened for writing.\n+ */\n+int tr2_dst_trace_want(struct tr2_dst *dst);\n+\n+/*\n+ * Write a single line/message to the trace file.\n+ */\n+void tr2_dst_write_line(struct tr2_dst *dst, struct strbuf *buf_line);\n+\n+#endif /* TR2_DST_H */\ndiff --git a/trace2/tr2_sid.c b/trace2/tr2_sid.c\nnew file mode 100644\nindex 0000000000..984524a43c\n--- /dev/null\n+++ b/trace2/tr2_sid.c\n@@ -0,0 +1,67 @@\n+#include \"cache.h\"\n+#include \"trace2/tr2_sid.h\"\n+\n+#define TR2_ENVVAR_PARENT_SID \"GIT_TR2_PARENT_SID\"\n+\n+static struct strbuf tr2sid_buf = STRBUF_INIT;\n+static int tr2sid_nr_git_parents;\n+\n+/*\n+ * Compute a \"unique\" session id (SID) for the current process.  This allows\n+ * all events from this process to have a single label (much like a PID).\n+ *\n+ * Export this into our environment so that all child processes inherit it.\n+ *\n+ * If we were started by another git instance, use our parent's SID as a\n+ * prefix.  (This lets us track parent/child relationships even if there\n+ * is an intermediate shell process.)\n+ *\n+ * Additionally, count the number of nested git processes.\n+ */\n+static void tr2_sid_compute(void)\n+{\n+\tuint64_t us_now;\n+\tconst char *parent_sid;\n+\n+\tif (tr2sid_buf.len)\n+\t\treturn;\n+\n+\tparent_sid = getenv(TR2_ENVVAR_PARENT_SID);\n+\tif (parent_sid && *parent_sid) {\n+\t\tconst char *p;\n+\t\tfor (p = parent_sid; *p; p++)\n+\t\t\tif (*p == '/')\n+\t\t\t\ttr2sid_nr_git_parents++;\n+\n+\t\tstrbuf_addstr(&tr2sid_buf, parent_sid);\n+\t\tstrbuf_addch(&tr2sid_buf, '/');\n+\t\ttr2sid_nr_git_parents++;\n+\t}\n+\n+\tus_now = getnanotime() / 1000;\n+\tstrbuf_addf(&tr2sid_buf, \"%\" PRIuMAX \"-%\" PRIdMAX, (uintmax_t)us_now,\n+\t\t    (intmax_t)getpid());\n+\n+\tsetenv(TR2_ENVVAR_PARENT_SID, tr2sid_buf.buf, 1);\n+}\n+\n+const char *tr2_sid_get(void)\n+{\n+\tif (!tr2sid_buf.len)\n+\t\ttr2_sid_compute();\n+\n+\treturn tr2sid_buf.buf;\n+}\n+\n+int tr2_sid_depth(void)\n+{\n+\tif (!tr2sid_buf.len)\n+\t\ttr2_sid_compute();\n+\n+\treturn tr2sid_nr_git_parents;\n+}\n+\n+void tr2_sid_release(void)\n+{\n+\tstrbuf_release(&tr2sid_buf);\n+}\ndiff --git a/trace2/tr2_sid.h b/trace2/tr2_sid.h\nnew file mode 100644\nindex 0000000000..9bef321708\n--- /dev/null\n+++ b/trace2/tr2_sid.h\n@@ -0,0 +1,18 @@\n+#ifndef TR2_SID_H\n+#define TR2_SID_H\n+\n+/*\n+ * Get our session id. Compute if necessary.\n+ */\n+const char *tr2_sid_get(void);\n+\n+/*\n+ * Get our process depth.  A top-level git process invoked from the\n+ * command line will have depth=0.  A child git process will have\n+ * depth=1 and so on.\n+ */\n+int tr2_sid_depth(void);\n+\n+void tr2_sid_release(void);\n+\n+#endif /* TR2_SID_H */\ndiff --git a/trace2/tr2_tbuf.c b/trace2/tr2_tbuf.c\nnew file mode 100644\nindex 0000000000..0844910423\n--- /dev/null\n+++ b/trace2/tr2_tbuf.c\n@@ -0,0 +1,32 @@\n+#include \"cache.h\"\n+#include \"tr2_tbuf.h\"\n+\n+void tr2_tbuf_local_time(struct tr2_tbuf *tb)\n+{\n+\tstruct timeval tv;\n+\tstruct tm tm;\n+\ttime_t secs;\n+\n+\tgettimeofday(&tv, NULL);\n+\tsecs = tv.tv_sec;\n+\tlocaltime_r(&secs, &tm);\n+\n+\txsnprintf(tb->buf, sizeof(tb->buf), \"%02d:%02d:%02d.%06ld\", tm.tm_hour,\n+\t\t  tm.tm_min, tm.tm_sec, (long)tv.tv_usec);\n+}\n+\n+void tr2_tbuf_utc_time(struct tr2_tbuf *tb)\n+{\n+\tstruct timeval tv;\n+\tstruct tm tm;\n+\ttime_t secs;\n+\n+\tgettimeofday(&tv, NULL);\n+\tsecs = tv.tv_sec;\n+\tgmtime_r(&secs, &tm);\n+\n+\txsnprintf(tb->buf, sizeof(tb->buf),\n+\t\t  \"%4d-%02d-%02d %02d:%02d:%02d.%06ld\", tm.tm_year + 1900,\n+\t\t  tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,\n+\t\t  (long)tv.tv_usec);\n+}\ndiff --git a/trace2/tr2_tbuf.h b/trace2/tr2_tbuf.h\nnew file mode 100644\nindex 0000000000..9cdefa3957\n--- /dev/null\n+++ b/trace2/tr2_tbuf.h\n@@ -0,0 +1,23 @@\n+#ifndef TR2_TBUF_H\n+#define TR2_TBUF_H\n+\n+/*\n+ * A simple wrapper around a fixed buffer to avoid C syntax\n+ * quirks and the need to pass around an additional size_t\n+ * argument.\n+ */\n+struct tr2_tbuf {\n+\tchar buf[32];\n+};\n+\n+/*\n+ * Fill buffer with formatted local time string.\n+ */\n+void tr2_tbuf_local_time(struct tr2_tbuf *tb);\n+\n+/*\n+ * Fill buffer with formatted UTC time string.\n+ */\n+void tr2_tbuf_utc_time(struct tr2_tbuf *tb);\n+\n+#endif /* TR2_TBUF_H */\ndiff --git a/trace2/tr2_tgt.h b/trace2/tr2_tgt.h\nnew file mode 100644\nindex 0000000000..297bb8ffbe\n--- /dev/null\n+++ b/trace2/tr2_tgt.h\n@@ -0,0 +1,133 @@\n+#ifndef TR2_TGT_H\n+#define TR2_TGT_H\n+\n+struct child_process;\n+struct repository;\n+struct json_writer;\n+\n+/*\n+ * Function prototypes for a TRACE2 \"target\" vtable.\n+ */\n+\n+typedef int(tr2_tgt_init_t)(void);\n+typedef void(tr2_tgt_term_t)(void);\n+\n+typedef void(tr2_tgt_evt_version_fl_t)(const char *file, int line);\n+\n+typedef void(tr2_tgt_evt_start_fl_t)(const char *file, int line,\n+\t\t\t\t     const char **argv);\n+typedef void(tr2_tgt_evt_exit_fl_t)(const char *file, int line,\n+\t\t\t\t    uint64_t us_elapsed_absolute, int code);\n+typedef void(tr2_tgt_evt_signal_t)(uint64_t us_elapsed_absolute, int signo);\n+typedef void(tr2_tgt_evt_atexit_t)(uint64_t us_elapsed_absolute, int code);\n+\n+typedef void(tr2_tgt_evt_error_va_fl_t)(const char *file, int line,\n+\t\t\t\t\tconst char *fmt, va_list ap);\n+\n+typedef void(tr2_tgt_evt_command_path_fl_t)(const char *file, int line,\n+\t\t\t\t\t    const char *command_path);\n+typedef void(tr2_tgt_evt_command_name_fl_t)(const char *file, int line,\n+\t\t\t\t\t    const char *name,\n+\t\t\t\t\t    const char *hierarchy);\n+typedef void(tr2_tgt_evt_command_mode_fl_t)(const char *file, int line,\n+\t\t\t\t\t    const char *mode);\n+\n+typedef void(tr2_tgt_evt_alias_fl_t)(const char *file, int line,\n+\t\t\t\t     const char *alias, const char **argv);\n+\n+typedef void(tr2_tgt_evt_child_start_fl_t)(const char *file, int line,\n+\t\t\t\t\t   uint64_t us_elapsed_absolute,\n+\t\t\t\t\t   const struct child_process *cmd);\n+typedef void(tr2_tgt_evt_child_exit_fl_t)(const char *file, int line,\n+\t\t\t\t\t  uint64_t us_elapsed_absolute, int cid,\n+\t\t\t\t\t  int pid, int code,\n+\t\t\t\t\t  uint64_t us_elapsed_child);\n+\n+typedef void(tr2_tgt_evt_thread_start_fl_t)(const char *file, int line,\n+\t\t\t\t\t    uint64_t us_elapsed_absolute);\n+typedef void(tr2_tgt_evt_thread_exit_fl_t)(const char *file, int line,\n+\t\t\t\t\t   uint64_t us_elapsed_absolute,\n+\t\t\t\t\t   uint64_t us_elapsed_thread);\n+\n+typedef void(tr2_tgt_evt_exec_fl_t)(const char *file, int line,\n+\t\t\t\t    uint64_t us_elapsed_absolute, int exec_id,\n+\t\t\t\t    const char *exe, const char **argv);\n+typedef void(tr2_tgt_evt_exec_result_fl_t)(const char *file, int line,\n+\t\t\t\t\t   uint64_t us_elapsed_absolute,\n+\t\t\t\t\t   int exec_id, int code);\n+\n+typedef void(tr2_tgt_evt_param_fl_t)(const char *file, int line,\n+\t\t\t\t     const char *param, const char *value);\n+\n+typedef void(tr2_tgt_evt_repo_fl_t)(const char *file, int line,\n+\t\t\t\t    const struct repository *repo);\n+\n+typedef void(tr2_tgt_evt_region_enter_printf_va_fl_t)(\n+\tconst char *file, int line, uint64_t us_elapsed_absolute,\n+\tconst char *category, const char *label, const struct repository *repo,\n+\tconst char *fmt, va_list ap);\n+typedef void(tr2_tgt_evt_region_leave_printf_va_fl_t)(\n+\tconst char *file, int line, uint64_t us_elapsed_absolute,\n+\tuint64_t us_elapsed_region, const char *category, const char *label,\n+\tconst struct repository *repo, const char *fmt, va_list ap);\n+\n+typedef void(tr2_tgt_evt_data_fl_t)(const char *file, int line,\n+\t\t\t\t    uint64_t us_elapsed_absolute,\n+\t\t\t\t    uint64_t us_elapsed_region,\n+\t\t\t\t    const char *category,\n+\t\t\t\t    const struct repository *repo,\n+\t\t\t\t    const char *key, const char *value);\n+typedef void(tr2_tgt_evt_data_json_fl_t)(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t uint64_t us_elapsed_region,\n+\t\t\t\t\t const char *category,\n+\t\t\t\t\t const struct repository *repo,\n+\t\t\t\t\t const char *key,\n+\t\t\t\t\t const struct json_writer *value);\n+\n+typedef void(tr2_tgt_evt_printf_va_fl_t)(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t const char *fmt, va_list ap);\n+\n+/*\n+ * \"vtable\" for a TRACE2 target.  Use NULL if a target does not want\n+ * to emit that message.\n+ */\n+/* clang-format off */\n+struct tr2_tgt {\n+\tstruct tr2_dst                          *pdst;\n+\n+\ttr2_tgt_init_t                          *pfn_init;\n+\ttr2_tgt_term_t                          *pfn_term;\n+\n+\ttr2_tgt_evt_version_fl_t                *pfn_version_fl;\n+\ttr2_tgt_evt_start_fl_t                  *pfn_start_fl;\n+\ttr2_tgt_evt_exit_fl_t                   *pfn_exit_fl;\n+\ttr2_tgt_evt_signal_t                    *pfn_signal;\n+\ttr2_tgt_evt_atexit_t                    *pfn_atexit;\n+\ttr2_tgt_evt_error_va_fl_t               *pfn_error_va_fl;\n+\ttr2_tgt_evt_command_path_fl_t           *pfn_command_path_fl;\n+\ttr2_tgt_evt_command_name_fl_t           *pfn_command_name_fl;\n+\ttr2_tgt_evt_command_mode_fl_t           *pfn_command_mode_fl;\n+\ttr2_tgt_evt_alias_fl_t                  *pfn_alias_fl;\n+\ttr2_tgt_evt_child_start_fl_t            *pfn_child_start_fl;\n+\ttr2_tgt_evt_child_exit_fl_t             *pfn_child_exit_fl;\n+\ttr2_tgt_evt_thread_start_fl_t           *pfn_thread_start_fl;\n+\ttr2_tgt_evt_thread_exit_fl_t            *pfn_thread_exit_fl;\n+\ttr2_tgt_evt_exec_fl_t                   *pfn_exec_fl;\n+\ttr2_tgt_evt_exec_result_fl_t            *pfn_exec_result_fl;\n+\ttr2_tgt_evt_param_fl_t                  *pfn_param_fl;\n+\ttr2_tgt_evt_repo_fl_t                   *pfn_repo_fl;\n+\ttr2_tgt_evt_region_enter_printf_va_fl_t *pfn_region_enter_printf_va_fl;\n+\ttr2_tgt_evt_region_leave_printf_va_fl_t *pfn_region_leave_printf_va_fl;\n+\ttr2_tgt_evt_data_fl_t                   *pfn_data_fl;\n+\ttr2_tgt_evt_data_json_fl_t              *pfn_data_json_fl;\n+\ttr2_tgt_evt_printf_va_fl_t              *pfn_printf_va_fl;\n+};\n+/* clang-format on */\n+\n+extern struct tr2_tgt tr2_tgt_event;\n+extern struct tr2_tgt tr2_tgt_normal;\n+extern struct tr2_tgt tr2_tgt_perf;\n+\n+#endif /* TR2_TGT_H */\ndiff --git a/trace2/tr2_tgt_event.c b/trace2/tr2_tgt_event.c\nnew file mode 100644\nindex 0000000000..da8380b5af\n--- /dev/null\n+++ b/trace2/tr2_tgt_event.c\n@@ -0,0 +1,589 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"json-writer.h\"\n+#include \"run-command.h\"\n+#include \"version.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_tbuf.h\"\n+#include \"trace2/tr2_sid.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+static struct tr2_dst tr2dst_event = { \"GIT_TR2_EVENT\", 0, 0, 0 };\n+\n+/*\n+ * The version number of the JSON data generated by the EVENT target\n+ * in this source file.  Update this if you make a significant change\n+ * to the JSON fields or message structure.  You probably do not need\n+ * to update this if you just add another call to one of the existing\n+ * TRACE2 API methods.\n+ */\n+#define TR2_EVENT_VERSION \"1\"\n+\n+/*\n+ * Region nesting limit for messages written to the event target.\n+ *\n+ * The \"region_enter\" and \"region_leave\" messages (especially recursive\n+ * messages such as those produced while diving the worktree or index)\n+ * are primarily intended for the performance target during debugging.\n+ *\n+ * Some of the outer-most messages, however, may be of interest to the\n+ * event target.  Set this environment variable to a larger integer for\n+ * more detail in the event target.\n+ */\n+#define TR2_ENVVAR_EVENT_NESTING \"GIT_TR2_EVENT_NESTING\"\n+static int tr2env_event_nesting_wanted = 2;\n+\n+/*\n+ * Set this environment variable to true to omit the <time>, <file>, and\n+ * <line> fields from most events.\n+ */\n+#define TR2_ENVVAR_EVENT_BRIEF \"GIT_TR2_EVENT_BRIEF\"\n+static int tr2env_event_brief;\n+\n+static int fn_init(void)\n+{\n+\tint want = tr2_dst_trace_want(&tr2dst_event);\n+\tint want_nesting;\n+\tint want_brief;\n+\tchar *nesting;\n+\tchar *brief;\n+\n+\tif (!want)\n+\t\treturn want;\n+\n+\tnesting = getenv(TR2_ENVVAR_EVENT_NESTING);\n+\tif (nesting && ((want_nesting = atoi(nesting)) > 0))\n+\t\ttr2env_event_nesting_wanted = want_nesting;\n+\n+\tbrief = getenv(TR2_ENVVAR_EVENT_BRIEF);\n+\tif (brief && ((want_brief = atoi(brief)) > 0))\n+\t\ttr2env_event_brief = want_brief;\n+\n+\treturn want;\n+}\n+\n+static void fn_term(void)\n+{\n+\ttr2_dst_trace_disable(&tr2dst_event);\n+}\n+\n+/*\n+ * Append common key-value pairs to the currently open JSON object.\n+ *     \"event:\"<event_name>\"\n+ *      \"sid\":\"<sid>\"\n+ *   \"thread\":\"<thread_name>\"\n+ *     \"time\":\"<time>\"\n+ *     \"file\":\"<filename>\"\n+ *     \"line\":<line_number>\n+ *     \"repo\":<repo_id>\n+ */\n+static void event_fmt_prepare(const char *event_name, const char *file,\n+\t\t\t      int line, const struct repository *repo,\n+\t\t\t      struct json_writer *jw)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tstruct tr2_tbuf tb_now;\n+\n+\tjw_object_string(jw, \"event\", event_name);\n+\tjw_object_string(jw, \"sid\", tr2_sid_get());\n+\tjw_object_string(jw, \"thread\", ctx->thread_name.buf);\n+\n+\t/*\n+\t * In brief mode, only emit <time> on these 2 event types.\n+\t */\n+\tif (!tr2env_event_brief || !strcmp(event_name, \"version\") ||\n+\t    !strcmp(event_name, \"atexit\")) {\n+\t\ttr2_tbuf_utc_time(&tb_now);\n+\t\tjw_object_string(jw, \"time\", tb_now.buf);\n+\t}\n+\n+\tif (!tr2env_event_brief && file && *file) {\n+\t\tjw_object_string(jw, \"file\", file);\n+\t\tjw_object_intmax(jw, \"line\", line);\n+\t}\n+\n+\tif (repo)\n+\t\tjw_object_intmax(jw, \"repo\", repo->trace2_repo_id);\n+}\n+\n+static void fn_version_fl(const char *file, int line)\n+{\n+\tconst char *event_name = \"version\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"evt\", TR2_EVENT_VERSION);\n+\tjw_object_string(&jw, \"exe\", git_version_string);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_start_fl(const char *file, int line, const char **argv)\n+{\n+\tconst char *event_name = \"start\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tjw_array_argv(&jw, argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_exit_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int code)\n+{\n+\tconst char *event_name = \"exit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_signal(uint64_t us_elapsed_absolute, int signo)\n+{\n+\tconst char *event_name = \"signal\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, __FILE__, __LINE__, NULL, &jw);\n+\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\tjw_object_intmax(&jw, \"signo\", signo);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_atexit(uint64_t us_elapsed_absolute, int code)\n+{\n+\tconst char *event_name = \"atexit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, __FILE__, __LINE__, NULL, &jw);\n+\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void maybe_add_string_va(struct json_writer *jw, const char *field_name,\n+\t\t\t\tconst char *fmt, va_list ap)\n+{\n+\tif (fmt && *fmt && ap) {\n+\t\tva_list copy_ap;\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\n+\t\tva_copy(copy_ap, ap);\n+\t\tstrbuf_vaddf(&buf, fmt, copy_ap);\n+\t\tva_end(copy_ap);\n+\n+\t\tjw_object_string(jw, field_name, buf.buf);\n+\t\tstrbuf_release(&buf);\n+\t\treturn;\n+\t}\n+\n+\tif (fmt && *fmt) {\n+\t\tjw_object_string(jw, field_name, fmt);\n+\t\treturn;\n+\t}\n+}\n+\n+static void fn_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t   va_list ap)\n+{\n+\tconst char *event_name = \"error\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tmaybe_add_string_va(&jw, \"msg\", fmt, ap);\n+\t/*\n+\t * Also emit the format string as a field in case\n+\t * post-processors want to aggregate common error\n+\t * messages by type without argument fields (such\n+\t * as pathnames or branch names) cluttering it up.\n+\t */\n+\tif (fmt && *fmt)\n+\t\tjw_object_string(&jw, \"fmt\", fmt);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_command_path_fl(const char *file, int line, const char *pathname)\n+{\n+\tconst char *event_name = \"cmd_path\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"path\", pathname);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_command_name_fl(const char *file, int line,\n+\t\t\t       const char *name,\n+\t\t\t       const char *hierarchy)\n+{\n+\tconst char *event_name = \"cmd_name\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"name\", name);\n+\tif (hierarchy && *hierarchy)\n+\t\tjw_object_string(&jw, \"hierarchy\", hierarchy);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_command_mode_fl(const char *file, int line, const char *mode)\n+{\n+\tconst char *event_name = \"cmd_mode\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"name\", mode);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_alias_fl(const char *file, int line, const char *alias,\n+\t\t\tconst char **argv)\n+{\n+\tconst char *event_name = \"alias\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"alias\", alias);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tjw_array_argv(&jw, argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_child_start_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      const struct child_process *cmd)\n+{\n+\tconst char *event_name = \"child_start\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"child_id\", cmd->trace2_child_id);\n+\tif (cmd->trace2_hook_name) {\n+\t\tjw_object_string(&jw, \"child_class\", \"hook\");\n+\t\tjw_object_string(&jw, \"hook_name\", cmd->trace2_hook_name);\n+\t} else {\n+\t\tconst char *child_class =\n+\t\t\tcmd->trace2_child_class ? cmd->trace2_child_class : \"?\";\n+\t\tjw_object_string(&jw, \"child_class\", child_class);\n+\t}\n+\tif (cmd->dir)\n+\t\tjw_object_string(&jw, \"cd\", cmd->dir);\n+\tjw_object_bool(&jw, \"use_shell\", cmd->use_shell);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tif (cmd->git_cmd)\n+\t\tjw_array_string(&jw, \"git\");\n+\tjw_array_argv(&jw, cmd->argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_child_exit_fl(const char *file, int line,\n+\t\t\t     uint64_t us_elapsed_absolute, int cid, int pid,\n+\t\t\t     int code, uint64_t us_elapsed_child)\n+{\n+\tconst char *event_name = \"child_exit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_rel = (double)us_elapsed_child / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"child_id\", cid);\n+\tjw_object_intmax(&jw, \"pid\", pid);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\n+\tjw_release(&jw);\n+}\n+\n+static void fn_thread_start_fl(const char *file, int line,\n+\t\t\t       uint64_t us_elapsed_absolute)\n+{\n+\tconst char *event_name = \"thread_start\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_thread_exit_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      uint64_t us_elapsed_thread)\n+{\n+\tconst char *event_name = \"thread_exit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_rel = (double)us_elapsed_thread / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int exec_id, const char *exe, const char **argv)\n+{\n+\tconst char *event_name = \"exec\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"exec_id\", exec_id);\n+\tif (exe)\n+\t\tjw_object_string(&jw, \"exe\", exe);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tjw_array_argv(&jw, argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_exec_result_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute, int exec_id,\n+\t\t\t      int code)\n+{\n+\tconst char *event_name = \"exec_result\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"exec_id\", exec_id);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_param_fl(const char *file, int line, const char *param,\n+\t\t\tconst char *value)\n+{\n+\tconst char *event_name = \"def_param\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"param\", param);\n+\tjw_object_string(&jw, \"value\", value);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_repo_fl(const char *file, int line,\n+\t\t       const struct repository *repo)\n+{\n+\tconst char *event_name = \"def_repo\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\tjw_object_string(&jw, \"worktree\", repo->worktree);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t const char *category,\n+\t\t\t\t\t const char *label,\n+\t\t\t\t\t const struct repository *repo,\n+\t\t\t\t\t const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_enter\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tif (category)\n+\t\t\tjw_object_string(&jw, \"category\", category);\n+\t\tif (label)\n+\t\t\tjw_object_string(&jw, \"label\", label);\n+\t\tmaybe_add_string_va(&jw, \"msg\", fmt, ap);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+static void fn_region_leave_printf_va_fl(\n+\tconst char *file, int line, uint64_t us_elapsed_absolute,\n+\tuint64_t us_elapsed_region, const char *category, const char *label,\n+\tconst struct repository *repo, const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_leave\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\t\tdouble t_rel = (double)us_elapsed_region / 1000000.0;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tif (category)\n+\t\t\tjw_object_string(&jw, \"category\", category);\n+\t\tif (label)\n+\t\t\tjw_object_string(&jw, \"label\", label);\n+\t\tmaybe_add_string_va(&jw, \"msg\", fmt, ap);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+static void fn_data_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       uint64_t us_elapsed_region, const char *category,\n+\t\t       const struct repository *repo, const char *key,\n+\t\t       const char *value)\n+{\n+\tconst char *event_name = \"data\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\t\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\t\tdouble t_rel = (double)us_elapsed_region / 1000000.0;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\t\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tjw_object_string(&jw, \"category\", category);\n+\t\tjw_object_string(&jw, \"key\", key);\n+\t\tjw_object_string(&jw, \"value\", value);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+static void fn_data_json_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute,\n+\t\t\t    uint64_t us_elapsed_region, const char *category,\n+\t\t\t    const struct repository *repo, const char *key,\n+\t\t\t    const struct json_writer *value)\n+{\n+\tconst char *event_name = \"data_json\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\t\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\t\tdouble t_rel = (double)us_elapsed_region / 1000000.0;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\t\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tjw_object_string(&jw, \"category\", category);\n+\t\tjw_object_string(&jw, \"key\", key);\n+\t\tjw_object_sub_jw(&jw, \"value\", value);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+struct tr2_tgt tr2_tgt_event = {\n+\t&tr2dst_event,\n+\n+\tfn_init,\n+\tfn_term,\n+\n+\tfn_version_fl,\n+\tfn_start_fl,\n+\tfn_exit_fl,\n+\tfn_signal,\n+\tfn_atexit,\n+\tfn_error_va_fl,\n+\tfn_command_path_fl,\n+\tfn_command_name_fl,\n+\tfn_command_mode_fl,\n+\tfn_alias_fl,\n+\tfn_child_start_fl,\n+\tfn_child_exit_fl,\n+\tfn_thread_start_fl,\n+\tfn_thread_exit_fl,\n+\tfn_exec_fl,\n+\tfn_exec_result_fl,\n+\tfn_param_fl,\n+\tfn_repo_fl,\n+\tfn_region_enter_printf_va_fl,\n+\tfn_region_leave_printf_va_fl,\n+\tfn_data_fl,\n+\tfn_data_json_fl,\n+\tNULL, /* printf */\n+};\ndiff --git a/trace2/tr2_tgt_normal.c b/trace2/tr2_tgt_normal.c\nnew file mode 100644\nindex 0000000000..3babf2af02\n--- /dev/null\n+++ b/trace2/tr2_tgt_normal.c\n@@ -0,0 +1,324 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"run-command.h\"\n+#include \"quote.h\"\n+#include \"version.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_tbuf.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+static struct tr2_dst tr2dst_normal = { \"GIT_TR2\", 0, 0, 0 };\n+\n+/*\n+ * Set this environment variable to true to omit the \"<time> <file>:<line>\"\n+ * fields from each line written to the builtin normal target.\n+ *\n+ * Unit tests may want to use this to help with testing.\n+ */\n+#define TR2_ENVVAR_NORMAL_BRIEF \"GIT_TR2_BRIEF\"\n+static int tr2env_normal_brief;\n+\n+#define TR2FMT_NORMAL_FL_WIDTH (50)\n+\n+static int fn_init(void)\n+{\n+\tint want = tr2_dst_trace_want(&tr2dst_normal);\n+\tint want_brief;\n+\tchar *brief;\n+\n+\tif (!want)\n+\t\treturn want;\n+\n+\tbrief = getenv(TR2_ENVVAR_NORMAL_BRIEF);\n+\tif (brief && *brief &&\n+\t    ((want_brief = git_parse_maybe_bool(brief)) != -1))\n+\t\ttr2env_normal_brief = want_brief;\n+\n+\treturn want;\n+}\n+\n+static void fn_term(void)\n+{\n+\ttr2_dst_trace_disable(&tr2dst_normal);\n+}\n+\n+static void normal_fmt_prepare(const char *file, int line, struct strbuf *buf)\n+{\n+\tstrbuf_setlen(buf, 0);\n+\n+\tif (!tr2env_normal_brief) {\n+\t\tstruct tr2_tbuf tb_now;\n+\n+\t\ttr2_tbuf_local_time(&tb_now);\n+\t\tstrbuf_addstr(buf, tb_now.buf);\n+\t\tstrbuf_addch(buf, ' ');\n+\n+\t\tif (file && *file)\n+\t\t\tstrbuf_addf(buf, \"%s:%d \", file, line);\n+\t\twhile (buf->len < TR2FMT_NORMAL_FL_WIDTH)\n+\t\t\tstrbuf_addch(buf, ' ');\n+\t}\n+}\n+\n+static void normal_io_write_fl(const char *file, int line,\n+\t\t\t       const struct strbuf *buf_payload)\n+{\n+\tstruct strbuf buf_line = STRBUF_INIT;\n+\n+\tnormal_fmt_prepare(file, line, &buf_line);\n+\tstrbuf_addbuf(&buf_line, buf_payload);\n+\ttr2_dst_write_line(&tr2dst_normal, &buf_line);\n+\tstrbuf_release(&buf_line);\n+}\n+\n+static void fn_version_fl(const char *file, int line)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"version %s\", git_version_string);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_start_fl(const char *file, int line, const char **argv)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"start \");\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exit_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int code)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"exit elapsed:%.6f code:%d\", elapsed, code);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_signal(uint64_t us_elapsed_absolute, int signo)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"signal elapsed:%.6f code:%d\", elapsed,\n+\t\t    signo);\n+\tnormal_io_write_fl(__FILE__, __LINE__, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_atexit(uint64_t us_elapsed_absolute, int code)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"atexit elapsed:%.6f code:%d\", elapsed, code);\n+\tnormal_io_write_fl(__FILE__, __LINE__, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void maybe_append_string_va(struct strbuf *buf, const char *fmt,\n+\t\t\t\t   va_list ap)\n+{\n+\tif (fmt && *fmt && ap) {\n+\t\tva_list copy_ap;\n+\n+\t\tva_copy(copy_ap, ap);\n+\t\tstrbuf_vaddf(buf, fmt, copy_ap);\n+\t\tva_end(copy_ap);\n+\t\treturn;\n+\t}\n+\n+\tif (fmt && *fmt) {\n+\t\tstrbuf_addstr(buf, fmt);\n+\t\treturn;\n+\t}\n+}\n+\n+static void fn_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t   va_list ap)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"error \");\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_path_fl(const char *file, int line, const char *pathname)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"cmd_path %s\", pathname);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_name_fl(const char *file, int line,\n+\t\t\t       const char *name,\n+\t\t\t       const char *hierarchy)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"cmd_name %s\", name);\n+\tif (hierarchy && *hierarchy)\n+\t\tstrbuf_addf(&buf_payload, \" (%s)\", hierarchy);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_mode_fl(const char *file, int line, const char *mode)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"cmd_mode %s\", mode);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_alias_fl(const char *file, int line, const char *alias,\n+\t\t\tconst char **argv)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"alias %s ->\", alias);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_start_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      const struct child_process *cmd)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"child_start[%d] \", cmd->trace2_child_id);\n+\n+\tif (cmd->dir) {\n+\t\tstrbuf_addstr(&buf_payload, \" cd\");\n+\t\tsq_quote_buf_pretty(&buf_payload, cmd->dir);\n+\t\tstrbuf_addstr(&buf_payload, \"; \");\n+\t}\n+\n+\t/*\n+\t * TODO if (cmd->env) { Consider dumping changes to environment. }\n+\t * See trace_add_env() in run-command.c as used by original trace.c\n+\t */\n+\n+\tif (cmd->git_cmd)\n+\t\tstrbuf_addstr(&buf_payload, \"git\");\n+\tsq_quote_argv_pretty(&buf_payload, cmd->argv);\n+\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_exit_fl(const char *file, int line,\n+\t\t\t     uint64_t us_elapsed_absolute, int cid, int pid,\n+\t\t\t     int code, uint64_t us_elapsed_child)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_child / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"child_exit[%d] pid:%d code:%d elapsed:%.6f\",\n+\t\t    cid, pid, code, elapsed);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int exec_id, const char *exe, const char **argv)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"exec[%d] \", exec_id);\n+\tif (exe)\n+\t\tstrbuf_addstr(&buf_payload, exe);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_result_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute, int exec_id,\n+\t\t\t      int code)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"exec_result[%d] code:%d\", exec_id, code);\n+\tif (code > 0)\n+\t\tstrbuf_addf(&buf_payload, \" err:%s\", strerror(code));\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_param_fl(const char *file, int line, const char *param,\n+\t\t\tconst char *value)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"def_param %s=%s\", param, value);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_repo_fl(const char *file, int line,\n+\t\t       const struct repository *repo)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"worktree \");\n+\tsq_quote_buf_pretty(&buf_payload, repo->worktree);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_printf_va_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute, const char *fmt,\n+\t\t\t    va_list ap)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+struct tr2_tgt tr2_tgt_normal = {\n+\t&tr2dst_normal,\n+\n+\tfn_init,\n+\tfn_term,\n+\n+\tfn_version_fl,\n+\tfn_start_fl,\n+\tfn_exit_fl,\n+\tfn_signal,\n+\tfn_atexit,\n+\tfn_error_va_fl,\n+\tfn_command_path_fl,\n+\tfn_command_name_fl,\n+\tfn_command_mode_fl,\n+\tfn_alias_fl,\n+\tfn_child_start_fl,\n+\tfn_child_exit_fl,\n+\tNULL, /* thread_start */\n+\tNULL, /* thread_exit */\n+\tfn_exec_fl,\n+\tfn_exec_result_fl,\n+\tfn_param_fl,\n+\tfn_repo_fl,\n+\tNULL, /* region_enter */\n+\tNULL, /* region_leave */\n+\tNULL, /* data */\n+\tNULL, /* data_json */\n+\tfn_printf_va_fl,\n+};\ndiff --git a/trace2/tr2_tgt_perf.c b/trace2/tr2_tgt_perf.c\nnew file mode 100644\nindex 0000000000..7b0cf169a4\n--- /dev/null\n+++ b/trace2/tr2_tgt_perf.c\n@@ -0,0 +1,535 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"run-command.h\"\n+#include \"quote.h\"\n+#include \"version.h\"\n+#include \"json-writer.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_sid.h\"\n+#include \"trace2/tr2_tbuf.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+static struct tr2_dst tr2dst_perf = { \"GIT_TR2_PERF\", 0, 0, 0 };\n+\n+/*\n+ * Set this environment variable to true to omit the \"<time> <file>:<line>\"\n+ * fields from each line written to the builtin performance target.\n+ *\n+ * Unit tests may want to use this to help with testing.\n+ */\n+#define TR2_ENVVAR_PERF_BRIEF \"GIT_TR2_PERF_BRIEF\"\n+static int tr2env_perf_brief;\n+\n+#define TR2FMT_PERF_FL_WIDTH (50)\n+#define TR2FMT_PERF_MAX_EVENT_NAME (12)\n+#define TR2FMT_PERF_REPO_WIDTH (4)\n+#define TR2FMT_PERF_CATEGORY_WIDTH (10)\n+\n+#define TR2_DOTS_BUFFER_SIZE (100)\n+#define TR2_INDENT (2)\n+#define TR2_INDENT_LENGTH(ctx) (((ctx)->nr_open_regions - 1) * TR2_INDENT)\n+\n+static struct strbuf dots = STRBUF_INIT;\n+\n+static int fn_init(void)\n+{\n+\tint want = tr2_dst_trace_want(&tr2dst_perf);\n+\tint want_brief;\n+\tchar *brief;\n+\n+\tif (!want)\n+\t\treturn want;\n+\n+\tstrbuf_addchars(&dots, '.', TR2_DOTS_BUFFER_SIZE);\n+\n+\tbrief = getenv(TR2_ENVVAR_PERF_BRIEF);\n+\tif (brief && *brief &&\n+\t    ((want_brief = git_parse_maybe_bool(brief)) != -1))\n+\t\ttr2env_perf_brief = want_brief;\n+\n+\treturn want;\n+}\n+\n+static void fn_term(void)\n+{\n+\ttr2_dst_trace_disable(&tr2dst_perf);\n+\n+\tstrbuf_release(&dots);\n+}\n+\n+/*\n+ * Format trace line prefix in human-readable classic format for\n+ * the performance target:\n+ *     \"[<time> [<file>:<line>] <bar>] <nr_parents> <bar>\n+ *         <thread_name> <bar> <event_name> <bar> [<repo>] <bar>\n+ *         [<elapsed_absolute>] [<elapsed_relative>] <bar>\n+ *         [<category>] <bar> [<dots>] \"\n+ */\n+static void perf_fmt_prepare(const char *event_name,\n+\t\t\t     struct tr2tls_thread_ctx *ctx, const char *file,\n+\t\t\t     int line, const struct repository *repo,\n+\t\t\t     uint64_t *p_us_elapsed_absolute,\n+\t\t\t     uint64_t *p_us_elapsed_relative,\n+\t\t\t     const char *category, struct strbuf *buf)\n+{\n+\tint len;\n+\n+\tstrbuf_setlen(buf, 0);\n+\n+\tif (!tr2env_perf_brief) {\n+\t\tstruct tr2_tbuf tb_now;\n+\n+\t\ttr2_tbuf_local_time(&tb_now);\n+\t\tstrbuf_addstr(buf, tb_now.buf);\n+\t\tstrbuf_addch(buf, ' ');\n+\n+\t\tif (file && *file)\n+\t\t\tstrbuf_addf(buf, \"%s:%d \", file, line);\n+\t\twhile (buf->len < TR2FMT_PERF_FL_WIDTH)\n+\t\t\tstrbuf_addch(buf, ' ');\n+\n+\t\tstrbuf_addstr(buf, \"| \");\n+\t}\n+\n+\tstrbuf_addf(buf, \"d%d | \", tr2_sid_depth());\n+\tstrbuf_addf(buf, \"%-*s | %-*s | \", TR2_MAX_THREAD_NAME,\n+\t\t    ctx->thread_name.buf, TR2FMT_PERF_MAX_EVENT_NAME,\n+\t\t    event_name);\n+\n+\tlen = buf->len + TR2FMT_PERF_REPO_WIDTH;\n+\tif (repo)\n+\t\tstrbuf_addf(buf, \"r%d \", repo->trace2_repo_id);\n+\twhile (buf->len < len)\n+\t\tstrbuf_addch(buf, ' ');\n+\tstrbuf_addstr(buf, \"| \");\n+\n+\tif (p_us_elapsed_absolute)\n+\t\tstrbuf_addf(buf, \"%9.6f | \",\n+\t\t\t    ((double)(*p_us_elapsed_absolute)) / 1000000.0);\n+\telse\n+\t\tstrbuf_addf(buf, \"%9s | \", \" \");\n+\n+\tif (p_us_elapsed_relative)\n+\t\tstrbuf_addf(buf, \"%9.6f | \",\n+\t\t\t    ((double)(*p_us_elapsed_relative)) / 1000000.0);\n+\telse\n+\t\tstrbuf_addf(buf, \"%9s | \", \" \");\n+\n+\tstrbuf_addf(buf, \"%-*s | \", TR2FMT_PERF_CATEGORY_WIDTH,\n+\t\t    (category ? category : \"\"));\n+\n+\tif (ctx->nr_open_regions > 0) {\n+\t\tint len_indent = TR2_INDENT_LENGTH(ctx);\n+\t\twhile (len_indent > dots.len) {\n+\t\t\tstrbuf_addbuf(buf, &dots);\n+\t\t\tlen_indent -= dots.len;\n+\t\t}\n+\t\tstrbuf_addf(buf, \"%.*s\", len_indent, dots.buf);\n+\t}\n+}\n+\n+static void perf_io_write_fl(const char *file, int line, const char *event_name,\n+\t\t\t     const struct repository *repo,\n+\t\t\t     uint64_t *p_us_elapsed_absolute,\n+\t\t\t     uint64_t *p_us_elapsed_relative,\n+\t\t\t     const char *category,\n+\t\t\t     const struct strbuf *buf_payload)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tstruct strbuf buf_line = STRBUF_INIT;\n+\n+\tperf_fmt_prepare(event_name, ctx, file, line, repo,\n+\t\t\t p_us_elapsed_absolute, p_us_elapsed_relative, category,\n+\t\t\t &buf_line);\n+\tstrbuf_addbuf(&buf_line, buf_payload);\n+\ttr2_dst_write_line(&tr2dst_perf, &buf_line);\n+\tstrbuf_release(&buf_line);\n+}\n+\n+static void fn_version_fl(const char *file, int line)\n+{\n+\tconst char *event_name = \"version\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, git_version_string);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_start_fl(const char *file, int line, const char **argv)\n+{\n+\tconst char *event_name = \"start\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exit_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int code)\n+{\n+\tconst char *event_name = \"exit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"code:%d\", code);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_signal(uint64_t us_elapsed_absolute, int signo)\n+{\n+\tconst char *event_name = \"signal\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"signo:%d\", signo);\n+\n+\tperf_io_write_fl(__FILE__, __LINE__, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_atexit(uint64_t us_elapsed_absolute, int code)\n+{\n+\tconst char *event_name = \"atexit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"code:%d\", code);\n+\n+\tperf_io_write_fl(__FILE__, __LINE__, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void maybe_append_string_va(struct strbuf *buf, const char *fmt,\n+\t\t\t\t   va_list ap)\n+{\n+\tif (fmt && *fmt && ap) {\n+\t\tva_list copy_ap;\n+\n+\t\tva_copy(copy_ap, ap);\n+\t\tstrbuf_vaddf(buf, fmt, copy_ap);\n+\t\tva_end(copy_ap);\n+\t\treturn;\n+\t}\n+\n+\tif (fmt && *fmt) {\n+\t\tstrbuf_addstr(buf, fmt);\n+\t\treturn;\n+\t}\n+}\n+\n+static void fn_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t   va_list ap)\n+{\n+\tconst char *event_name = \"error\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_path_fl(const char *file, int line, const char *pathname)\n+{\n+\tconst char *event_name = \"cmd_path\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, pathname);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_name_fl(const char *file, int line,\n+\t\t\t       const char *name,\n+\t\t\t       const char *hierarchy)\n+{\n+\tconst char *event_name = \"cmd_name\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, name);\n+\tif (hierarchy && *hierarchy)\n+\t\tstrbuf_addf(&buf_payload, \" (%s)\", hierarchy);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_mode_fl(const char *file, int line, const char *mode)\n+{\n+\tconst char *event_name = \"cmd_mode\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, mode);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_alias_fl(const char *file, int line, const char *alias,\n+\t\t\tconst char **argv)\n+{\n+\tconst char *event_name = \"alias\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"alias:%s argv:\", alias);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_start_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      const struct child_process *cmd)\n+{\n+\tconst char *event_name = \"child_start\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tif (cmd->trace2_hook_name) {\n+\t\tstrbuf_addf(&buf_payload, \"[ch%d] class:hook hook:%s\",\n+\t\t\t    cmd->trace2_child_id, cmd->trace2_hook_name);\n+\t} else {\n+\t\tconst char *child_class =\n+\t\t\tcmd->trace2_child_class ? cmd->trace2_child_class : \"?\";\n+\t\tstrbuf_addf(&buf_payload, \"[ch%d] class:%s\",\n+\t\t\t    cmd->trace2_child_id, child_class);\n+\t}\n+\n+\tif (cmd->dir) {\n+\t\tstrbuf_addstr(&buf_payload, \" cd:\");\n+\t\tsq_quote_buf_pretty(&buf_payload, cmd->dir);\n+\t}\n+\n+\tstrbuf_addstr(&buf_payload, \" argv:\");\n+\tif (cmd->git_cmd)\n+\t\tstrbuf_addstr(&buf_payload, \" git\");\n+\tsq_quote_argv_pretty(&buf_payload, cmd->argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_exit_fl(const char *file, int line,\n+\t\t\t     uint64_t us_elapsed_absolute, int cid, int pid,\n+\t\t\t     int code, uint64_t us_elapsed_child)\n+{\n+\tconst char *event_name = \"child_exit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"[ch%d] pid:%d code:%d\", cid, pid, code);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_child, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_thread_start_fl(const char *file, int line,\n+\t\t\t       uint64_t us_elapsed_absolute)\n+{\n+\tconst char *event_name = \"thread_start\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_thread_exit_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      uint64_t us_elapsed_thread)\n+{\n+\tconst char *event_name = \"thread_exit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_thread, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int exec_id, const char *exe, const char **argv)\n+{\n+\tconst char *event_name = \"exec\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"id:%d \", exec_id);\n+\tstrbuf_addstr(&buf_payload, \"argv:\");\n+\tif (exe)\n+\t\tstrbuf_addf(&buf_payload, \" %s\", exe);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_result_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute, int exec_id,\n+\t\t\t      int code)\n+{\n+\tconst char *event_name = \"exec_result\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"id:%d code:%d\", exec_id, code);\n+\tif (code > 0)\n+\t\tstrbuf_addf(&buf_payload, \" err:%s\", strerror(code));\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_param_fl(const char *file, int line, const char *param,\n+\t\t\tconst char *value)\n+{\n+\tconst char *event_name = \"def_param\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"%s:%s\", param, value);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_repo_fl(const char *file, int line,\n+\t\t       const struct repository *repo)\n+{\n+\tconst char *event_name = \"def_repo\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"worktree:\");\n+\tsq_quote_buf_pretty(&buf_payload, repo->worktree);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t const char *category,\n+\t\t\t\t\t const char *label,\n+\t\t\t\t\t const struct repository *repo,\n+\t\t\t\t\t const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_enter\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tif (label)\n+\t\tstrbuf_addf(&buf_payload, \"label:%s \", label);\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,\n+\t\t\t NULL, category, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_region_leave_printf_va_fl(\n+\tconst char *file, int line, uint64_t us_elapsed_absolute,\n+\tuint64_t us_elapsed_region, const char *category, const char *label,\n+\tconst struct repository *repo, const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_leave\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tif (label)\n+\t\tstrbuf_addf(&buf_payload, \"label:%s \", label);\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_region, category, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_data_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       uint64_t us_elapsed_region, const char *category,\n+\t\t       const struct repository *repo, const char *key,\n+\t\t       const char *value)\n+{\n+\tconst char *event_name = \"data\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"%s:%s\", key, value);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_region, category, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_data_json_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute,\n+\t\t\t    uint64_t us_elapsed_region, const char *category,\n+\t\t\t    const struct repository *repo, const char *key,\n+\t\t\t    const struct json_writer *value)\n+{\n+\tconst char *event_name = \"data_json\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"%s:%s\", key, value->json.buf);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_region, category, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_printf_va_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute, const char *fmt,\n+\t\t\t    va_list ap)\n+{\n+\tconst char *event_name = \"printf\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+struct tr2_tgt tr2_tgt_perf = {\n+\t&tr2dst_perf,\n+\n+\tfn_init,\n+\tfn_term,\n+\n+\tfn_version_fl,\n+\tfn_start_fl,\n+\tfn_exit_fl,\n+\tfn_signal,\n+\tfn_atexit,\n+\tfn_error_va_fl,\n+\tfn_command_path_fl,\n+\tfn_command_name_fl,\n+\tfn_command_mode_fl,\n+\tfn_alias_fl,\n+\tfn_child_start_fl,\n+\tfn_child_exit_fl,\n+\tfn_thread_start_fl,\n+\tfn_thread_exit_fl,\n+\tfn_exec_fl,\n+\tfn_exec_result_fl,\n+\tfn_param_fl,\n+\tfn_repo_fl,\n+\tfn_region_enter_printf_va_fl,\n+\tfn_region_leave_printf_va_fl,\n+\tfn_data_fl,\n+\tfn_data_json_fl,\n+\tfn_printf_va_fl,\n+};\ndiff --git a/trace2/tr2_tls.c b/trace2/tr2_tls.c\nnew file mode 100644\nindex 0000000000..8e65b0361d\n--- /dev/null\n+++ b/trace2/tr2_tls.c\n@@ -0,0 +1,164 @@\n+#include \"cache.h\"\n+#include \"thread-utils.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+/*\n+ * Initialize size of the thread stack for nested regions.\n+ * This is used to store nested region start times.  Note that\n+ * this stack is per-thread and not per-trace-key.\n+ */\n+#define TR2_REGION_NESTING_INITIAL_SIZE (100)\n+\n+static struct tr2tls_thread_ctx *tr2tls_thread_main;\n+static uint64_t tr2tls_us_start_main;\n+\n+static pthread_mutex_t tr2tls_mutex;\n+static pthread_key_t tr2tls_key;\n+\n+static int tr2_next_thread_id; /* modify under lock */\n+\n+struct tr2tls_thread_ctx *tr2tls_create_self(const char *thread_name)\n+{\n+\tuint64_t us_now = getnanotime() / 1000;\n+\tstruct tr2tls_thread_ctx *ctx = xcalloc(1, sizeof(*ctx));\n+\n+\t/*\n+\t * Implicitly \"tr2tls_push_self()\" to capture the thread's start\n+\t * time in array_us_start[0].  For the main thread this gives us the\n+\t * application run time.\n+\t */\n+\tctx->alloc = TR2_REGION_NESTING_INITIAL_SIZE;\n+\tctx->array_us_start = (uint64_t *)xcalloc(ctx->alloc, sizeof(uint64_t));\n+\tctx->array_us_start[ctx->nr_open_regions++] = us_now;\n+\n+\tctx->thread_id = tr2tls_locked_increment(&tr2_next_thread_id);\n+\n+\tstrbuf_init(&ctx->thread_name, 0);\n+\tif (ctx->thread_id)\n+\t\tstrbuf_addf(&ctx->thread_name, \"th%02d:\", ctx->thread_id);\n+\tstrbuf_addstr(&ctx->thread_name, thread_name);\n+\tif (ctx->thread_name.len > TR2_MAX_THREAD_NAME)\n+\t\tstrbuf_setlen(&ctx->thread_name, TR2_MAX_THREAD_NAME);\n+\n+\tpthread_setspecific(tr2tls_key, ctx);\n+\n+\treturn ctx;\n+}\n+\n+struct tr2tls_thread_ctx *tr2tls_get_self(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = pthread_getspecific(tr2tls_key);\n+\n+\t/*\n+\t * If the thread-proc did not call trace2_thread_start(), we won't\n+\t * have any TLS data associated with the current thread.  Fix it\n+\t * here and silently continue.\n+\t */\n+\tif (!ctx)\n+\t\tctx = tr2tls_create_self(\"unknown\");\n+\n+\treturn ctx;\n+}\n+\n+int tr2tls_is_main_thread(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = pthread_getspecific(tr2tls_key);\n+\n+\treturn ctx == tr2tls_thread_main;\n+}\n+\n+void tr2tls_unset_self(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx;\n+\n+\tctx = tr2tls_get_self();\n+\n+\tpthread_setspecific(tr2tls_key, NULL);\n+\n+\tfree(ctx->array_us_start);\n+\tfree(ctx);\n+}\n+\n+void tr2tls_push_self(uint64_t us_now)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\n+\tALLOC_GROW(ctx->array_us_start, ctx->nr_open_regions + 1, ctx->alloc);\n+\tctx->array_us_start[ctx->nr_open_regions++] = us_now;\n+}\n+\n+void tr2tls_pop_self(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\n+\tif (!ctx->nr_open_regions)\n+\t\tBUG(\"no open regions in thread '%s'\", ctx->thread_name.buf);\n+\n+\tctx->nr_open_regions--;\n+}\n+\n+void tr2tls_pop_unwind_self(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\n+\twhile (ctx->nr_open_regions > 1)\n+\t\ttr2tls_pop_self();\n+}\n+\n+uint64_t tr2tls_region_elasped_self(uint64_t us)\n+{\n+\tstruct tr2tls_thread_ctx *ctx;\n+\tuint64_t us_start;\n+\n+\tctx = tr2tls_get_self();\n+\tif (!ctx->nr_open_regions)\n+\t\treturn 0;\n+\n+\tus_start = ctx->array_us_start[ctx->nr_open_regions - 1];\n+\n+\treturn us - us_start;\n+}\n+\n+uint64_t tr2tls_absolute_elapsed(uint64_t us)\n+{\n+\tif (!tr2tls_thread_main)\n+\t\treturn 0;\n+\n+\treturn us - tr2tls_us_start_main;\n+}\n+\n+void tr2tls_init(void)\n+{\n+\tpthread_key_create(&tr2tls_key, NULL);\n+\tinit_recursive_mutex(&tr2tls_mutex);\n+\n+\ttr2tls_thread_main = tr2tls_create_self(\"main\");\n+\t/*\n+\t * Keep a copy of the absolute start time of the main thread\n+\t * in a fixed variable since other threads need to access it.\n+\t * This also eliminates the need to lock accesses to the main\n+\t * thread's array (because of reallocs).\n+\t */\n+\ttr2tls_us_start_main = tr2tls_thread_main->array_us_start[0];\n+}\n+\n+void tr2tls_release(void)\n+{\n+\ttr2tls_unset_self();\n+\ttr2tls_thread_main = NULL;\n+\n+\tpthread_mutex_destroy(&tr2tls_mutex);\n+\tpthread_key_delete(tr2tls_key);\n+}\n+\n+int tr2tls_locked_increment(int *p)\n+{\n+\tint current_value;\n+\n+\tpthread_mutex_lock(&tr2tls_mutex);\n+\tcurrent_value = *p;\n+\t*p = current_value + 1;\n+\tpthread_mutex_unlock(&tr2tls_mutex);\n+\n+\treturn current_value;\n+}\ndiff --git a/trace2/tr2_tls.h b/trace2/tr2_tls.h\nnew file mode 100644\nindex 0000000000..b271ccd8dd\n--- /dev/null\n+++ b/trace2/tr2_tls.h\n@@ -0,0 +1,97 @@\n+#ifndef TR2_TLS_H\n+#define TR2_TLS_H\n+\n+struct strbuf;\n+\n+/*\n+ * Arbitry limit for thread names for column alignment.\n+ */\n+#define TR2_MAX_THREAD_NAME (24)\n+\n+struct tr2tls_thread_ctx {\n+\tstruct strbuf thread_name;\n+\tuint64_t *array_us_start;\n+\tint alloc;\n+\tint nr_open_regions; /* plays role of \"nr\" in ALLOC_GROW */\n+\tint thread_id;\n+};\n+\n+/*\n+ * Create TLS data for the current thread.  This gives us a place to\n+ * put per-thread data, such as thread start time, function nesting\n+ * and a per-thread label for our messages.\n+ *\n+ * We assume the first thread is \"main\".  Other threads are given\n+ * non-zero thread-ids to help distinguish messages from concurrent\n+ * threads.\n+ *\n+ * Truncate the thread name if necessary to help with column alignment\n+ * in printf-style messages.\n+ *\n+ * In this and all following functions the term \"self\" refers to the\n+ * current thread.\n+ */\n+struct tr2tls_thread_ctx *tr2tls_create_self(const char *thread_name);\n+\n+/*\n+ * Get our TLS data.\n+ */\n+struct tr2tls_thread_ctx *tr2tls_get_self(void);\n+\n+/*\n+ * return true if the current thread is the main thread.\n+ */\n+int tr2tls_is_main_thread(void);\n+\n+/*\n+ * Free our TLS data.\n+ */\n+void tr2tls_unset_self(void);\n+\n+/*\n+ * Begin a new nested region and remember the start time.\n+ */\n+void tr2tls_push_self(uint64_t us_now);\n+\n+/*\n+ * End the innermost nested region.\n+ */\n+void tr2tls_pop_self(void);\n+\n+/*\n+ * Pop any extra (above the first) open regions on the current\n+ * thread and discard.  During a thread-exit, we should only\n+ * have region[0] that was pushed in trace2_thread_start() if\n+ * the thread exits normally.\n+ */\n+void tr2tls_pop_unwind_self(void);\n+\n+/*\n+ * Compute the elapsed time since the innermost region in the\n+ * current thread started and the given time (usually now).\n+ */\n+uint64_t tr2tls_region_elasped_self(uint64_t us);\n+\n+/*\n+ * Compute the elapsed time since the main thread started\n+ * and the given time (usually now).  This is assumed to\n+ * be the absolute run time of the process.\n+ */\n+uint64_t tr2tls_absolute_elapsed(uint64_t us);\n+\n+/*\n+ * Initialize the tr2 TLS system.\n+ */\n+void tr2tls_init(void);\n+\n+/*\n+ * Free all tr2 TLS resources.\n+ */\n+void tr2tls_release(void);\n+\n+/*\n+ * Protected increment of an integer.\n+ */\n+int tr2tls_locked_increment(int *p);\n+\n+#endif /* TR2_TLS_H */\ndiff --git a/usage.c b/usage.c\nindex cc803336bd..2fdb20086b 100644\n--- a/usage.c\n+++ b/usage.c\n@@ -22,17 +22,48 @@ void vreportf(const char *prefix, const char *err, va_list params)\n static NORETURN void usage_builtin(const char *err, va_list params)\n {\n \tvreportf(\"usage: \", err, params);\n+\n+\t/*\n+\t * When we detect a usage error *before* the command dispatch in\n+\t * cmd_main(), we don't know what verb to report.  Force it to this\n+\t * to facilitate post-processing.\n+\t */\n+\ttrace2_cmd_name(\"_usage_\");\n+\n+\t/*\n+\t * Currently, the (err, params) are usually just the static usage\n+\t * string which isn't very useful here.  Usually, the call site\n+\t * manually calls fprintf(stderr,...) with the actual detailed\n+\t * syntax error before calling usage().\n+\t *\n+\t * TODO It would be nice to update the call sites to pass both\n+\t * the static usage string and the detailed error message.\n+\t */\n+\n \texit(129);\n }\n \n static NORETURN void die_builtin(const char *err, va_list params)\n {\n+\t/*\n+\t * We call this trace2 function first and expect it to va_copy 'params'\n+\t * before using it (because an 'ap' can only be walked once).\n+\t */\n+\ttrace2_cmd_error_va(err, params);\n+\n \tvreportf(\"fatal: \", err, params);\n+\n \texit(128);\n }\n \n static void error_builtin(const char *err, va_list params)\n {\n+\t/*\n+\t * We call this trace2 function first and expect it to va_copy 'params'\n+\t * before using it (because an 'ap' can only be walked once).\n+\t */\n+\ttrace2_cmd_error_va(err, params);\n+\n \tvreportf(\"error: \", err, params);\n }\n \n-- \ngitgitgadget\n\n"},{"id":"368354","messageId":"5f77c9b63307aab2bc5cd083f27b3a9c2f339ba8.1549043937.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 15/15] trace2: add for_each macros to clang-format","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-01T17:59:11Z","receivedAt":"2019-02-01T17:59:19Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n .clang-format | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/.clang-format b/.clang-format\nindex de1c8b5c77..41d4cd23fd 100644\n--- a/.clang-format\n+++ b/.clang-format\n@@ -149,7 +149,7 @@ Cpp11BracedListStyle: false\n \n # A list of macros that should be interpreted as foreach loops instead of as\n # function calls.\n-ForEachMacros: ['for_each_string_list_item']\n+ForEachMacros: ['for_each_string_list_item', 'for_each_wanted_builtin', 'for_each_builtin', 'for_each_ut']\n \n # The maximum number of consecutive empty lines to keep.\n MaxEmptyLinesToKeep: 1\n-- \ngitgitgadget\n"},{"id":"368355","messageId":"00b25da38bd107b543a34833e5e363385c9eaa37.1549043937.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 14/15] trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-01T17:59:10Z","receivedAt":"2019-02-01T17:59:20Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate unit tests for Trace2.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                  |   1 +\n t/helper/test-tool.c      |   1 +\n t/helper/test-tool.h      |   1 +\n t/helper/test-trace2.c    | 273 ++++++++++++++++++++++++++++++++++++++\n t/t0210-trace2-normal.sh  | 135 +++++++++++++++++++\n t/t0210/scrub_normal.perl |  48 +++++++\n t/t0211-trace2-perf.sh    | 153 +++++++++++++++++++++\n t/t0211/scrub_perf.perl   |  76 +++++++++++\n t/t0212-trace2-event.sh   | 234 ++++++++++++++++++++++++++++++++\n t/t0212/parse_events.perl | 251 +++++++++++++++++++++++++++++++++++\n 10 files changed, 1173 insertions(+)\n create mode 100644 t/helper/test-trace2.c\n create mode 100755 t/t0210-trace2-normal.sh\n create mode 100644 t/t0210/scrub_normal.perl\n create mode 100755 t/t0211-trace2-perf.sh\n create mode 100644 t/t0211/scrub_perf.perl\n create mode 100755 t/t0212-trace2-event.sh\n create mode 100644 t/t0212/parse_events.perl\n\ndiff --git a/Makefile b/Makefile\nindex 2ab7b73000..040f5846d2 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -762,6 +762,7 @@ TEST_BUILTINS_OBJS += test-string-list.o\n TEST_BUILTINS_OBJS += test-submodule-config.o\n TEST_BUILTINS_OBJS += test-submodule-nested-repo-config.o\n TEST_BUILTINS_OBJS += test-subprocess.o\n+TEST_BUILTINS_OBJS += test-trace2.o\n TEST_BUILTINS_OBJS += test-urlmatch-normalization.o\n TEST_BUILTINS_OBJS += test-wildmatch.o\n TEST_BUILTINS_OBJS += test-windows-named-pipe.o\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 4dbfff7dee..25b1294353 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -51,6 +51,7 @@ static struct test_cmd cmds[] = {\n \t{ \"submodule-config\", cmd__submodule_config },\n \t{ \"submodule-nested-repo-config\", cmd__submodule_nested_repo_config },\n \t{ \"subprocess\", cmd__subprocess },\n+\t{ \"trace2\", cmd__trace2 },\n \t{ \"urlmatch-normalization\", cmd__urlmatch_normalization },\n \t{ \"wildmatch\", cmd__wildmatch },\n #ifdef GIT_WINDOWS_NATIVE\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex ca5c88edb2..61d2938ce5 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -46,6 +46,7 @@ int cmd__string_list(int argc, const char **argv);\n int cmd__submodule_config(int argc, const char **argv);\n int cmd__submodule_nested_repo_config(int argc, const char **argv);\n int cmd__subprocess(int argc, const char **argv);\n+int cmd__trace2(int argc, const char **argv);\n int cmd__urlmatch_normalization(int argc, const char **argv);\n int cmd__wildmatch(int argc, const char **argv);\n #ifdef GIT_WINDOWS_NATIVE\ndiff --git a/t/helper/test-trace2.c b/t/helper/test-trace2.c\nnew file mode 100644\nindex 0000000000..197819c872\n--- /dev/null\n+++ b/t/helper/test-trace2.c\n@@ -0,0 +1,273 @@\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"argv-array.h\"\n+#include \"run-command.h\"\n+#include \"exec-cmd.h\"\n+#include \"config.h\"\n+\n+typedef int(fn_unit_test)(int argc, const char **argv);\n+\n+struct unit_test {\n+\tfn_unit_test *ut_fn;\n+\tconst char *ut_name;\n+\tconst char *ut_usage;\n+};\n+\n+#define MyOk 0\n+#define MyError 1\n+\n+static int get_i(int *p_value, const char *data)\n+{\n+\tchar *endptr;\n+\n+\tif (!data || !*data)\n+\t\treturn MyError;\n+\n+\t*p_value = strtol(data, &endptr, 10);\n+\tif (*endptr || errno == ERANGE)\n+\t\treturn MyError;\n+\n+\treturn MyOk;\n+}\n+\n+/*\n+ * Cause process to exit with the requested value via \"return\".\n+ *\n+ * Rely on test-tool.c:cmd_main() to call trace2_cmd_exit()\n+ * with our result.\n+ *\n+ * Test harness can confirm:\n+ * [] the process-exit value.\n+ * [] the \"code\" field in the \"exit\" trace2 event.\n+ * [] the \"code\" field in the \"atexit\" trace2 event.\n+ * [] the \"name\" field in the \"cmd_name\" trace2 event.\n+ * [] \"def_param\" events for all of the \"interesting\" pre-defined\n+ * config settings.\n+ */\n+static int ut_001return(int argc, const char **argv)\n+{\n+\tint rc;\n+\n+\tif (get_i(&rc, argv[0]))\n+\t\tdie(\"expect <exit_code>\");\n+\n+\treturn rc;\n+}\n+\n+/*\n+ * Cause the process to exit with the requested value via \"exit()\".\n+ *\n+ * Test harness can confirm:\n+ * [] the \"code\" field in the \"exit\" trace2 event.\n+ * [] the \"code\" field in the \"atexit\" trace2 event.\n+ * [] the \"name\" field in the \"cmd_name\" trace2 event.\n+ * [] \"def_param\" events for all of the \"interesting\" pre-defined\n+ * config settings.\n+ */\n+static int ut_002exit(int argc, const char **argv)\n+{\n+\tint rc;\n+\n+\tif (get_i(&rc, argv[0]))\n+\t\tdie(\"expect <exit_code>\");\n+\n+\texit(rc);\n+}\n+\n+/*\n+ * Send an \"error\" event with each value in argv.  Normally, git only issues\n+ * a single \"error\" event immediately before issuing an \"exit\" event (such\n+ * as in die() or BUG()), but multiple \"error\" events are allowed.\n+ *\n+ * Test harness can confirm:\n+ * [] a trace2 \"error\" event for each value in argv.\n+ * [] the \"name\" field in the \"cmd_name\" trace2 event.\n+ * [] (optional) the file:line in the \"exit\" event refers to this function.\n+ */\n+static int ut_003error(int argc, const char **argv)\n+{\n+\tint k;\n+\n+\tif (!argv[0] || !*argv[0])\n+\t\tdie(\"expect <error_message>\");\n+\n+\tfor (k = 0; k < argc; k++)\n+\t\terror(\"%s\", argv[k]);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Run a child process and wait for it to finish and exit with its return code.\n+ * test-tool trace2 004child [<child-command-line>]\n+ *\n+ * For example:\n+ * test-tool trace2 004child git version\n+ * test-tool trace2 004child test-tool trace2 001return 0\n+ * test-tool trace2 004child test-tool trace2 004child test-tool trace2 004child\n+ * test-tool trace2 004child git -c alias.xyz=version xyz\n+ *\n+ * Test harness can confirm:\n+ * [] the \"name\" field in the \"cmd_name\" trace2 event.\n+ * [] that the outer process has a single component SID (or depth \"d0\" in\n+ *    the PERF stream).\n+ * [] that \"child_start\" and \"child_exit\" events are generated for the child.\n+ * [] if the child process is an instrumented executable:\n+ *    [] that \"version\", \"start\", ..., \"exit\", and \"atexit\" events are\n+ *       generated by the child process.\n+ *    [] that the child process events have a multiple component SID (or\n+ *       depth \"dN+1\" in the PERF stream).\n+ * [] that the child exit code is propagated to the parent process \"exit\"\n+ *    and \"atexit\" events..\n+ * [] (optional) that the \"t_abs\" field in the child process \"atexit\" event\n+ *    is less than the \"t_rel\" field in the \"child_exit\" event of the parent\n+ *    process.\n+ * [] if the child process is like the alias example above,\n+ *    [] (optional) the child process attempts to run \"git-xyx\" as a dashed\n+ *       command.\n+ *    [] the child process emits an \"alias\" event with \"xyz\" => \"version\"\n+ *    [] the child process runs \"git version\" as a child process.\n+ *    [] the child process has a 3 component SID (or depth \"d2\" in the PERF\n+ *       stream).\n+ */\n+static int ut_004child(int argc, const char **argv)\n+{\n+\tint result;\n+\n+\t/*\n+\t * Allow empty <child_command_line> so we can do arbitrarily deep\n+\t * command nesting and let the last one be null.\n+\t */\n+\tif (!argc)\n+\t\treturn 0;\n+\n+\tresult = run_command_v_opt(argv, 0);\n+\texit(result);\n+}\n+\n+/*\n+ * Exec a git command.  This may either create a child process (Windows)\n+ * or replace the existing process.\n+ * test-tool trace2 005exec <git_command_args>\n+ *\n+ * For example:\n+ * test-tool trace2 005exec version\n+ *\n+ * Test harness can confirm (on Windows):\n+ * [] the \"name\" field in the \"cmd_name\" trace2 event.\n+ * [] that the outer process has a single component SID (or depth \"d0\" in\n+ *    the PERF stream).\n+ * [] that \"exec\" and \"exec_result\" events are generated for the child\n+ *    process (since the Windows compatibility layer fakes an exec() with\n+ *    a CreateProcess(), WaitForSingleObject(), and exit()).\n+ * [] that the child process has multiple component SID (or depth \"dN+1\"\n+ *    in the PERF stream).\n+ *\n+ * Test harness can confirm (on platforms with a real exec() function):\n+ * [] TODO talk about process replacement and how it affects SID.\n+ */\n+static int ut_005exec(int argc, const char **argv)\n+{\n+\tint result;\n+\n+\tif (!argc)\n+\t\treturn 0;\n+\n+\tresult = execv_git_cmd(argv);\n+\treturn result;\n+}\n+\n+static int ut_006data(int argc, const char **argv)\n+{\n+\tconst char *usage_error =\n+\t\t\"expect <cat0> <k0> <v0> [<cat1> <k1> <v1> [...]]\";\n+\n+\tif (argc % 3 != 0)\n+\t\tdie(\"%s\", usage_error);\n+\n+\twhile (argc) {\n+\t\tif (!argv[0] || !*argv[0] || !argv[1] || !*argv[1] ||\n+\t\t    !argv[2] || !*argv[2])\n+\t\t\tdie(\"%s\", usage_error);\n+\n+\t\ttrace2_data_string(argv[0], the_repository, argv[1], argv[2]);\n+\t\targv += 3;\n+\t\targc -= 3;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Usage:\n+ *     test-tool trace2 <ut_name_1> <ut_usage_1>\n+ *     test-tool trace2 <ut_name_2> <ut_usage_2>\n+ *     ...\n+ */\n+#define USAGE_PREFIX \"test-tool trace2\"\n+\n+/* clang-format off */\n+static struct unit_test ut_table[] = {\n+\t{ ut_001return,   \"001return\", \"<exit_code>\" },\n+\t{ ut_002exit,     \"002exit\",   \"<exit_code>\" },\n+\t{ ut_003error,    \"003error\",  \"<error_message>+\" },\n+\t{ ut_004child,    \"004child\",  \"[<child_command_line>]\" },\n+\t{ ut_005exec,     \"005exec\",   \"<git_command_args>\" },\n+\t{ ut_006data,     \"006data\",   \"[<category> <key> <value>]+\" },\n+};\n+/* clang-format on */\n+\n+/* clang-format off */\n+#define for_each_ut(k, ut_k)\t\t\t\\\n+\tfor (k = 0, ut_k = &ut_table[k];\t\\\n+\t     k < ARRAY_SIZE(ut_table);\t\t\\\n+\t     k++, ut_k = &ut_table[k])\n+/* clang-format on */\n+\n+static int print_usage(void)\n+{\n+\tint k;\n+\tstruct unit_test *ut_k;\n+\n+\tfprintf(stderr, \"usage:\\n\");\n+\tfor_each_ut (k, ut_k)\n+\t\tfprintf(stderr, \"\\t%s %s %s\\n\", USAGE_PREFIX, ut_k->ut_name,\n+\t\t\tut_k->ut_usage);\n+\n+\treturn 129;\n+}\n+\n+/*\n+ * Issue various trace2 events for testing.\n+ *\n+ * We assume that these trace2 routines has already been called:\n+ *    [] trace2_initialize()      [common-main.c:main()]\n+ *    [] trace2_cmd_start()       [common-main.c:main()]\n+ *    [] trace2_cmd_name()        [test-tool.c:cmd_main()]\n+ *    [] tracd2_cmd_list_config() [test-tool.c:cmd_main()]\n+ * So that:\n+ *    [] the various trace2 streams are open.\n+ *    [] the process SID has been created.\n+ *    [] the \"version\" event has been generated.\n+ *    [] the \"start\" event has been generated.\n+ *    [] the \"cmd_name\" event has been generated.\n+ *    [] this writes various \"def_param\" events for interesting config values.\n+ *\n+ * We further assume that if we return (rather than exit()), trace2_cmd_exit()\n+ * will be called by test-tool.c:cmd_main().\n+ */\n+int cmd__trace2(int argc, const char **argv)\n+{\n+\tint k;\n+\tstruct unit_test *ut_k;\n+\n+\targc--; /* skip over \"trace2\" arg */\n+\targv++;\n+\n+\tif (argc)\n+\t\tfor_each_ut (k, ut_k)\n+\t\t\tif (!strcmp(argv[0], ut_k->ut_name))\n+\t\t\t\treturn ut_k->ut_fn(argc - 1, argv + 1);\n+\n+\treturn print_usage();\n+}\ndiff --git a/t/t0210-trace2-normal.sh b/t/t0210-trace2-normal.sh\nnew file mode 100755\nindex 0000000000..03a0aedb1d\n--- /dev/null\n+++ b/t/t0210-trace2-normal.sh\n@@ -0,0 +1,135 @@\n+#!/bin/sh\n+\n+test_description='test trace2 facility (normal target)'\n+. ./test-lib.sh\n+\n+# Add t/helper directory to PATH so that we can use a relative\n+# path to run nested instances of test-tool.exe (see 004child).\n+# This helps with HEREDOC comparisons later.\n+TTDIR=\"$GIT_BUILD_DIR/t/helper/\" && export TTDIR\n+PATH=\"$TTDIR:$PATH\" && export PATH\n+\n+# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe\n+# Warning: to do the actual diff/comparison, so the HEREDOCs here\n+# Warning: only cover our actual calls to test-tool and/or git.\n+# Warning: So you may see extra lines in artifact files when\n+# Warning: interactively debugging.\n+\n+# Turn off any inherited trace2 settings for this test.\n+unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT\n+unset GIT_TR2_BRIEF\n+unset GIT_TR2_CONFIG_PARAMS\n+\n+V=$(git version | sed -e 's/^git version //') && export V\n+\n+# There are multiple trace2 targets: normal, perf, and event.\n+# Trace2 events will/can be written to each active target (subject\n+# to whatever filtering that target decides to do).\n+# This script tests the normal target in isolation.\n+#\n+# Defer setting GIT_TR2 until the actual command line we want to test\n+# because hidden git and test-tool commands run by the test harness\n+# can contaminate our output.\n+\n+# Enable \"brief\" feature which turns off \"<clock> <file>:<line> \" prefix.\n+GIT_TR2_BRIEF=1 && export GIT_TR2_BRIEF\n+\n+# Basic tests of the trace2 normal stream.  Since this stream is used\n+# primarily with printf-style debugging/tracing, we do limited testing\n+# here.\n+#\n+# We do confirm the following API features:\n+# [] the 'version <v>' event\n+# [] the 'start <argv>' event\n+# [] the 'cmd_name <name>' event\n+# [] the 'exit <time> code:<code>' event\n+# [] the 'atexit <time> code:<code>' event\n+#\n+# Fields of the form _FIELD_ are tokens that have been replaced (such\n+# as the elapsed time).\n+\n+# Verb 001return\n+#\n+# Implicit return from cmd_<verb> function propagates <code>.\n+\n+test_expect_success 'normal stream, return code 0' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\tGIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 001return 0\n+\t\tcmd_name trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:0\n+\t\tatexit elapsed:_TIME_ code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'normal stream, return code 1' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\ttest_must_fail env GIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 001return 1 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 001return 1\n+\t\tcmd_name trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:1\n+\t\tatexit elapsed:_TIME_ code:1\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 002exit\n+#\n+# Explicit exit(code) from within cmd_<verb> propagates <code>.\n+\n+test_expect_success 'normal stream, exit code 0' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\tGIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 002exit 0 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 002exit 0\n+\t\tcmd_name trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:0\n+\t\tatexit elapsed:_TIME_ code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'normal stream, exit code 1' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\ttest_must_fail env GIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 002exit 1 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 002exit 1\n+\t\tcmd_name trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:1\n+\t\tatexit elapsed:_TIME_ code:1\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 003error\n+#\n+# To the above, add multiple 'error <msg>' events\n+\n+test_expect_success 'normal stream, error event' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\tGIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 003error \"hello world\" \"this is a test\" &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 003error '\\''hello world'\\'' '\\''this is a test'\\''\n+\t\tcmd_name trace2 (trace2)\n+\t\terror hello world\n+\t\terror this is a test\n+\t\texit elapsed:_TIME_ code:0\n+\t\tatexit elapsed:_TIME_ code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t0210/scrub_normal.perl b/t/t0210/scrub_normal.perl\nnew file mode 100644\nindex 0000000000..c65d1a815e\n--- /dev/null\n+++ b/t/t0210/scrub_normal.perl\n@@ -0,0 +1,48 @@\n+#!/usr/bin/perl\n+#\n+# Scrub the variable fields from the normal trace2 output to\n+# make testing easier.\n+\n+use strict;\n+use warnings;\n+\n+my $float = '[0-9]*\\.[0-9]+([eE][-+]?[0-9]+)?';\n+\n+# This code assumes that the trace2 data was written with bare\n+# turned on (which omits the \"<clock> <file>:<line>\" prefix.\n+\n+while (<>) {\n+    # Various messages include an elapsed time in the middle\n+    # of the message.  Replace the time with a placeholder to\n+    # simplify our HEREDOC in the test script.\n+    s/elapsed:$float/elapsed:_TIME_/g;\n+\n+    my $line = $_;\n+\n+    # we expect:\n+    #    start <argv0> [<argv1> [<argv2> [...]]]\n+    #\n+    # where argv0 might be a relative or absolute path, with\n+    # or without quotes, and platform dependent.  Replace argv0\n+    # with a token for HEREDOC matching in the test script.\n+\n+    if ($line =~ m/^start/) {\n+\t$line =~ /^start\\s+(.*)/;\n+\tmy $argv = $1;\n+\t$argv =~ m/(\\'[^\\']*\\'|[^ ]+)\\s+(.*)/;\n+\tmy $argv_0 = $1;\n+\tmy $argv_rest = $2;\n+\n+\tprint \"start _EXE_ $argv_rest\\n\";\n+    }\n+    elsif ($line =~ m/^cmd_path/) {\n+\t# Likewise, the 'cmd_path' message breaks out argv[0].\n+\t#\n+\t# This line is only emitted when RUNTIME_PREFIX is defined,\n+\t# so just omit it for testing purposes.\n+\t# print \"cmd_path _EXE_\\n\";\n+    }\n+    else {\n+\tprint \"$line\";\n+    }\n+}\ndiff --git a/t/t0211-trace2-perf.sh b/t/t0211-trace2-perf.sh\nnew file mode 100755\nindex 0000000000..953e2f7847\n--- /dev/null\n+++ b/t/t0211-trace2-perf.sh\n@@ -0,0 +1,153 @@\n+#!/bin/sh\n+\n+test_description='test trace2 facility (perf target)'\n+. ./test-lib.sh\n+\n+# Add t/helper directory to PATH so that we can use a relative\n+# path to run nested instances of test-tool.exe (see 004child).\n+# This helps with HEREDOC comparisons later.\n+TTDIR=\"$GIT_BUILD_DIR/t/helper/\" && export TTDIR\n+PATH=\"$TTDIR:$PATH\" && export PATH\n+\n+# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe\n+# Warning: to do the actual diff/comparison, so the HEREDOCs here\n+# Warning: only cover our actual calls to test-tool and/or git.\n+# Warning: So you may see extra lines in artifact files when\n+# Warning: interactively debugging.\n+\n+# Turn off any inherited trace2 settings for this test.\n+unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT\n+unset GIT_TR2_PERF_BRIEF\n+unset GIT_TR2_CONFIG_PARAMS\n+\n+V=$(git version | sed -e 's/^git version //') && export V\n+\n+# There are multiple trace2 targets: normal, perf, and event.\n+# Trace2 events will/can be written to each active target (subject\n+# to whatever filtering that target decides to do).\n+# Test each target independently.\n+#\n+# Defer setting GIT_TR2_PERF until the actual command we want to\n+# test because hidden git and test-tool commands in the test\n+# harness can contaminate our output.\n+\n+# Enable \"brief\" feature which turns off the prefix:\n+#     \"<clock> <file>:<line> | <nr_parents> | \"\n+GIT_TR2_PERF_BRIEF=1 && export GIT_TR2_PERF_BRIEF\n+\n+# Repeat some of the t0210 tests using the perf target stream instead of\n+# the normal stream.\n+#\n+# Tokens here of the form _FIELD_ have been replaced in the observed output.\n+\n+# Verb 001return\n+#\n+# Implicit return from cmd_<verb> function propagates <code>.\n+\n+test_expect_success 'perf stream, return code 0' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 001return 0\n+\t\td0|main|cmd_name|||||trace2 (trace2)\n+\t\td0|main|exit||_T_ABS_|||code:0\n+\t\td0|main|atexit||_T_ABS_|||code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'perf stream, return code 1' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\ttest_must_fail env GIT_TR2_PERF=\"$(pwd)/trace.perf\" test-tool trace2 001return 1 &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 001return 1\n+\t\td0|main|cmd_name|||||trace2 (trace2)\n+\t\td0|main|exit||_T_ABS_|||code:1\n+\t\td0|main|atexit||_T_ABS_|||code:1\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 003error\n+#\n+# To the above, add multiple 'error <msg>' events\n+\n+test_expect_success 'perf stream, error event' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" test-tool trace2 003error \"hello world\" \"this is a test\" &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 003error '\\''hello world'\\'' '\\''this is a test'\\''\n+\t\td0|main|cmd_name|||||trace2 (trace2)\n+\t\td0|main|error|||||hello world\n+\t\td0|main|error|||||this is a test\n+\t\td0|main|exit||_T_ABS_|||code:0\n+\t\td0|main|atexit||_T_ABS_|||code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 004child\n+#\n+# Test nested spawning of child processes.\n+#\n+# Conceptually, this looks like:\n+#    P1: TT trace2 004child\n+#    P2: |--- TT trace2 004child\n+#    P3:      |--- TT trace2 001return 0\n+#\n+# Which should generate events:\n+#    P1: version\n+#    P1: start\n+#    P1: cmd_name\n+#    P1: child_start\n+#        P2: version\n+#        P2: start\n+#        P2: cmd_name\n+#        P2: child_start\n+#            P3: version\n+#            P3: start\n+#            P3: cmd_name\n+#            P3: exit\n+#            P3: atexit\n+#        P2: child_exit\n+#        P2: exit\n+#        P2: atexit\n+#    P1: child_exit\n+#    P1: exit\n+#    P1: atexit\n+\n+test_expect_success 'perf stream, child processes' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" test-tool trace2 004child test-tool trace2 004child test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 004child test-tool trace2 004child test-tool trace2 001return 0\n+\t\td0|main|cmd_name|||||trace2 (trace2)\n+\t\td0|main|child_start||_T_ABS_|||[ch0] class:? argv: test-tool trace2 004child test-tool trace2 001return 0\n+\t\td1|main|version|||||$V\n+\t\td1|main|start|||||_EXE_ trace2 004child test-tool trace2 001return 0\n+\t\td1|main|cmd_name|||||trace2 (trace2/trace2)\n+\t\td1|main|child_start||_T_ABS_|||[ch0] class:? argv: test-tool trace2 001return 0\n+\t\td2|main|version|||||$V\n+\t\td2|main|start|||||_EXE_ trace2 001return 0\n+\t\td2|main|cmd_name|||||trace2 (trace2/trace2/trace2)\n+\t\td2|main|exit||_T_ABS_|||code:0\n+\t\td2|main|atexit||_T_ABS_|||code:0\n+\t\td1|main|child_exit||_T_ABS_|_T_REL_||[ch0] pid:_PID_ code:0\n+\t\td1|main|exit||_T_ABS_|||code:0\n+\t\td1|main|atexit||_T_ABS_|||code:0\n+\t\td0|main|child_exit||_T_ABS_|_T_REL_||[ch0] pid:_PID_ code:0\n+\t\td0|main|exit||_T_ABS_|||code:0\n+\t\td0|main|atexit||_T_ABS_|||code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t0211/scrub_perf.perl b/t/t0211/scrub_perf.perl\nnew file mode 100644\nindex 0000000000..351af7844e\n--- /dev/null\n+++ b/t/t0211/scrub_perf.perl\n@@ -0,0 +1,76 @@\n+#!/usr/bin/perl\n+#\n+# Scrub the variable fields from the perf trace2 output to\n+# make testing easier.\n+\n+use strict;\n+use warnings;\n+\n+my $qpath = '\\'[^\\']*\\'|[^ ]*';\n+\n+my $col_depth=0;\n+my $col_thread=1;\n+my $col_event=2;\n+my $col_repo=3;\n+my $col_t_abs=4;\n+my $col_t_rel=5;\n+my $col_category=6;\n+my $col_rest=7;\n+\n+# This code assumes that the trace2 data was written with bare\n+# turned on (which omits the \"<clock> <file>:<line> | <parents>\"\n+# prefix.\n+\n+while (<>) {\n+    my @tokens = split /\\|/;\n+\n+    foreach my $col (@tokens) { $col =~ s/^\\s+|\\s+$//g; }\n+\n+    if ($tokens[$col_event] =~ m/^start/) {\n+\t# The 'start' message lists the contents of argv in $col_rest.\n+\t# On some platforms (Windows), argv[0] is *sometimes* a canonical\n+\t# absolute path to the EXE rather than the value passed in the\n+\t# shell script.  Replace it with a placeholder to simplify our\n+\t# HEREDOC in the test script.\n+\tmy $argv0;\n+\tmy $argvRest;\n+\t$tokens[$col_rest] =~ s/^($qpath)\\W*(.*)/_EXE_ $2/;\n+    }\n+    elsif ($tokens[$col_event] =~ m/cmd_path/) {\n+\t# Likewise, the 'cmd_path' message breaks out argv[0].\n+\t#\n+\t# This line is only emitted when RUNTIME_PREFIX is defined,\n+\t# so just omit it for testing purposes.\n+\t# $tokens[$col_rest] = \"_EXE_\";\n+\tgoto SKIP_LINE;\n+    }\n+    elsif ($tokens[$col_event] =~ m/child_exit/) {\n+\t$tokens[$col_rest] =~ s/ pid:\\d* / pid:_PID_ /;\n+    }\n+    elsif ($tokens[$col_event] =~ m/data/) {\n+\tif ($tokens[$col_category] =~ m/process/) {\n+\t    # 'data' and 'data_json' events containing 'process'\n+\t    # category data are assumed to be platform-specific\n+\t    # and highly variable.  Just omit them.\n+\t    goto SKIP_LINE;\n+\t}\n+    }\n+\n+    # t_abs and t_rel are either blank or a float.  Replace the float\n+    # with a constant for matching the HEREDOC in the test script.\n+    if ($tokens[$col_t_abs] =~ m/\\d/) {\n+\t$tokens[$col_t_abs] = \"_T_ABS_\";\n+    }\n+    if ($tokens[$col_t_rel] =~ m/\\d/) {\n+\t$tokens[$col_t_rel] = \"_T_REL_\";\n+    }\n+\n+    my $out;\n+\n+    $out = join('|', @tokens);\n+    print \"$out\\n\";\n+\n+  SKIP_LINE:\n+}\n+\n+\ndiff --git a/t/t0212-trace2-event.sh b/t/t0212-trace2-event.sh\nnew file mode 100755\nindex 0000000000..ee3ab7ffa0\n--- /dev/null\n+++ b/t/t0212-trace2-event.sh\n@@ -0,0 +1,234 @@\n+#!/bin/sh\n+\n+test_description='test trace2 facility'\n+. ./test-lib.sh\n+\n+# Add t/helper directory to PATH so that we can use a relative\n+# path to run nested instances of test-tool.exe (see 004child).\n+# This helps with HEREDOC comparisons later.\n+TTDIR=\"$GIT_BUILD_DIR/t/helper/\" && export TTDIR\n+PATH=\"$TTDIR:$PATH\" && export PATH\n+\n+# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe\n+# Warning: to do the actual diff/comparison, so the HEREDOCs here\n+# Warning: only cover our actual calls to test-tool and/or git.\n+# Warning: So you may see extra lines in artifact files when\n+# Warning: interactively debugging.\n+\n+# Turn off any inherited trace2 settings for this test.\n+unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT\n+unset GIT_TR2_BARE\n+unset GIT_TR2_CONFIG_PARAMS\n+\n+V=$(git version | sed -e 's/^git version //') && export V\n+\n+# There are multiple trace2 targets: normal, perf, and event.\n+# Trace2 events will/can be written to each active target (subject\n+# to whatever filtering that target decides to do).\n+# Test each target independently.\n+#\n+# Defer setting GIT_TR2_PERF until the actual command we want to\n+# test because hidden git and test-tool commands in the test\n+# harness can contaminate our output.\n+\n+# We don't bother repeating the 001return and 002exit tests, since they\n+# have coverage in the normal and perf targets.\n+\n+# Verb 003error\n+#\n+# To the above, add multiple 'error <msg>' events\n+\n+test_expect_success 'event stream, error event' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 003error \"hello world\" \"this is a test\" &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"003error\",\n+\t|      \"hello world\",\n+\t|      \"this is a test\"\n+\t|    ],\n+\t|    \"errors\":[\n+\t|      \"%s\",\n+\t|      \"%s\"\n+\t|    ],\n+\t|    \"exit_code\":0,\n+\t|    \"hierarchy\":\"trace2\",\n+\t|    \"name\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 004child\n+#\n+# Test nested spawning of child processes.\n+#\n+# Conceptually, this looks like:\n+#    P1: TT trace2 004child\n+#    P2: |--- TT trace2 004child\n+#    P3:      |--- TT trace2 001return 0\n+\n+test_expect_success 'event stream, return code 0' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 004child test-tool trace2 004child test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"004child\",\n+\t|      \"test-tool\",\n+\t|      \"trace2\",\n+\t|      \"004child\",\n+\t|      \"test-tool\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"child\":{\n+\t|      \"0\":{\n+\t|        \"child_argv\":[\n+\t|          \"_EXE_\",\n+\t|          \"trace2\",\n+\t|          \"004child\",\n+\t|          \"test-tool\",\n+\t|          \"trace2\",\n+\t|          \"001return\",\n+\t|          \"0\"\n+\t|        ],\n+\t|        \"child_class\":\"?\",\n+\t|        \"child_code\":0,\n+\t|        \"use_shell\":0\n+\t|      }\n+\t|    },\n+\t|    \"exit_code\":0,\n+\t|    \"hierarchy\":\"trace2\",\n+\t|    \"name\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  },\n+\t|  \"_SID0_/_SID1_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"004child\",\n+\t|      \"test-tool\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"child\":{\n+\t|      \"0\":{\n+\t|        \"child_argv\":[\n+\t|          \"_EXE_\",\n+\t|          \"trace2\",\n+\t|          \"001return\",\n+\t|          \"0\"\n+\t|        ],\n+\t|        \"child_class\":\"?\",\n+\t|        \"child_code\":0,\n+\t|        \"use_shell\":0\n+\t|      }\n+\t|    },\n+\t|    \"exit_code\":0,\n+\t|    \"hierarchy\":\"trace2/trace2\",\n+\t|    \"name\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  },\n+\t|  \"_SID0_/_SID1_/_SID2_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"exit_code\":0,\n+\t|    \"hierarchy\":\"trace2/trace2/trace2\",\n+\t|    \"name\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Test listing of all \"interesting\" config settings.\n+\n+test_expect_success 'event stream, list config' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tgit config --local t0212.abc 1 &&\n+\tgit config --local t0212.def \"hello world\" &&\n+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" GIT_TR2_CONFIG_PARAMS=\"t0212.*\" test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"exit_code\":0,\n+\t|    \"hierarchy\":\"trace2\",\n+\t|    \"name\":\"trace2\",\n+\t|    \"params\":[\n+\t|      {\n+\t|        \"param\":\"t0212.abc\",\n+\t|        \"value\":\"1\"\n+\t|      },\n+\t|      {\n+\t|        \"param\":\"t0212.def\",\n+\t|        \"value\":\"hello world\"\n+\t|      }\n+\t|    ],\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'basic trace2_data' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 006data test_category k1 v1 test_category k2 v2 &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"006data\",\n+\t|      \"test_category\",\n+\t|      \"k1\",\n+\t|      \"v1\",\n+\t|      \"test_category\",\n+\t|      \"k2\",\n+\t|      \"v2\"\n+\t|    ],\n+\t|    \"data\":{\n+\t|      \"test_category\":{\n+\t|        \"k1\":\"v1\",\n+\t|        \"k2\":\"v2\"\n+\t|      }\n+\t|    },\n+\t|    \"exit_code\":0,\n+\t|    \"hierarchy\":\"trace2\",\n+\t|    \"name\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t0212/parse_events.perl b/t/t0212/parse_events.perl\nnew file mode 100644\nindex 0000000000..a2776ba216\n--- /dev/null\n+++ b/t/t0212/parse_events.perl\n@@ -0,0 +1,251 @@\n+#!/usr/bin/perl\n+#\n+# Parse event stream and convert individual events into a summary\n+# record for the process.\n+#\n+# Git.exe generates one or more \"event\" records for each API method,\n+# such as \"start <argv>\" and \"exit <code>\", during the life of the git\n+# process.  Additionally, the input may contain interleaved events\n+# from multiple concurrent git processes and/or multiple threads from\n+# within a git process.\n+#\n+# Accumulate events for each process (based on its unique SID) in a\n+# dictionary and emit process summary records.\n+#\n+# Convert some of the variable fields (such as elapsed time) into\n+# placeholders (or omit them) to make HEREDOC comparisons easier in\n+# the test scripts.\n+#\n+# We may also omit fields not (currently) useful for testing purposes.\n+\n+use strict;\n+use warnings;\n+use JSON::PP;\n+use Data::Dumper;\n+use Getopt::Long;\n+\n+# The version of the trace2 event target format that we understand.\n+# This is reported in the 'version' event in the 'evt' field.\n+# It comes from the GIT_TR2_EVENT_VERSION macro in trace2/tr2_tgt_event.c\n+my $evt_version = '1';\n+\n+my $show_children = 1;\n+my $show_exec     = 1;\n+my $show_threads  = 1;\n+\n+# A hack to generate test HEREDOC data for pasting into the test script.\n+# Usage:\n+#    cd \"t/trash directory.t0212-trace2-event\"\n+#    $TT trace ... >trace.event\n+#    VV=$(../../git.exe version | sed -e 's/^git version //')\n+#    perl ../t0212/parse_events.perl --HEREDOC --VERSION=$VV <trace.event >heredoc\n+# Then paste heredoc into your new test.\n+\n+my $gen_heredoc = 0;\n+my $gen_version = '';\n+\n+GetOptions(\"children!\" => \\$show_children,\n+\t   \"exec!\"     => \\$show_exec,\n+\t   \"threads!\"  => \\$show_threads,\n+\t   \"HEREDOC!\"  => \\$gen_heredoc,\n+\t   \"VERSION=s\" => \\$gen_version    )\n+    or die(\"Error in command line arguments\\n\");\n+\n+\n+# SIDs contains timestamps and PIDs of the process and its parents.\n+# This makes it difficult to match up in a HEREDOC in the test script.\n+# Build a map from actual SIDs to predictable constant values and yet\n+# keep the parent/child relationships.  For example:\n+# {..., \"sid\":\"1539706952458276-8652\", ...}\n+# {..., \"sid\":\"1539706952458276-8652/1539706952649493-15452\", ...}\n+# becomes:\n+# {..., \"sid\":\"_SID1_\", ...}\n+# {..., \"sid\":\"_SID1_/_SID2_\", ...}\n+my $sid_map;\n+my $sid_count = 0;\n+\n+my $processes;\n+\n+while (<>) {\n+    my $line = decode_json( $_ );\n+\n+    my $sid = \"\";\n+    my $sid_sep = \"\";\n+\n+    my $raw_sid = $line->{'sid'};\n+    my @raw_sid_parts = split /\\//, $raw_sid;\n+    foreach my $raw_sid_k (@raw_sid_parts) {\n+\tif (!exists $sid_map->{$raw_sid_k}) {\n+\t    $sid_map->{$raw_sid_k} = '_SID' . $sid_count . '_';\n+\t    $sid_count++;\n+\t}\n+\t$sid = $sid . $sid_sep . $sid_map->{$raw_sid_k};\n+\t$sid_sep = '/';\n+    }\n+    \n+    my $event = $line->{'event'};\n+\n+    if ($event eq 'version') {\n+\t$processes->{$sid}->{'version'} = $line->{'exe'};\n+\tif ($gen_heredoc == 1 && $gen_version eq $line->{'exe'}) {\n+\t    # If we are generating data FOR the test script, replace\n+\t    # the reported git.exe version with a reference to an\n+\t    # environment variable.  When our output is pasted into\n+\t    # the test script, it will then be expanded in future\n+\t    # test runs to the THEN current version of git.exe.\n+\t    # We assume that the test script uses env var $V.\n+\t    $processes->{$sid}->{'version'} = \"\\$V\";\n+\t}\n+    }\n+\n+    elsif ($event eq 'start') {\n+\t$processes->{$sid}->{'argv'} = $line->{'argv'};\n+\t$processes->{$sid}->{'argv'}[0] = \"_EXE_\";\n+    }\n+\n+    elsif ($event eq 'exit') {\n+\t$processes->{$sid}->{'exit_code'} = $line->{'code'};\n+    }\n+\n+    elsif ($event eq 'atexit') {\n+\t$processes->{$sid}->{'exit_code'} = $line->{'code'};\n+    }\n+\n+    elsif ($event eq 'error') {\n+\t# For HEREDOC purposes, use the error message format string if\n+\t# available, rather than the formatted message (which probably\n+\t# has an absolute pathname).\n+\tif (exists $line->{'fmt'}) {\n+\t    push( @{$processes->{$sid}->{'errors'}}, $line->{'fmt'} );\n+\t}\n+\telsif (exists $line->{'msg'}) {\n+\t    push( @{$processes->{$sid}->{'errors'}}, $line->{'msg'} );\n+\t}\n+    }\n+\n+    elsif ($event eq 'cmd_path') {\n+\t## $processes->{$sid}->{'path'} = $line->{'path'};\n+\t#\n+\t# Like in the 'start' event, we need to replace the value of\n+\t# argv[0] with a token for HEREDOC purposes.  However, the\n+\t# event is only emitted when RUNTIME_PREFIX is defined, so\n+\t# just omit it for testing purposes.\n+\t# $processes->{$sid}->{'path'} = \"_EXE_\";\n+    }\n+    \n+    elsif ($event eq 'cmd_name') {\n+\t$processes->{$sid}->{'name'} = $line->{'name'};\n+\t$processes->{$sid}->{'hierarchy'} = $line->{'hierarchy'};\n+    }\n+\n+    elsif ($event eq 'alias') {\n+\t$processes->{$sid}->{'alias'}->{'key'} = $line->{'alias'};\n+\t$processes->{$sid}->{'alias'}->{'argv'} = $line->{'argv'};\n+    }\n+\n+    elsif ($event eq 'def_param') {\n+\tmy $kv;\n+\t$kv->{'param'} = $line->{'param'};\n+\t$kv->{'value'} = $line->{'value'};\n+\tpush( @{$processes->{$sid}->{'params'}}, $kv );\n+    }\n+\n+    elsif ($event eq 'child_start') {\n+\tif ($show_children == 1) {\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_class'} = $line->{'child_class'};\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_argv'} = $line->{'argv'};\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_argv'}[0] = \"_EXE_\";\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'use_shell'} = $line->{'use_shell'} ? 1 : 0;\n+\t}\n+    }\n+\n+    elsif ($event eq 'child_exit') {\n+\tif ($show_children == 1) {\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_code'} = $line->{'code'};\n+\t}\n+    }\n+\n+    # TODO decide what information we want to test from thread events.\n+\n+    elsif ($event eq 'thread_start') {\n+\tif ($show_threads == 1) {\n+\t}\n+    }\n+\n+    elsif ($event eq 'thread_exit') {\n+\tif ($show_threads == 1) {\n+\t}\n+    }\n+\n+    # TODO decide what information we want to test from exec events.\n+\n+    elsif ($event eq 'exec') {\n+\tif ($show_exec == 1) {\n+\t}\n+    }\n+\n+    elsif ($event eq 'exec_result') {\n+\tif ($show_exec == 1) {\n+\t}\n+    }\n+\n+    elsif ($event eq 'def_param') {\n+\t# Accumulate parameter key/value pairs by key rather than in an array\n+\t# so that we get overwrite (last one wins) effects.\n+\t$processes->{$sid}->{'params'}->{$line->{'param'}} = $line->{'value'};\n+    }\n+\n+    elsif ($event eq 'def_repo') {\n+\t# $processes->{$sid}->{'repos'}->{$line->{'repo'}} = $line->{'worktree'};\n+\t$processes->{$sid}->{'repos'}->{$line->{'repo'}} = \"_WORKTREE_\";\n+    }\n+\n+    # A series of potentially nested and threaded region and data events\n+    # is fundamentally incompatibile with the type of summary record we\n+    # are building in this script.  Since they are intended for\n+    # perf-trace-like analysis rather than a result summary, we ignore\n+    # most of them here.\n+\n+    # elsif ($event eq 'region_enter') {\n+    # }\n+    # elsif ($event eq 'region_leave') {\n+    # }\n+\n+    elsif ($event eq 'data') {\n+\tmy $cat = $line->{'category'};\n+\tif ($cat eq 'test_category') {\n+\t    \n+\t    my $key = $line->{'key'};\n+\t    my $value = $line->{'value'};\n+\t    $processes->{$sid}->{'data'}->{$cat}->{$key} = $value;\n+\t}\n+    }\n+\n+    # This trace2 target does not emit 'printf' events.\n+    #\n+    # elsif ($event eq 'printf') {\n+    # }\n+}\n+\n+# Dump the resulting hash into something that we can compare against\n+# in the test script.  These options make Dumper output look a little\n+# bit like JSON.  Also convert variable references of the form \"$VAR*\"\n+# so that the matching HEREDOC doesn't need to escape it.\n+\n+$Data::Dumper::Sortkeys = 1;\n+$Data::Dumper::Indent = 1;\n+$Data::Dumper::Purity = 1;\n+$Data::Dumper::Pair = ':';\n+\n+my $out = Dumper($processes);\n+$out =~ s/'/\"/g;\n+$out =~ s/\\$VAR/VAR/g;\n+\n+# Finally, if we're running this script to generate (manually confirmed)\n+# data to add to the test script, guard the indentation.\n+\n+if ($gen_heredoc == 1) {\n+    $out =~ s/^/\\t\\|/gms;\n+}\n+\n+print $out;\n-- \ngitgitgadget\n\n"},{"id":"368357","messageId":"cc612010610c2f9ff90e412a6f798976118fc028.1549043937.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 13/15] trace2:data: add subverb for rebase","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-01T17:59:10Z","receivedAt":"2019-02-01T17:59:22Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/rebase.c | 17 +++++++++++++++++\n 1 file changed, 17 insertions(+)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 774264bae8..f5ac4fe2ea 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -850,6 +850,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tACTION_EDIT_TODO,\n \t\tACTION_SHOW_CURRENT_PATCH,\n \t} action = NO_ACTION;\n+\tstatic const char *action_names[] = { N_(\"undefined\"),\n+\t\t\t\t\t      N_(\"continue\"),\n+\t\t\t\t\t      N_(\"skip\"),\n+\t\t\t\t\t      N_(\"abort\"),\n+\t\t\t\t\t      N_(\"quit\"),\n+\t\t\t\t\t      N_(\"edit_todo\"),\n+\t\t\t\t\t      N_(\"show_current_patch\"),\n+\t\t\t\t\t      NULL };\n \tconst char *gpg_sign = NULL;\n \tstruct string_list exec = STRING_LIST_INIT_NODUP;\n \tconst char *rebase_merges = NULL;\n@@ -1039,6 +1047,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tdie(_(\"The --edit-todo action can only be used during \"\n \t\t      \"interactive rebase.\"));\n \n+\tif (trace2_is_enabled()) {\n+\t\tif (is_interactive(&options))\n+\t\t\ttrace2_cmd_mode(\"interactive\");\n+\t\telse if (exec.nr)\n+\t\t\ttrace2_cmd_mode(\"interactive-exec\");\n+\t\telse\n+\t\t\ttrace2_cmd_mode(action_names[action]);\n+\t}\n+\n \tswitch (action) {\n \tcase ACTION_CONTINUE: {\n \t\tstruct object_id head;\n-- \ngitgitgadget\n\n"},{"id":"368359","messageId":"ab74ef5f23676e48db29f08cb9e864fb3afb408c.1549043936.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 10/15] trace2:data: pack-objects: add trace2 regions","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-01T17:59:07Z","receivedAt":"2019-02-01T17:59:23Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWhen studying the performance of 'git push' we would like to know\nhow much time is spent at various parts of the command. One area\nthat could cause performance trouble is 'git pack-objects'.\n\nAdd trace2 regions around the three main actions taken in this\ncommand:\n\n1. Enumerate objects.\n2. Prepare pack.\n3. Write pack-file.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/pack-objects.c | 16 +++++++++++++++-\n 1 file changed, 15 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 0a70d04604..8a64c2868e 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -33,6 +33,7 @@\n #include \"object-store.h\"\n #include \"dir.h\"\n #include \"midx.h\"\n+#include \"trace2.h\"\n \n #define IN_PACK(obj) oe_in_pack(&to_pack, obj)\n #define SIZE(obj) oe_size(&to_pack, obj)\n@@ -3472,6 +3473,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n+\ttrace2_region_enter(\"pack-objects\", \"enumerate-objects\",\n+\t\t\t    the_repository);\n \tprepare_packing_data(the_repository, &to_pack);\n \n \tif (progress)\n@@ -3486,12 +3489,23 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)\n \tif (include_tag && nr_result)\n \t\tfor_each_ref(add_ref_tag, NULL);\n \tstop_progress(&progress_state);\n+\ttrace2_region_leave(\"pack-objects\", \"enumerate-objects\",\n+\t\t\t    the_repository);\n \n \tif (non_empty && !nr_result)\n \t\treturn 0;\n-\tif (nr_result)\n+\tif (nr_result) {\n+\t\ttrace2_region_enter(\"pack-objects\", \"prepare-pack\",\n+\t\t\t\t    the_repository);\n \t\tprepare_pack(window, depth);\n+\t\ttrace2_region_leave(\"pack-objects\", \"prepare-pack\",\n+\t\t\t\t    the_repository);\n+\t}\n+\n+\ttrace2_region_enter(\"pack-objects\", \"write-pack-file\", the_repository);\n \twrite_pack_file();\n+\ttrace2_region_leave(\"pack-objects\", \"write-pack-file\", the_repository);\n+\n \tif (progress)\n \t\tfprintf_ln(stderr,\n \t\t\t   _(\"Total %\"PRIu32\" (delta %\"PRIu32\"),\"\n-- \ngitgitgadget\n\n"},{"id":"368361","messageId":"5c7f0de22859094ac0d1abef0e0c719a61e9e8c1.1549043937.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 12/15] trace2:data: add subverb to reset command","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-01T17:59:09Z","receivedAt":"2019-02-01T17:59:25Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/reset.c | 6 ++++++\n 1 file changed, 6 insertions(+)\n\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex 59898c972e..4e34c61401 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -340,6 +340,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \tif (patch_mode) {\n \t\tif (reset_type != NONE)\n \t\t\tdie(_(\"--patch is incompatible with --{hard,mixed,soft}\"));\n+\t\ttrace2_cmd_mode(\"patch-interactive\");\n \t\treturn run_add_interactive(rev, \"--patch=reset\", &pathspec);\n \t}\n \n@@ -356,6 +357,11 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \tif (reset_type == NONE)\n \t\treset_type = MIXED; /* by default */\n \n+\tif (pathspec.nr)\n+\t\ttrace2_cmd_mode(\"path\");\n+\telse\n+\t\ttrace2_cmd_mode(reset_type_names[reset_type]);\n+\n \tif (reset_type != SOFT && (reset_type != MIXED || get_git_work_tree()))\n \t\tsetup_work_tree();\n \n-- \ngitgitgadget\n\n"},{"id":"368358","messageId":"3cd525b80edcba78b097b805ce2937e6e54cbdec.1549043936.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 07/15] trace2:data: add trace2 transport child classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-01T17:59:04Z","receivedAt":"2019-02-01T17:59:27Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 child classification for transport processes.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n connect.c          | 3 +++\n transport-helper.c | 2 ++\n 2 files changed, 5 insertions(+)\n\ndiff --git a/connect.c b/connect.c\nindex 24281b6082..3c6f829a05 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -1251,6 +1251,7 @@ struct child_process *git_connect(int fd[2], const char *url,\n \t\tconn = NULL;\n \t} else if (protocol == PROTO_GIT) {\n \t\tconn = git_connect_git(fd, hostandport, path, prog, version, flags);\n+\t\tconn->trace2_child_class = \"transport/git\";\n \t} else {\n \t\tstruct strbuf cmd = STRBUF_INIT;\n \t\tconst char *const *var;\n@@ -1293,9 +1294,11 @@ struct child_process *git_connect(int fd[2], const char *url,\n \t\t\t\tstrbuf_release(&cmd);\n \t\t\t\treturn NULL;\n \t\t\t}\n+\t\t\tconn->trace2_child_class = \"transport/ssh\";\n \t\t\tfill_ssh_args(conn, ssh_host, port, version, flags);\n \t\t} else {\n \t\t\ttransport_check_allowed(\"file\");\n+\t\t\tconn->trace2_child_class = \"transport/file\";\n \t\t\tif (version > 0) {\n \t\t\t\targv_array_pushf(&conn->env_array, GIT_PROTOCOL_ENVIRONMENT \"=version=%d\",\n \t\t\t\t\t\t version);\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 6cf3bb324e..a01cc0093f 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -127,6 +127,8 @@ static struct child_process *get_helper(struct transport *transport)\n \t\targv_array_pushf(&helper->env_array, \"%s=%s\",\n \t\t\t\t GIT_DIR_ENVIRONMENT, get_git_dir());\n \n+\thelper->trace2_child_class = helper->args.argv[0]; /* \"remote-<name>\" */\n+\n \tcode = start_command(helper);\n \tif (code < 0 && errno == ENOENT)\n \t\tdie(_(\"unable to find remote helper for '%s'\"), data->name);\n-- \ngitgitgadget\n\n"},{"id":"368356","messageId":"30bcea9435558edae4836a24cb2bf5f30a579321.1549043936.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v5 05/15] trace2:data: add editor/pager child classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-01T17:59:02Z","receivedAt":"2019-02-01T17:59:28Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 process classification for editor and pager\nchild processes.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n editor.c | 1 +\n pager.c  | 1 +\n 2 files changed, 2 insertions(+)\n\ndiff --git a/editor.c b/editor.c\nindex c985eee1f9..71547674ab 100644\n--- a/editor.c\n+++ b/editor.c\n@@ -78,6 +78,7 @@ static int launch_specified_editor(const char *editor, const char *path,\n \t\tp.argv = args;\n \t\tp.env = env;\n \t\tp.use_shell = 1;\n+\t\tp.trace2_child_class = \"editor\";\n \t\tif (start_command(&p) < 0)\n \t\t\treturn error(\"unable to start editor '%s'\", editor);\n \ndiff --git a/pager.c b/pager.c\nindex a768797fcf..4168460ae9 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -100,6 +100,7 @@ void prepare_pager_args(struct child_process *pager_process, const char *pager)\n \targv_array_push(&pager_process->args, pager);\n \tpager_process->use_shell = 1;\n \tsetup_pager_env(&pager_process->env_array);\n+\tpager_process->trace2_child_class = \"pager\";\n }\n \n void setup_pager(void)\n-- \ngitgitgadget\n\n"},{"id":"368360","messageId":"pull.108.v5.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v4.git.gitgitgadget@gmail.com","subject":"[PATCH v5 00/15] Trace2 tracing facility","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-01T17:58:58Z","receivedAt":"2019-02-01T17:59:29Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"V5 addresses: [] renames \"verb\" and \"subverb\" to \"cmd_name\" and \"cmd_mode\"\nin code and documentation. [] updates clang-format config to not complain\nabout my for_each macros. [] update formatting around each use of my\nfor_each macros. [] update the platform-specific process info commit to\nindicate it only handles Windows and leaves other platform for future\nefforts. [] alters title of pack-objects commit to include \"trace2:data:\" to\nmatch the other data-only commits.\n\nI think this version addresses all of the feedback received do date.\n\nThanks Jeff\n\n\n----------------------------------------------------------------------------\n\nSorry to spam the list, but here is V4. After building V3 on 3 platforms\nwithout error and submitting, the compilers on platforms 4 and 5 complained\nabout a variable declaration. (sigh) [] fix declaration after first\nstatement [] add -DNO_UNIX_SOCKETS to BASIC_CFLAGS when NO_UNIX_SOCKETS is\ndefined in the Makefile.\n\n\n----------------------------------------------------------------------------\n\nV3 addresses: [] re-fix the trace2 tests using an inline environment\nvariable rather than exporting and unsetting. [] overhaul the design\ndocument to include prototype declarations and more file format information.\n[] incorporate most of the suggestions from clang-format. [] add ability to\ntrace to a unix domain socket. [] added forward declarations suggested by\nRamsay. [] rebased onto current master to fixup conflict with\nsb/submodule-recursive-fetch-gets-the-tip that was merged yesterday.\n\n\n----------------------------------------------------------------------------\n\nV2 addresses: [] \"jh/trace2\" bad interaction with \"js/vsts-ci\" in \"pu\". []\ncoccinelle warnings in trace2/tr2_tgt_perf.c reported during CI testing.\n\n\n----------------------------------------------------------------------------\n\nThis patch series contains a greatly refactored version of my original\nTrace2 series [1] from August 2018.\n\nA new design doc in Documentation/technical/api-trace2.txt (in the first\ncommit) explains the relationship of Trace2 to the current tracing facility.\nCalls to the current tracing facility have not been changed, rather new\ntrace2 calls have been added so that both continue to work in parallel for\nthe time being.\n\n[1] https://public-inbox.org/git/pull.29.git.gitgitgadget@gmail.com/\n\nCc: gitster@pobox.comCc: peff@peff.netCc: jrnieder@gmail.com\n\nDerrick Stolee (1):\n  trace2:data: pack-objects: add trace2 regions\n\nJeff Hostetler (14):\n  trace2: Documentation/technical/api-trace2.txt\n  trace2: create new combined trace facility\n  trace2: collect Windows-specific process information\n  trace2:data: add trace2 regions to wt-status\n  trace2:data: add editor/pager child classification\n  trace2:data: add trace2 sub-process classification\n  trace2:data: add trace2 transport child classification\n  trace2:data: add trace2 hook classification\n  trace2:data: add trace2 instrumentation to index read/write\n  trace2:data: add subverb to checkout command\n  trace2:data: add subverb to reset command\n  trace2:data: add subverb for rebase\n  trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh\n  trace2: add for_each macros to clang-format\n\n .clang-format                            |    2 +-\n Documentation/technical/api-trace2.txt   | 1347 ++++++++++++++++++++++\n Makefile                                 |   15 +-\n builtin/am.c                             |    1 +\n builtin/checkout.c                       |    7 +\n builtin/pack-objects.c                   |   16 +-\n builtin/rebase.c                         |   17 +\n builtin/receive-pack.c                   |    4 +\n builtin/reset.c                          |    6 +\n builtin/submodule--helper.c              |    9 +-\n builtin/worktree.c                       |    1 +\n cache.h                                  |    1 +\n common-main.c                            |   13 +-\n compat/mingw.c                           |   11 +-\n compat/mingw.h                           |    3 +-\n compat/win32/trace2_win32_process_info.c |  101 ++\n config.c                                 |    2 +\n config.mak.uname                         |    2 +\n connect.c                                |    3 +\n editor.c                                 |    1 +\n exec-cmd.c                               |    2 +\n git-compat-util.h                        |    7 +\n git.c                                    |   65 ++\n pager.c                                  |    1 +\n read-cache.c                             |   51 +-\n remote-curl.c                            |    7 +\n repository.c                             |    2 +\n repository.h                             |    3 +\n run-command.c                            |   59 +-\n run-command.h                            |   13 +-\n sequencer.c                              |    2 +\n sh-i18n--envsubst.c                      |    3 +\n sub-process.c                            |    1 +\n submodule.c                              |   11 +-\n t/helper/test-parse-options.c            |    3 +\n t/helper/test-tool.c                     |    4 +\n t/helper/test-tool.h                     |    1 +\n t/helper/test-trace2.c                   |  273 +++++\n t/t0001-init.sh                          |    1 +\n t/t0210-trace2-normal.sh                 |  135 +++\n t/t0210/scrub_normal.perl                |   48 +\n t/t0211-trace2-perf.sh                   |  153 +++\n t/t0211/scrub_perf.perl                  |   76 ++\n t/t0212-trace2-event.sh                  |  234 ++++\n t/t0212/parse_events.perl                |  251 ++++\n trace2.c                                 |  762 ++++++++++++\n trace2.h                                 |  385 +++++++\n trace2/tr2_cfg.c                         |   90 ++\n trace2/tr2_cfg.h                         |   19 +\n trace2/tr2_cmd_name.c                    |   30 +\n trace2/tr2_cmd_name.h                    |   24 +\n trace2/tr2_dst.c                         |  197 ++++\n trace2/tr2_dst.h                         |   36 +\n trace2/tr2_sid.c                         |   67 ++\n trace2/tr2_sid.h                         |   18 +\n trace2/tr2_tbuf.c                        |   32 +\n trace2/tr2_tbuf.h                        |   23 +\n trace2/tr2_tgt.h                         |  133 +++\n trace2/tr2_tgt_event.c                   |  589 ++++++++++\n trace2/tr2_tgt_normal.c                  |  324 ++++++\n trace2/tr2_tgt_perf.c                    |  535 +++++++++\n trace2/tr2_tls.c                         |  164 +++\n trace2/tr2_tls.h                         |   97 ++\n transport-helper.c                       |    2 +\n transport.c                              |    1 +\n usage.c                                  |   31 +\n wt-status.c                              |   24 +-\n 67 files changed, 6528 insertions(+), 23 deletions(-)\n create mode 100644 Documentation/technical/api-trace2.txt\n create mode 100644 compat/win32/trace2_win32_process_info.c\n create mode 100644 t/helper/test-trace2.c\n create mode 100755 t/t0210-trace2-normal.sh\n create mode 100644 t/t0210/scrub_normal.perl\n create mode 100755 t/t0211-trace2-perf.sh\n create mode 100644 t/t0211/scrub_perf.perl\n create mode 100755 t/t0212-trace2-event.sh\n create mode 100644 t/t0212/parse_events.perl\n create mode 100644 trace2.c\n create mode 100644 trace2.h\n create mode 100644 trace2/tr2_cfg.c\n create mode 100644 trace2/tr2_cfg.h\n create mode 100644 trace2/tr2_cmd_name.c\n create mode 100644 trace2/tr2_cmd_name.h\n create mode 100644 trace2/tr2_dst.c\n create mode 100644 trace2/tr2_dst.h\n create mode 100644 trace2/tr2_sid.c\n create mode 100644 trace2/tr2_sid.h\n create mode 100644 trace2/tr2_tbuf.c\n create mode 100644 trace2/tr2_tbuf.h\n create mode 100644 trace2/tr2_tgt.h\n create mode 100644 trace2/tr2_tgt_event.c\n create mode 100644 trace2/tr2_tgt_normal.c\n create mode 100644 trace2/tr2_tgt_perf.c\n create mode 100644 trace2/tr2_tls.c\n create mode 100644 trace2/tr2_tls.h\n\n\nbase-commit: b5101f929789889c2e536d915698f58d5c5c6b7a\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-108%2Fjeffhostetler%2Fcore-trace2-2019-v0-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-108/jeffhostetler/core-trace2-2019-v0-v5\nPull-Request: https://github.com/gitgitgadget/git/pull/108\n\nRange-diff vs v4:\n\n  1:  60b56d6a8f !  1:  4c006f4995 trace2: Documentation/technical/api-trace2.txt\n     @@ -54,7 +54,7 @@\n      +$ cat ~/log.normal\n      +12:28:42.620009 common-main.c:38                  version 2.20.1.155.g426c96fcdb\n      +12:28:42.620989 common-main.c:39                  start git version\n     -+12:28:42.621101 git.c:432                         cmd_verb version (version)\n     ++12:28:42.621101 git.c:432                         cmd_name version (version)\n      +12:28:42.621215 git.c:662                         exit elapsed:0.001227 code:0\n      +12:28:42.621250 trace2/tr2_tgt_normal.c:124       atexit elapsed:0.001265 code:0\n      +------------\n     @@ -74,7 +74,7 @@\n      +$ cat ~/log.perf\n      +12:28:42.620675 common-main.c:38                  | d0 | main                     | version      |     |           |           |            | 2.20.1.155.g426c96fcdb\n      +12:28:42.621001 common-main.c:39                  | d0 | main                     | start        |     |           |           |            | git version\n     -+12:28:42.621111 git.c:432                         | d0 | main                     | cmd_verb     |     |           |           |            | version (version)\n     ++12:28:42.621111 git.c:432                         | d0 | main                     | cmd_name     |     |           |           |            | version (version)\n      +12:28:42.621225 git.c:662                         | d0 | main                     | exit         |     |  0.001227 |           |            | code:0\n      +12:28:42.621259 trace2/tr2_tgt_perf.c:211         | d0 | main                     | atexit       |     |  0.001265 |           |            | code:0\n      +------------\n     @@ -93,7 +93,7 @@\n      +$ cat ~/log.event\n      +{\"event\":\"version\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.620713\",\"file\":\"common-main.c\",\"line\":38,\"evt\":\"1\",\"exe\":\"2.20.1.155.g426c96fcdb\"}\n      +{\"event\":\"start\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621027\",\"file\":\"common-main.c\",\"line\":39,\"argv\":[\"git\",\"version\"]}\n     -+{\"event\":\"cmd_verb\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621122\",\"file\":\"git.c\",\"line\":432,\"name\":\"version\",\"hierarchy\":\"version\"}\n     ++{\"event\":\"cmd_name\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621122\",\"file\":\"git.c\",\"line\":432,\"name\":\"version\",\"hierarchy\":\"version\"}\n      +{\"event\":\"exit\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621236\",\"file\":\"git.c\",\"line\":662,\"t_abs\":0.001227,\"code\":0}\n      +{\"event\":\"atexit\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621268\",\"file\":\"trace2/tr2_tgt_event.c\",\"line\":163,\"t_abs\":0.001265,\"code\":0}\n      +------------\n     @@ -213,20 +213,20 @@\n      +These are concerned with describing the specific Git command\n      +after the command line, config, and environment are inspected.\n      +\n     -+`void trace2_cmd_verb(const char *command_verb)`::\n     ++`void trace2_cmd_name(const char *name)`::\n      +\n     -+\tEmits a \"cmd_verb\" message with the canonical name of\n     -+\t(usually) builtin command, for example \"status\" or \"checkout\".\n     ++\tEmits a \"cmd_name\" message with the canonical name of the\n     ++\tcommand, for example \"status\" or \"checkout\".\n      +\n     -+`void trace2_cmd_subverb(const char *command_subverb)`::\n     ++`void trace2_cmd_mode(const char *mode)`::\n      +\n     -+\tEmits a \"cmd_subverb\" message with a qualifier name to further\n     ++\tEmits a \"cmd_mode\" message with a qualifier name to further\n      +\tdescribe the current git command.\n      ++\n      +This message is intended to be used with git commands having multiple\n      +major modes.  For example, a \"checkout\" command can checkout a new\n     -+branch or can checkout a single file, so the checkout code could\n     -+emit a subverb message of \"branch\" or \"file\".\n     ++branch or it can checkout a single file, so the checkout code could\n     ++emit a cmd_mode message of \"branch\" or \"file\".\n      +\n      +`void trace2_cmd_alias(const char *alias, const char **argv_expansion)`::\n      +\n     @@ -682,13 +682,13 @@\n      +}\n      +------------\n      +\n     -+`\"cmd_verb\"`::\n     -+\tThis event contains the primary verb for this git process\n     -+\tand the hierarchy of verbs from parent git processes.\n     ++`\"cmd_name\"`::\n     ++\tThis event contains the command name for this git process\n     ++\tand the hierarchy of commands from parent git processes.\n      ++\n      +------------\n      +{\n     -+\t\"event\":\"cmd_verb\",\n     ++\t\"event\":\"cmd_name\",\n      +\t...\n      +\t\"name\":\"pack-objects\",\n      +\t\"hierarchy\":\"push/pack-objects\"\n     @@ -696,7 +696,7 @@\n      +------------\n      ++\n      +Normally, the \"name\" field contains the canonical name of the\n     -+command.  When a canonical verb is not available, one of\n     ++command.  When a canonical name is not available, one of\n      +these special values are used:\n      ++\n      +------------\n     @@ -707,22 +707,22 @@\n      +\"_usage_\"            # usage error\n      +------------\n      +\n     -+`\"cmd_subverb\"`::\n     -+\tThis event, when present, describes the variant of the main\n     -+\tverb.  This event may be emitted more than once.\n     ++`\"cmd_mode\"`::\n     ++\tThis event, when present, describes the command variant This\n     ++\tevent may be emitted more than once.\n      ++\n      +------------\n      +{\n     -+\t\"event\":\"cmd_subverb\",\n     ++\t\"event\":\"cmd_mode\",\n      +\t...\n      +\t\"name\":\"branch\"\n      +}\n      +------------\n      ++\n     -+The \"name\" field is an arbitrary string that makes sense in the\n     -+context of the primary verb. For example, checkout can checkout a\n     -+branch or an individual file.  And these variations typically have\n     -+different performance characteristics that are not comparable.\n     ++The \"name\" field is an arbitrary string to describe the command mode.\n     ++For example, checkout can checkout a branch or an individual file.\n     ++And these variations typically have different performance\n     ++characteristics that are not comparable.\n      +\n      +`\"alias\"`::\n      +\tThis event is present when an alias is expanded.\n     @@ -983,8 +983,8 @@\n      +----------------\n      +int cmd_checkout(int argc, const char **argv)\n      +{\n     -+\ttrace2_cmd_verb(\"checkout\");\n     -+\ttrace2_cmd_subverb(\"branch\");\n     ++\ttrace2_cmd_name(\"checkout\");\n     ++\ttrace2_cmd_mode(\"branch\");\n      +\ttrace2_def_repo(the_repository);\n      +\n      +\t// emit \"def_param\" messages for \"interesting\" config settings.\n     @@ -1031,7 +1031,7 @@\n      +version 2.20.1.vfs.1.1.47.g534dbe1ad1\n      +start git fetch origin\n      +worktree /Users/jeffhost/work/gfw\n     -+cmd_verb fetch (fetch)\n     ++cmd_name fetch (fetch)\n      +child_start[0] ssh git@github.com ...\n      +child_start[1] git index-pack ...\n      +... (Trace2 events from child processes omitted)\n     @@ -1049,9 +1049,9 @@\n      ++\n      +When a git process is a (direct or indirect) child of another\n      +git process, it inherits Trace2 context information.  This\n     -+allows the child to print a verb hierarchy.  This example\n     ++allows the child to print the command hierarchy.  This example\n      +shows gc as child[3] of fetch.  When the gc process reports\n     -+its verb as \"gc\", it also reports the hierarchy as \"fetch/gc\".\n     ++its name as \"gc\", it also reports the hierarchy as \"fetch/gc\".\n      +(In this example, trace2 messages from the child process is\n      +indented for clarity.)\n      ++\n     @@ -1067,13 +1067,13 @@\n      +version 2.20.1.160.g5676107ecd.dirty\n      +start git fetch official\n      +worktree /Users/jeffhost/work/gfw\n     -+cmd_verb fetch (fetch)\n     ++cmd_name fetch (fetch)\n      +...\n      +child_start[3] git gc --auto\n      +    version 2.20.1.160.g5676107ecd.dirty\n      +    start /Users/jeffhost/work/gfw/git gc --auto\n      +    worktree /Users/jeffhost/work/gfw\n     -+    cmd_verb gc (fetch/gc)\n     ++    cmd_name gc (fetch/gc)\n      +    exit elapsed:0.001959 code:0\n      +    atexit elapsed:0.001997 code:0\n      +child_exit[3] pid:20303 code:0 elapsed:0.007564\n     @@ -1124,7 +1124,7 @@\n      +d0 | main                     | version      |     |           |           |            | 2.20.1.160.g5676107ecd.dirty\n      +d0 | main                     | start        |     |           |           |            | git status\n      +d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n     -+d0 | main                     | cmd_verb     |     |           |           |            | status (status)\n     ++d0 | main                     | cmd_name     |     |           |           |            | status (status)\n      +...\n      +d0 | main                     | region_enter | r1  |  0.010988 |           | status     | label:worktrees\n      +d0 | main                     | region_leave | r1  |  0.011236 |  0.000248 | status     | label:worktrees\n     @@ -1169,7 +1169,7 @@\n      +d0 | main                     | version      |     |           |           |            | 2.20.1.162.gb4ccea44db.dirty\n      +d0 | main                     | start        |     |           |           |            | git status\n      +d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n     -+d0 | main                     | cmd_verb     |     |           |           |            | status (status)\n     ++d0 | main                     | cmd_name     |     |           |           |            | status (status)\n      +...\n      +d0 | main                     | region_enter | r1  |  0.015047 |           | status     | label:untracked\n      +d0 | main                     | region_enter |     |  0.015132 |           | dir        | ..label:read_recursive\n     @@ -1225,7 +1225,7 @@\n      +d0 | main                     | version      |     |           |           |            | 2.20.1.156.gf9916ae094.dirty\n      +d0 | main                     | start        |     |           |           |            | git status\n      +d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n     -+d0 | main                     | cmd_verb     |     |           |           |            | status (status)\n     ++d0 | main                     | cmd_name     |     |           |           |            | status (status)\n      +d0 | main                     | region_enter | r1  |  0.001791 |           | index      | label:do_read_index .git/index\n      +d0 | main                     | data         | r1  |  0.002494 |  0.000703 | index      | ..read/version:2\n      +d0 | main                     | data         | r1  |  0.002520 |  0.000729 | index      | ..read/cache_nr:3552\n  2:  8c9687830e !  2:  6120ce1bbe trace2: create new combined trace facility\n     @@ -41,6 +41,7 @@\n       LIB_OBJS += trace.o\n      +LIB_OBJS += trace2.o\n      +LIB_OBJS += trace2/tr2_cfg.o\n     ++LIB_OBJS += trace2/tr2_cmd_name.o\n      +LIB_OBJS += trace2/tr2_dst.o\n      +LIB_OBJS += trace2/tr2_sid.o\n      +LIB_OBJS += trace2/tr2_tbuf.o\n     @@ -48,7 +49,6 @@\n      +LIB_OBJS += trace2/tr2_tgt_normal.o\n      +LIB_OBJS += trace2/tr2_tgt_perf.o\n      +LIB_OBJS += trace2/tr2_tls.o\n     -+LIB_OBJS += trace2/tr2_verb.o\n       LIB_OBJS += trailer.o\n       LIB_OBJS += transport.o\n       LIB_OBJS += transport-helper.o\n     @@ -243,20 +243,20 @@\n       \t\t\t\tgit_set_exec_path(cmd + 1);\n       \t\t\telse {\n       \t\t\t\tputs(git_exec_path());\n     -+\t\t\t\ttrace2_cmd_verb(\"_query_\");\n     ++\t\t\t\ttrace2_cmd_name(\"_query_\");\n       \t\t\t\texit(0);\n       \t\t\t}\n       \t\t} else if (!strcmp(cmd, \"--html-path\")) {\n       \t\t\tputs(system_path(GIT_HTML_PATH));\n     -+\t\t\ttrace2_cmd_verb(\"_query_\");\n     ++\t\t\ttrace2_cmd_name(\"_query_\");\n       \t\t\texit(0);\n       \t\t} else if (!strcmp(cmd, \"--man-path\")) {\n       \t\t\tputs(system_path(GIT_MAN_PATH));\n     -+\t\t\ttrace2_cmd_verb(\"_query_\");\n     ++\t\t\ttrace2_cmd_name(\"_query_\");\n       \t\t\texit(0);\n       \t\t} else if (!strcmp(cmd, \"--info-path\")) {\n       \t\t\tputs(system_path(GIT_INFO_PATH));\n     -+\t\t\ttrace2_cmd_verb(\"_query_\");\n     ++\t\t\ttrace2_cmd_name(\"_query_\");\n       \t\t\texit(0);\n       \t\t} else if (!strcmp(cmd, \"-p\") || !strcmp(cmd, \"--paginate\")) {\n       \t\t\tuse_pager = 1;\n     @@ -264,7 +264,7 @@\n       \t\t\t(*argv)++;\n       \t\t\t(*argc)--;\n       \t\t} else if (skip_prefix(cmd, \"--list-cmds=\", &cmd)) {\n     -+\t\t\ttrace2_cmd_verb(\"_query_\");\n     ++\t\t\ttrace2_cmd_name(\"_query_\");\n       \t\t\tif (!strcmp(cmd, \"parseopt\")) {\n       \t\t\t\tstruct string_list list = STRING_LIST_INIT_DUP;\n       \t\t\t\tint i;\n     @@ -278,7 +278,7 @@\n       \n      +\t\t\ttrace2_cmd_alias(alias_command, child.args.argv);\n      +\t\t\ttrace2_cmd_list_config();\n     -+\t\t\ttrace2_cmd_verb(\"_run_shell_alias_\");\n     ++\t\t\ttrace2_cmd_name(\"_run_shell_alias_\");\n      +\n       \t\t\tret = run_command(&child);\n       \t\t\tif (ret >= 0)   /* normal exit */\n     @@ -297,7 +297,7 @@\n       \t\tsetup_work_tree();\n       \n       \ttrace_argv_printf(argv, \"trace: built-in: git\");\n     -+\ttrace2_cmd_verb(p->cmd);\n     ++\ttrace2_cmd_name(p->cmd);\n      +\ttrace2_cmd_list_config();\n       \n       \tvalidate_cache_entries(&the_index);\n     @@ -308,7 +308,7 @@\n       \tcmd.silent_exec_failure = 1;\n      +\tcmd.trace2_child_class = \"dashed\";\n       \n     -+\ttrace2_cmd_verb(\"_run_dashed_\");\n     ++\ttrace2_cmd_name(\"_run_dashed_\");\n      +\n      +\t/*\n      +\t * The code in run_command() logs trace2 child_start/child_exit\n     @@ -346,7 +346,7 @@\n      +\t\t\t * command verb to indicate this.  Note that the child\n      +\t\t\t * process will log the actual verb when it runs.\n      +\t\t\t */\n     -+\t\t\ttrace2_cmd_verb(\"_run_git_alias_\");\n     ++\t\t\ttrace2_cmd_name(\"_run_git_alias_\");\n      +\n      +\t\t\tif (get_super_prefix())\n      +\t\t\t\tdie(\"%s doesn't support --super-prefix\", **argv);\n     @@ -387,7 +387,7 @@\n      +\t * (\"git-remote-http\", \"git-remote-https\", and etc.) here since they\n      +\t * are all just copies of the same actual executable.\n      +\t */\n     -+\ttrace2_cmd_verb(\"remote-curl\");\n     ++\ttrace2_cmd_name(\"remote-curl\");\n      +\n       \tremote = remote_get(argv[1]);\n       \n     @@ -603,7 +603,7 @@\n         /* Default values for command line options.  */\n         /* unsigned short int show_variables = 0; */\n       \n     -+  trace2_cmd_verb(\"sh-i18n--envsubst\");\n     ++  trace2_cmd_name(\"sh-i18n--envsubst\");\n      +\n         switch (argc)\n       \t{\n     @@ -646,7 +646,7 @@\n       \tint i;\n       \tint ret = 0;\n       \n     -+\ttrace2_cmd_verb(\"_parse_\");\n     ++\ttrace2_cmd_name(\"_parse_\");\n      +\n       \targc = parse_options(argc, (const char **)argv, prefix, options, usage, 0);\n       \n     @@ -666,7 +666,7 @@\n       \t\tif (!strcmp(cmds[i].name, argv[1])) {\n       \t\t\targv++;\n       \t\t\targc--;\n     -+\t\t\ttrace2_cmd_verb(cmds[i].name);\n     ++\t\t\ttrace2_cmd_name(cmds[i].name);\n      +\t\t\ttrace2_cmd_list_config();\n       \t\t\treturn cmds[i].fn(argc, argv);\n       \t\t}\n     @@ -698,11 +698,11 @@\n      +#include \"thread-utils.h\"\n      +#include \"version.h\"\n      +#include \"trace2/tr2_cfg.h\"\n     ++#include \"trace2/tr2_cmd_name.h\"\n      +#include \"trace2/tr2_dst.h\"\n      +#include \"trace2/tr2_sid.h\"\n      +#include \"trace2/tr2_tgt.h\"\n      +#include \"trace2/tr2_tls.h\"\n     -+#include \"trace2/tr2_verb.h\"\n      +\n      +static int trace2_enabled;\n      +\n     @@ -715,6 +715,7 @@\n      + * enabled or disabled.  Each TRACE2 API method will try to write an event to\n      + * *each* of the enabled targets.\n      + */\n     ++/* clang-format off */\n      +static struct tr2_tgt *tr2_tgt_builtins[] =\n      +{\n      +\t&tr2_tgt_normal,\n     @@ -722,14 +723,20 @@\n      +\t&tr2_tgt_event,\n      +\tNULL\n      +};\n     ++/* clang-format on */\n      +\n     ++/* clang-format off */\n      +#define for_each_builtin(j, tgt_j)\t\t\t\\\n      +\tfor (j = 0, tgt_j = tr2_tgt_builtins[j];\t\\\n      +\t     tgt_j;\t\t\t\t\t\\\n      +\t     j++, tgt_j = tr2_tgt_builtins[j])\n     ++/* clang-format on */\n      +\n     -+#define for_each_wanted_builtin(j, tgt_j) \\\n     -+\tfor_each_builtin(j, tgt_j) if (tr2_dst_trace_want(tgt_j->pdst))\n     ++/* clang-format off */\n     ++#define for_each_wanted_builtin(j, tgt_j)            \\\n     ++\tfor_each_builtin(j, tgt_j)                   \\\n     ++\t\tif (tr2_dst_trace_want(tgt_j->pdst))\n     ++/* clang-format on */\n      +\n      +/*\n      + * Force (rather than lazily) initialize any of the requested\n     @@ -745,11 +752,9 @@\n      +\tint j;\n      +\tint sum = 0;\n      +\n     -+\tfor_each_builtin(j, tgt_j)\n     -+\t{\n     ++\tfor_each_builtin (j, tgt_j)\n      +\t\tif (tgt_j->pfn_init())\n      +\t\t\tsum++;\n     -+\t}\n      +\n      +\treturn sum;\n      +}\n     @@ -764,10 +769,8 @@\n      +\tstruct tr2_tgt *tgt_j;\n      +\tint j;\n      +\n     -+\tfor_each_builtin(j, tgt_j)\n     -+\t{\n     ++\tfor_each_builtin (j, tgt_j)\n      +\t\ttgt_j->pfn_term();\n     -+\t}\n      +}\n      +\n      +static int tr2main_exit_code;\n     @@ -797,18 +800,16 @@\n      +\t */\n      +\ttr2tls_pop_unwind_self();\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j)\n     -+\t{\n     ++\tfor_each_wanted_builtin (j, tgt_j)\n      +\t\tif (tgt_j->pfn_atexit)\n      +\t\t\ttgt_j->pfn_atexit(us_elapsed_absolute,\n      +\t\t\t\t\t  tr2main_exit_code);\n     -+\t}\n      +\n      +\ttr2_tgt_disable_builtins();\n      +\n      +\ttr2tls_release();\n      +\ttr2_sid_release();\n     -+\ttr2_verb_release();\n     ++\ttr2_cmd_name_release();\n      +\ttr2_cfg_free_patterns();\n      +\n      +\ttrace2_enabled = 0;\n     @@ -824,11 +825,9 @@\n      +\tus_now = getnanotime() / 1000;\n      +\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j)\n     -+\t{\n     ++\tfor_each_wanted_builtin (j, tgt_j)\n      +\t\tif (tgt_j->pfn_signal)\n      +\t\t\ttgt_j->pfn_signal(us_elapsed_absolute, signo);\n     -+\t}\n      +\n      +\tsigchain_pop(signo);\n      +\traise(signo);\n     @@ -855,11 +854,9 @@\n      +\t/*\n      +\t * Emit 'version' message on each active builtin target.\n      +\t */\n     -+\tfor_each_wanted_builtin(j, tgt_j)\n     -+\t{\n     ++\tfor_each_wanted_builtin (j, tgt_j)\n      +\t\tif (tgt_j->pfn_version_fl)\n      +\t\t\ttgt_j->pfn_version_fl(file, line);\n     -+\t}\n      +}\n      +\n      +int trace2_is_enabled(void)\n     @@ -875,11 +872,9 @@\n      +\tif (!trace2_enabled)\n      +\t\treturn;\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j)\n     -+\t{\n     ++\tfor_each_wanted_builtin (j, tgt_j)\n      +\t\tif (tgt_j->pfn_start_fl)\n      +\t\t\ttgt_j->pfn_start_fl(file, line, argv);\n     -+\t}\n      +}\n      +\n      +int trace2_cmd_exit_fl(const char *file, int line, int code)\n     @@ -899,12 +894,10 @@\n      +\tus_now = getnanotime() / 1000;\n      +\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j)\n     -+\t{\n     ++\tfor_each_wanted_builtin (j, tgt_j)\n      +\t\tif (tgt_j->pfn_exit_fl)\n      +\t\t\ttgt_j->pfn_exit_fl(file, line, us_elapsed_absolute,\n      +\t\t\t\t\t   code);\n     -+\t}\n      +\n      +\treturn code;\n      +}\n     @@ -922,11 +915,9 @@\n      +\t * We expect each target function to treat 'ap' as constant\n      +\t * and use va_copy (because an 'ap' can only be walked once).\n      +\t */\n     -+\tfor_each_wanted_builtin(j, tgt_j)\n     -+\t{\n     ++\tfor_each_wanted_builtin (j, tgt_j)\n      +\t\tif (tgt_j->pfn_error_va_fl)\n      +\t\t\ttgt_j->pfn_error_va_fl(file, line, fmt, ap);\n     -+\t}\n      +}\n      +\n      +void trace2_cmd_path_fl(const char *file, int line, const char *pathname)\n     @@ -937,14 +928,12 @@\n      +\tif (!trace2_enabled)\n      +\t\treturn;\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j)\n     -+\t{\n     ++\tfor_each_wanted_builtin (j, tgt_j)\n      +\t\tif (tgt_j->pfn_command_path_fl)\n      +\t\t\ttgt_j->pfn_command_path_fl(file, line, pathname);\n     -+\t}\n      +}\n      +\n     -+void trace2_cmd_verb_fl(const char *file, int line, const char *command_verb)\n     ++void trace2_cmd_name_fl(const char *file, int line, const char *name)\n      +{\n      +\tstruct tr2_tgt *tgt_j;\n      +\tconst char *hierarchy;\n     @@ -953,19 +942,16 @@\n      +\tif (!trace2_enabled)\n      +\t\treturn;\n      +\n     -+\ttr2_verb_append_hierarchy(command_verb);\n     -+\thierarchy = tr2_verb_get_hierarchy();\n     ++\ttr2_cmd_name_append_hierarchy(name);\n     ++\thierarchy = tr2_cmd_name_get_hierarchy();\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j)\n     -+\t{\n     -+\t\tif (tgt_j->pfn_command_verb_fl)\n     -+\t\t\ttgt_j->pfn_command_verb_fl(file, line, command_verb,\n     ++\tfor_each_wanted_builtin (j, tgt_j)\n     ++\t\tif (tgt_j->pfn_command_name_fl)\n     ++\t\t\ttgt_j->pfn_command_name_fl(file, line, name,\n      +\t\t\t\t\t\t   hierarchy);\n     -+\t}\n      +}\n      +\n     -+void trace2_cmd_subverb_fl(const char *file, int line,\n     -+\t\t\t   const char *command_subverb)\n     ++void trace2_cmd_mode_fl(const char *file, int line, const char *mode)\n      +{\n      +\tstruct tr2_tgt *tgt_j;\n      +\tint j;\n     @@ -973,12 +959,9 @@\n      +\tif (!trace2_enabled)\n      +\t\treturn;\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j)\n     -+\t{\n     -+\t\tif (tgt_j->pfn_command_subverb_fl)\n     -+\t\t\ttgt_j->pfn_command_subverb_fl(file, line,\n     -+\t\t\t\t\t\t      command_subverb);\n     -+\t}\n     ++\tfor_each_wanted_builtin (j, tgt_j)\n     ++\t\tif (tgt_j->pfn_command_mode_fl)\n     ++\t\t\ttgt_j->pfn_command_mode_fl(file, line, mode);\n      +}\n      +\n      +void trace2_cmd_alias_fl(const char *file, int line, const char *alias,\n     @@ -990,11 +973,9 @@\n      +\tif (!trace2_enabled)\n      +\t\treturn;\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j)\n     -+\t{\n     ++\tfor_each_wanted_builtin (j, tgt_j)\n      +\t\tif (tgt_j->pfn_alias_fl)\n      +\t\t\ttgt_j->pfn_alias_fl(file, line, alias, argv);\n     -+\t}\n      +}\n      +\n      +void trace2_cmd_list_config_fl(const char *file, int line)\n     @@ -1031,12 +1012,10 @@\n      +\tcmd->trace2_child_id = tr2tls_locked_increment(&tr2_next_child_id);\n      +\tcmd->trace2_child_us_start = us_now;\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j)\n     -+\t{\n     ++\tfor_each_wanted_builtin (j, tgt_j)\n      +\t\tif (tgt_j->pfn_child_start_fl)\n      +\t\t\ttgt_j->pfn_child_start_fl(file, line,\n      +\t\t\t\t\t\t  us_elapsed_absolute, cmd);\n     -+\t}\n      +}\n      +\n      +void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd,\n     @@ -1059,15 +1038,13 @@\n      +\telse\n      +\t\tus_elapsed_child = 0;\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j)\n     -+\t{\n     ++\tfor_each_wanted_builtin (j, tgt_j)\n      +\t\tif (tgt_j->pfn_child_exit_fl)\n      +\t\t\ttgt_j->pfn_child_exit_fl(file, line,\n      +\t\t\t\t\t\t us_elapsed_absolute,\n      +\t\t\t\t\t\t cmd->trace2_child_id, cmd->pid,\n      +\t\t\t\t\t\t child_exit_code,\n      +\t\t\t\t\t\t us_elapsed_child);\n     -+\t}\n      +}\n      +\n      +int trace2_exec_fl(const char *file, int line, const char *exe,\n     @@ -1087,12 +1064,10 @@\n      +\n      +\texec_id = tr2tls_locked_increment(&tr2_next_exec_id);\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j)\n     -+\t{\n     ++\tfor_each_wanted_builtin (j, tgt_j)\n      +\t\tif (tgt_j->pfn_exec_fl)\n      +\t\t\ttgt_j->pfn_exec_fl(file, line, us_elapsed_absolute,\n      +\t\t\t\t\t   exec_id, exe, argv);\n     -+\t}\n      +\n      +\treturn exec_id;\n      +}\n     @@ -1110,12 +1085,10 @@\n      +\tus_now = getnanotime() / 1000;\n      +\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j)\n     -+\t{\n     ++\tfor_each_wanted_builtin (j, tgt_j)\n      +\t\tif (tgt_j->pfn_exec_result_fl)\n      +\t\t\ttgt_j->pfn_exec_result_fl(\n      +\t\t\t\tfile, line, us_elapsed_absolute, exec_id, code);\n     -+\t}\n      +}\n      +\n      +void trace2_thread_start_fl(const char *file, int line, const char *thread_name)\n     @@ -1149,12 +1122,10 @@\n      +\n      +\ttr2tls_create_self(thread_name);\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j)\n     -+\t{\n     ++\tfor_each_wanted_builtin (j, tgt_j)\n      +\t\tif (tgt_j->pfn_thread_start_fl)\n      +\t\t\ttgt_j->pfn_thread_start_fl(file, line,\n      +\t\t\t\t\t\t   us_elapsed_absolute);\n     -+\t}\n      +}\n      +\n      +void trace2_thread_exit_fl(const char *file, int line)\n     @@ -1195,13 +1166,11 @@\n      +\ttr2tls_pop_unwind_self();\n      +\tus_elapsed_thread = tr2tls_region_elasped_self(us_now);\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j)\n     -+\t{\n     ++\tfor_each_wanted_builtin (j, tgt_j)\n      +\t\tif (tgt_j->pfn_thread_exit_fl)\n      +\t\t\ttgt_j->pfn_thread_exit_fl(file, line,\n      +\t\t\t\t\t\t  us_elapsed_absolute,\n      +\t\t\t\t\t\t  us_elapsed_thread);\n     -+\t}\n      +\n      +\ttr2tls_unset_self();\n      +}\n     @@ -1215,11 +1184,9 @@\n      +\tif (!trace2_enabled)\n      +\t\treturn;\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j)\n     -+\t{\n     ++\tfor_each_wanted_builtin (j, tgt_j)\n      +\t\tif (tgt_j->pfn_param_fl)\n      +\t\t\ttgt_j->pfn_param_fl(file, line, param, value);\n     -+\t}\n      +}\n      +\n      +void trace2_def_repo_fl(const char *file, int line, struct repository *repo)\n     @@ -1235,11 +1202,9 @@\n      +\n      +\trepo->trace2_repo_id = tr2tls_locked_increment(&tr2_next_repo_id);\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j)\n     -+\t{\n     ++\tfor_each_wanted_builtin (j, tgt_j)\n      +\t\tif (tgt_j->pfn_repo_fl)\n      +\t\t\ttgt_j->pfn_repo_fl(file, line, repo);\n     -+\t}\n      +}\n      +\n      +void trace2_region_enter_printf_va_fl(const char *file, int line,\n     @@ -1265,13 +1230,11 @@\n      +\t * We expect each target function to treat 'ap' as constant\n      +\t * and use va_copy.\n      +\t */\n     -+\tfor_each_wanted_builtin(j, tgt_j)\n     -+\t{\n     ++\tfor_each_wanted_builtin (j, tgt_j)\n      +\t\tif (tgt_j->pfn_region_enter_printf_va_fl)\n      +\t\t\ttgt_j->pfn_region_enter_printf_va_fl(\n      +\t\t\t\tfile, line, us_elapsed_absolute, category,\n      +\t\t\t\tlabel, repo, fmt, ap);\n     -+\t}\n      +\n      +\ttr2tls_push_self(us_now);\n      +}\n     @@ -1341,14 +1304,12 @@\n      +\t * We expect each target function to treat 'ap' as constant\n      +\t * and use va_copy.\n      +\t */\n     -+\tfor_each_wanted_builtin(j, tgt_j)\n     -+\t{\n     ++\tfor_each_wanted_builtin (j, tgt_j)\n      +\t\tif (tgt_j->pfn_region_leave_printf_va_fl)\n      +\t\t\ttgt_j->pfn_region_leave_printf_va_fl(\n      +\t\t\t\tfile, line, us_elapsed_absolute,\n      +\t\t\t\tus_elapsed_region, category, label, repo, fmt,\n      +\t\t\t\tap);\n     -+\t}\n      +}\n      +\n      +void trace2_region_leave_fl(const char *file, int line, const char *category,\n     @@ -1402,13 +1363,11 @@\n      +\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n      +\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j)\n     -+\t{\n     ++\tfor_each_wanted_builtin (j, tgt_j)\n      +\t\tif (tgt_j->pfn_data_fl)\n      +\t\t\ttgt_j->pfn_data_fl(file, line, us_elapsed_absolute,\n      +\t\t\t\t\t   us_elapsed_region, category, repo,\n      +\t\t\t\t\t   key, value);\n     -+\t}\n      +}\n      +\n      +void trace2_data_intmax_fl(const char *file, int line, const char *category,\n     @@ -1442,13 +1401,11 @@\n      +\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n      +\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n      +\n     -+\tfor_each_wanted_builtin(j, tgt_j)\n     -+\t{\n     ++\tfor_each_wanted_builtin (j, tgt_j)\n      +\t\tif (tgt_j->pfn_data_fl)\n      +\t\t\ttgt_j->pfn_data_json_fl(file, line, us_elapsed_absolute,\n      +\t\t\t\t\t\tus_elapsed_region, category,\n      +\t\t\t\t\t\trepo, key, value);\n     -+\t}\n      +}\n      +\n      +void trace2_printf_va_fl(const char *file, int line, const char *fmt,\n     @@ -1469,12 +1426,10 @@\n      +\t * We expect each target function to treat 'ap' as constant\n      +\t * and use va_copy.\n      +\t */\n     -+\tfor_each_wanted_builtin(j, tgt_j)\n     -+\t{\n     ++\tfor_each_wanted_builtin (j, tgt_j)\n      +\t\tif (tgt_j->pfn_printf_va_fl)\n      +\t\t\ttgt_j->pfn_printf_va_fl(file, line, us_elapsed_absolute,\n      +\t\t\t\t\t\tfmt, ap);\n     -+\t}\n      +}\n      +\n      +void trace2_printf_fl(const char *file, int line, const char *fmt, ...)\n     @@ -1581,24 +1536,23 @@\n      +#define trace2_cmd_path(p) trace2_cmd_path_fl(__FILE__, __LINE__, (p))\n      +\n      +/*\n     -+ * Emit a 'cmd_verb' event with the canonical name of the (usually)\n     -+ * builtin command.  This gives post-processors a simple field\n     -+ * to identify the command verb without having to parse the argv.\n     ++ * Emit a 'cmd_name' event with the canonical name of the command.\n     ++ * This gives post-processors a simple field to identify the command\n     ++ * without having to parse the argv.\n      + */\n     -+void trace2_cmd_verb_fl(const char *file, int line, const char *command_verb);\n     ++void trace2_cmd_name_fl(const char *file, int line, const char *name);\n      +\n     -+#define trace2_cmd_verb(v) trace2_cmd_verb_fl(__FILE__, __LINE__, (v))\n     ++#define trace2_cmd_name(v) trace2_cmd_name_fl(__FILE__, __LINE__, (v))\n      +\n      +/*\n     -+ * Emit a 'cmd_subverb' event to further describe the command being run.\n     ++ * Emit a 'cmd_mode' event to further describe the command being run.\n      + * For example, \"checkout\" can checkout a single file or can checkout a\n      + * different branch.  This gives post-processors a simple field to compare\n      + * equivalent commands without having to parse the argv.\n      + */\n     -+void trace2_cmd_subverb_fl(const char *file, int line,\n     -+\t\t\t   const char *command_subverb);\n     ++void trace2_cmd_mode_fl(const char *file, int line, const char *mode);\n      +\n     -+#define trace2_cmd_subverb(sv) trace2_cmd_subverb_fl(__FILE__, __LINE__, (sv))\n     ++#define trace2_cmd_mode(sv) trace2_cmd_mode_fl(__FILE__, __LINE__, (sv))\n      +\n      +/*\n      + * Emit an 'alias' expansion event.\n     @@ -1767,10 +1721,12 @@\n      +\ttrace2_region_enter_printf_fl(__FILE__, __LINE__, (category), (label), \\\n      +\t\t\t\t      (repo), __VA_ARGS__)\n      +#else\n     ++/* clang-format off */\n      +__attribute__((format (region_enter_printf, 4, 5)))\n      +void trace2_region_enter_printf(const char *category, const char *label,\n      +\t\t\t\tconst struct repository *repo, const char *fmt,\n      +\t\t\t\t...);\n     ++/* clang-format on */\n      +#endif\n      +\n      +/*\n     @@ -1805,10 +1761,12 @@\n      +\ttrace2_region_leave_printf_fl(__FILE__, __LINE__, (category), (label), \\\n      +\t\t\t\t      (repo), __VA_ARGS__)\n      +#else\n     ++/* clang-format off */\n      +__attribute__((format (region_leave_printf, 4, 5)))\n      +void trace2_region_leave_printf(const char *category, const char *label,\n      +\t\t\t\tconst struct repository *repo, const char *fmt,\n      +\t\t\t\t...);\n     ++/* clang-format on */\n      +#endif\n      +\n      +/*\n     @@ -1863,8 +1821,10 @@\n      +#ifdef HAVE_VARIADIC_MACROS\n      +#define trace2_printf(...) trace2_printf_fl(__FILE__, __LINE__, __VA_ARGS__)\n      +#else\n     ++/* clang-format off */\n      +__attribute__((format (printf, 1, 2)))\n      +void trace2_printf(const char *fmt, ...);\n     ++/* clang-format on */\n      +#endif\n      +\n      +#endif /* TRACE2_H */\n     @@ -1990,6 +1950,72 @@\n      +\n      +#endif /* TR2_CFG_H */\n      \n     + diff --git a/trace2/tr2_cmd_name.c b/trace2/tr2_cmd_name.c\n     + new file mode 100644\n     + --- /dev/null\n     + +++ b/trace2/tr2_cmd_name.c\n     +@@\n     ++#include \"cache.h\"\n     ++#include \"trace2/tr2_cmd_name.h\"\n     ++\n     ++#define TR2_ENVVAR_PARENT_NAME \"GIT_TR2_PARENT_NAME\"\n     ++\n     ++static struct strbuf tr2cmdname_hierarchy = STRBUF_INIT;\n     ++\n     ++void tr2_cmd_name_append_hierarchy(const char *name)\n     ++{\n     ++\tconst char *parent_name = getenv(TR2_ENVVAR_PARENT_NAME);\n     ++\n     ++\tstrbuf_reset(&tr2cmdname_hierarchy);\n     ++\tif (parent_name && *parent_name) {\n     ++\t\tstrbuf_addstr(&tr2cmdname_hierarchy, parent_name);\n     ++\t\tstrbuf_addch(&tr2cmdname_hierarchy, '/');\n     ++\t}\n     ++\tstrbuf_addstr(&tr2cmdname_hierarchy, name);\n     ++\n     ++\tsetenv(TR2_ENVVAR_PARENT_NAME, tr2cmdname_hierarchy.buf, 1);\n     ++}\n     ++\n     ++const char *tr2_cmd_name_get_hierarchy(void)\n     ++{\n     ++\treturn tr2cmdname_hierarchy.buf;\n     ++}\n     ++\n     ++void tr2_cmd_name_release(void)\n     ++{\n     ++\tstrbuf_release(&tr2cmdname_hierarchy);\n     ++}\n     +\n     + diff --git a/trace2/tr2_cmd_name.h b/trace2/tr2_cmd_name.h\n     + new file mode 100644\n     + --- /dev/null\n     + +++ b/trace2/tr2_cmd_name.h\n     +@@\n     ++#ifndef TR2_CMD_NAME_H\n     ++#define TR2_CMD_NAME_H\n     ++\n     ++/*\n     ++ * Append the current command name to the list being maintained\n     ++ * in the environment.\n     ++ *\n     ++ * The hierarchy for a top-level git command is just the current\n     ++ * command name.  For a child git process, the hierarchy includes the\n     ++ * names of the parent processes.\n     ++ *\n     ++ * The hierarchy for the current process will be exported to the\n     ++ * environment and inherited by child processes.\n     ++ */\n     ++void tr2_cmd_name_append_hierarchy(const char *name);\n     ++\n     ++/*\n     ++ * Get the command name hierarchy for the current process.\n     ++ */\n     ++const char *tr2_cmd_name_get_hierarchy(void);\n     ++\n     ++void tr2_cmd_name_release(void);\n     ++\n     ++#endif /* TR2_CMD_NAME_H */\n     +\n       diff --git a/trace2/tr2_dst.c b/trace2/tr2_dst.c\n       new file mode 100644\n       --- /dev/null\n     @@ -2432,11 +2458,11 @@\n      +\n      +typedef void(tr2_tgt_evt_command_path_fl_t)(const char *file, int line,\n      +\t\t\t\t\t    const char *command_path);\n     -+typedef void(tr2_tgt_evt_command_verb_fl_t)(const char *file, int line,\n     -+\t\t\t\t\t    const char *command_verb,\n     ++typedef void(tr2_tgt_evt_command_name_fl_t)(const char *file, int line,\n     ++\t\t\t\t\t    const char *name,\n      +\t\t\t\t\t    const char *hierarchy);\n     -+typedef void(tr2_tgt_evt_command_subverb_fl_t)(const char *file, int line,\n     -+\t\t\t\t\t       const char *command_subverb);\n     ++typedef void(tr2_tgt_evt_command_mode_fl_t)(const char *file, int line,\n     ++\t\t\t\t\t    const char *mode);\n      +\n      +typedef void(tr2_tgt_evt_alias_fl_t)(const char *file, int line,\n      +\t\t\t\t     const char *alias, const char **argv);\n     @@ -2499,6 +2525,7 @@\n      + * \"vtable\" for a TRACE2 target.  Use NULL if a target does not want\n      + * to emit that message.\n      + */\n     ++/* clang-format off */\n      +struct tr2_tgt {\n      +\tstruct tr2_dst                          *pdst;\n      +\n     @@ -2512,8 +2539,8 @@\n      +\ttr2_tgt_evt_atexit_t                    *pfn_atexit;\n      +\ttr2_tgt_evt_error_va_fl_t               *pfn_error_va_fl;\n      +\ttr2_tgt_evt_command_path_fl_t           *pfn_command_path_fl;\n     -+\ttr2_tgt_evt_command_verb_fl_t           *pfn_command_verb_fl;\n     -+\ttr2_tgt_evt_command_subverb_fl_t        *pfn_command_subverb_fl;\n     ++\ttr2_tgt_evt_command_name_fl_t           *pfn_command_name_fl;\n     ++\ttr2_tgt_evt_command_mode_fl_t           *pfn_command_mode_fl;\n      +\ttr2_tgt_evt_alias_fl_t                  *pfn_alias_fl;\n      +\ttr2_tgt_evt_child_start_fl_t            *pfn_child_start_fl;\n      +\ttr2_tgt_evt_child_exit_fl_t             *pfn_child_exit_fl;\n     @@ -2529,6 +2556,7 @@\n      +\ttr2_tgt_evt_data_json_fl_t              *pfn_data_json_fl;\n      +\ttr2_tgt_evt_printf_va_fl_t              *pfn_printf_va_fl;\n      +};\n     ++/* clang-format on */\n      +\n      +extern struct tr2_tgt tr2_tgt_event;\n      +extern struct tr2_tgt tr2_tgt_normal;\n     @@ -2789,33 +2817,32 @@\n      +\tjw_release(&jw);\n      +}\n      +\n     -+static void fn_command_verb_fl(const char *file, int line,\n     -+\t\t\t       const char *command_verb,\n     -+\t\t\t       const char *verb_hierarchy)\n     ++static void fn_command_name_fl(const char *file, int line,\n     ++\t\t\t       const char *name,\n     ++\t\t\t       const char *hierarchy)\n      +{\n     -+\tconst char *event_name = \"cmd_verb\";\n     ++\tconst char *event_name = \"cmd_name\";\n      +\tstruct json_writer jw = JSON_WRITER_INIT;\n      +\n      +\tjw_object_begin(&jw, 0);\n      +\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n     -+\tjw_object_string(&jw, \"name\", command_verb);\n     -+\tif (verb_hierarchy && *verb_hierarchy)\n     -+\t\tjw_object_string(&jw, \"hierarchy\", verb_hierarchy);\n     ++\tjw_object_string(&jw, \"name\", name);\n     ++\tif (hierarchy && *hierarchy)\n     ++\t\tjw_object_string(&jw, \"hierarchy\", hierarchy);\n      +\tjw_end(&jw);\n      +\n      +\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n      +\tjw_release(&jw);\n      +}\n      +\n     -+static void fn_command_subverb_fl(const char *file, int line,\n     -+\t\t\t\t  const char *command_subverb)\n     ++static void fn_command_mode_fl(const char *file, int line, const char *mode)\n      +{\n     -+\tconst char *event_name = \"cmd_subverb\";\n     ++\tconst char *event_name = \"cmd_mode\";\n      +\tstruct json_writer jw = JSON_WRITER_INIT;\n      +\n      +\tjw_object_begin(&jw, 0);\n      +\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n     -+\tjw_object_string(&jw, \"name\", command_subverb);\n     ++\tjw_object_string(&jw, \"name\", mode);\n      +\tjw_end(&jw);\n      +\n      +\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n     @@ -3114,8 +3141,8 @@\n      +\tfn_atexit,\n      +\tfn_error_va_fl,\n      +\tfn_command_path_fl,\n     -+\tfn_command_verb_fl,\n     -+\tfn_command_subverb_fl,\n     ++\tfn_command_name_fl,\n     ++\tfn_command_mode_fl,\n      +\tfn_alias_fl,\n      +\tfn_child_start_fl,\n      +\tfn_child_exit_fl,\n     @@ -3300,25 +3327,24 @@\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n     -+static void fn_command_verb_fl(const char *file, int line,\n     -+\t\t\t       const char *command_verb,\n     -+\t\t\t       const char *verb_hierarchy)\n     ++static void fn_command_name_fl(const char *file, int line,\n     ++\t\t\t       const char *name,\n     ++\t\t\t       const char *hierarchy)\n      +{\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n      +\n     -+\tstrbuf_addf(&buf_payload, \"cmd_verb %s\", command_verb);\n     -+\tif (verb_hierarchy && *verb_hierarchy)\n     -+\t\tstrbuf_addf(&buf_payload, \" (%s)\", verb_hierarchy);\n     ++\tstrbuf_addf(&buf_payload, \"cmd_name %s\", name);\n     ++\tif (hierarchy && *hierarchy)\n     ++\t\tstrbuf_addf(&buf_payload, \" (%s)\", hierarchy);\n      +\tnormal_io_write_fl(file, line, &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n     -+static void fn_command_subverb_fl(const char *file, int line,\n     -+\t\t\t\t  const char *command_subverb)\n     ++static void fn_command_mode_fl(const char *file, int line, const char *mode)\n      +{\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n      +\n     -+\tstrbuf_addf(&buf_payload, \"cmd_subverb %s\", command_subverb);\n     ++\tstrbuf_addf(&buf_payload, \"cmd_mode %s\", mode);\n      +\tnormal_io_write_fl(file, line, &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n     @@ -3445,8 +3471,8 @@\n      +\tfn_atexit,\n      +\tfn_error_va_fl,\n      +\tfn_command_path_fl,\n     -+\tfn_command_verb_fl,\n     -+\tfn_command_subverb_fl,\n     ++\tfn_command_name_fl,\n     ++\tfn_command_mode_fl,\n      +\tfn_alias_fl,\n      +\tfn_child_start_fl,\n      +\tfn_child_exit_fl,\n     @@ -3721,29 +3747,28 @@\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n     -+static void fn_command_verb_fl(const char *file, int line,\n     -+\t\t\t       const char *command_verb,\n     -+\t\t\t       const char *verb_hierarchy)\n     ++static void fn_command_name_fl(const char *file, int line,\n     ++\t\t\t       const char *name,\n     ++\t\t\t       const char *hierarchy)\n      +{\n     -+\tconst char *event_name = \"cmd_verb\";\n     ++\tconst char *event_name = \"cmd_name\";\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n      +\n     -+\tstrbuf_addstr(&buf_payload, command_verb);\n     -+\tif (verb_hierarchy && *verb_hierarchy)\n     -+\t\tstrbuf_addf(&buf_payload, \" (%s)\", verb_hierarchy);\n     ++\tstrbuf_addstr(&buf_payload, name);\n     ++\tif (hierarchy && *hierarchy)\n     ++\t\tstrbuf_addf(&buf_payload, \" (%s)\", hierarchy);\n      +\n      +\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n      +\t\t\t &buf_payload);\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n     -+static void fn_command_subverb_fl(const char *file, int line,\n     -+\t\t\t\t  const char *command_subverb)\n     ++static void fn_command_mode_fl(const char *file, int line, const char *mode)\n      +{\n     -+\tconst char *event_name = \"cmd_subverb\";\n     ++\tconst char *event_name = \"cmd_mode\";\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n      +\n     -+\tstrbuf_addstr(&buf_payload, command_subverb);\n     ++\tstrbuf_addstr(&buf_payload, mode);\n      +\n      +\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n      +\t\t\t &buf_payload);\n     @@ -3987,8 +4012,8 @@\n      +\tfn_atexit,\n      +\tfn_error_va_fl,\n      +\tfn_command_path_fl,\n     -+\tfn_command_verb_fl,\n     -+\tfn_command_subverb_fl,\n     ++\tfn_command_name_fl,\n     ++\tfn_command_mode_fl,\n      +\tfn_alias_fl,\n      +\tfn_child_start_fl,\n      +\tfn_child_exit_fl,\n     @@ -4278,72 +4303,6 @@\n      +\n      +#endif /* TR2_TLS_H */\n      \n     - diff --git a/trace2/tr2_verb.c b/trace2/tr2_verb.c\n     - new file mode 100644\n     - --- /dev/null\n     - +++ b/trace2/tr2_verb.c\n     -@@\n     -+#include \"cache.h\"\n     -+#include \"trace2/tr2_verb.h\"\n     -+\n     -+#define TR2_ENVVAR_PARENT_VERB \"GIT_TR2_PARENT_VERB\"\n     -+\n     -+static struct strbuf tr2verb_hierarchy = STRBUF_INIT;\n     -+\n     -+void tr2_verb_append_hierarchy(const char *verb)\n     -+{\n     -+\tconst char *parent_verb = getenv(TR2_ENVVAR_PARENT_VERB);\n     -+\n     -+\tstrbuf_reset(&tr2verb_hierarchy);\n     -+\tif (parent_verb && *parent_verb) {\n     -+\t\tstrbuf_addstr(&tr2verb_hierarchy, parent_verb);\n     -+\t\tstrbuf_addch(&tr2verb_hierarchy, '/');\n     -+\t}\n     -+\tstrbuf_addstr(&tr2verb_hierarchy, verb);\n     -+\n     -+\tsetenv(TR2_ENVVAR_PARENT_VERB, tr2verb_hierarchy.buf, 1);\n     -+}\n     -+\n     -+const char *tr2_verb_get_hierarchy(void)\n     -+{\n     -+\treturn tr2verb_hierarchy.buf;\n     -+}\n     -+\n     -+void tr2_verb_release(void)\n     -+{\n     -+\tstrbuf_release(&tr2verb_hierarchy);\n     -+}\n     -\n     - diff --git a/trace2/tr2_verb.h b/trace2/tr2_verb.h\n     - new file mode 100644\n     - --- /dev/null\n     - +++ b/trace2/tr2_verb.h\n     -@@\n     -+#ifndef TR2_VERB_H\n     -+#define TR2_VERB_H\n     -+\n     -+/*\n     -+ * Append the current git command's \"verb\" to the list being maintained\n     -+ * in the environment.\n     -+ *\n     -+ * The hierarchy for a top-level git command is just the current verb.\n     -+ * For a child git process, the hierarchy lists the verbs of the parent\n     -+ * git processes (much like the SID).\n     -+ *\n     -+ * The hierarchy for the current process will be exported to the environment\n     -+ * and inherited by child processes.\n     -+ */\n     -+void tr2_verb_append_hierarchy(const char *verb);\n     -+\n     -+/*\n     -+ * Get the verb hierarchy for the current process.\n     -+ */\n     -+const char *tr2_verb_get_hierarchy(void);\n     -+\n     -+void tr2_verb_release(void);\n     -+\n     -+#endif /* TR2_VERB_H */\n     -\n       diff --git a/usage.c b/usage.c\n       --- a/usage.c\n       +++ b/usage.c\n     @@ -4357,7 +4316,7 @@\n      +\t * cmd_main(), we don't know what verb to report.  Force it to this\n      +\t * to facilitate post-processing.\n      +\t */\n     -+\ttrace2_cmd_verb(\"_usage_\");\n     ++\ttrace2_cmd_name(\"_usage_\");\n      +\n      +\t/*\n      +\t * Currently, the (err, params) are usually just the static usage\n  3:  6167aba76c !  3:  7c987cde86 trace2: collect platform-specific process information\n     @@ -1,16 +1,14 @@\n      Author: Jeff Hostetler <jeffhost@microsoft.com>\n      \n     -    trace2: collect platform-specific process information\n     +    trace2: collect Windows-specific process information\n      \n     -    Add optional platform-specific code to log information about\n     -    the current process.\n     +    Add platform-specific interface to log information about the current\n     +    process.\n      \n     -    On Windows, this includes whether git.exe is running under a\n     -    debugger and information about the ancestors of the process.\n     +    On Windows, this interface is used to indicate whether the git process\n     +    is running under a debugger and list names of the process ancestors.\n      \n     -    The purpose of this information is to help indicate if the\n     -    process was launched interactively or in the background by\n     -    an IDE, for example.\n     +    Information for other platforms is left for a future effort.\n      \n          Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n      \n     @@ -26,10 +24,10 @@\n       \tgit_resolve_executable_dir(argv[0]);\n       \n      \n     - diff --git a/compat/win32/ancestry.c b/compat/win32/ancestry.c\n     + diff --git a/compat/win32/trace2_win32_process_info.c b/compat/win32/trace2_win32_process_info.c\n       new file mode 100644\n       --- /dev/null\n     - +++ b/compat/win32/ancestry.c\n     + +++ b/compat/win32/trace2_win32_process_info.c\n      @@\n      +#include \"../../cache.h\"\n      +#include \"../../json-writer.h\"\n     @@ -140,7 +138,7 @@\n       \tBASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE\n       \tCOMPAT_OBJS = compat/msvc.o compat/winansi.o \\\n       \t\tcompat/win32/pthread.o compat/win32/syslog.o \\\n     -+\t\tcompat/win32/ancestry.o \\\n     ++\t\tcompat/win32/trace2_win32_process_info.o \\\n       \t\tcompat/win32/dirent.o\n       \tCOMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\\\".exe\\\"\n       \tBASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE\n     @@ -148,7 +146,7 @@\n       \tCOMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32\n       \tCOMPAT_CFLAGS += -DSTRIP_EXTENSION=\\\".exe\\\"\n       \tCOMPAT_OBJS += compat/mingw.o compat/winansi.o \\\n     -+\t\tcompat/win32/ancestry.o \\\n     ++\t\tcompat/win32/trace2_win32_process_info.o \\\n       \t\tcompat/win32/path-utils.o \\\n       \t\tcompat/win32/pthread.o compat/win32/syslog.o \\\n       \t\tcompat/win32/dirent.o\n     @@ -157,7 +155,7 @@\n       --- a/trace2.h\n       +++ b/trace2.h\n      @@\n     - void trace2_printf(const char *fmt, ...);\n     + /* clang-format on */\n       #endif\n       \n      +/*\n  4:  3624c6cbd9 =  4:  cd860799f8 trace2:data: add trace2 regions to wt-status\n  5:  638fdb294f =  5:  30bcea9435 trace2:data: add editor/pager child classification\n  6:  f4f0453e6b =  6:  c869de8063 trace2:data: add trace2 sub-process classification\n  7:  660b83c453 =  7:  3cd525b80e trace2:data: add trace2 transport child classification\n  8:  ab26888126 =  8:  c2c1ea5ce3 trace2:data: add trace2 hook classification\n  9:  87a73af338 =  9:  57b23542b3 trace2:data: add trace2 instrumentation to index read/write\n 10:  be707ea960 ! 10:  ab74ef5f23 pack-objects: add trace2 regions\n     @@ -1,6 +1,6 @@\n      Author: Derrick Stolee <dstolee@microsoft.com>\n      \n     -    pack-objects: add trace2 regions\n     +    trace2:data: pack-objects: add trace2 regions\n      \n          When studying the performance of 'git push' we would like to know\n          how much time is spent at various parts of the command. One area\n 11:  b4c8ff53dd ! 11:  6a82426a83 trace2:data: add subverb to checkout command\n     @@ -11,7 +11,7 @@\n       \tstruct lock_file lock_file = LOCK_INIT;\n       \tint nr_checkouts = 0;\n       \n     -+\ttrace2_cmd_subverb(opts->patch_mode ? \"patch\" : \"path\");\n     ++\ttrace2_cmd_mode(opts->patch_mode ? \"patch\" : \"path\");\n      +\n       \tif (opts->track != BRANCH_TRACK_UNSPECIFIED)\n       \t\tdie(_(\"'%s' cannot be used with updating paths\"), \"--track\");\n     @@ -21,7 +21,7 @@\n       \tstruct object_id rev;\n       \tint flag, writeout_error = 0;\n      +\n     -+\ttrace2_cmd_subverb(\"branch\");\n     ++\ttrace2_cmd_mode(\"branch\");\n      +\n       \tmemset(&old_branch_info, 0, sizeof(old_branch_info));\n       \told_branch_info.path = path_to_free = resolve_refdup(\"HEAD\", 0, &rev, &flag);\n     @@ -30,7 +30,7 @@\n       \tint status;\n       \tstruct strbuf branch_ref = STRBUF_INIT;\n       \n     -+\ttrace2_cmd_subverb(\"unborn\");\n     ++\ttrace2_cmd_mode(\"unborn\");\n      +\n       \tif (!opts->new_branch)\n       \t\tdie(_(\"You are on a branch yet to be born\"));\n 12:  ed1d612006 ! 12:  5c7f0de228 trace2:data: add subverb to reset command\n     @@ -11,7 +11,7 @@\n       \tif (patch_mode) {\n       \t\tif (reset_type != NONE)\n       \t\t\tdie(_(\"--patch is incompatible with --{hard,mixed,soft}\"));\n     -+\t\ttrace2_cmd_subverb(\"patch-interactive\");\n     ++\t\ttrace2_cmd_mode(\"patch-interactive\");\n       \t\treturn run_add_interactive(rev, \"--patch=reset\", &pathspec);\n       \t}\n       \n     @@ -20,9 +20,9 @@\n       \t\treset_type = MIXED; /* by default */\n       \n      +\tif (pathspec.nr)\n     -+\t\ttrace2_cmd_subverb(\"path\");\n     ++\t\ttrace2_cmd_mode(\"path\");\n      +\telse\n     -+\t\ttrace2_cmd_subverb(reset_type_names[reset_type]);\n     ++\t\ttrace2_cmd_mode(reset_type_names[reset_type]);\n      +\n       \tif (reset_type != SOFT && (reset_type != MIXED || get_git_work_tree()))\n       \t\tsetup_work_tree();\n 13:  4f55f33624 ! 13:  cc61201061 trace2:data: add subverb for rebase\n     @@ -28,11 +28,11 @@\n       \n      +\tif (trace2_is_enabled()) {\n      +\t\tif (is_interactive(&options))\n     -+\t\t\ttrace2_cmd_subverb(\"interactive\");\n     ++\t\t\ttrace2_cmd_mode(\"interactive\");\n      +\t\telse if (exec.nr)\n     -+\t\t\ttrace2_cmd_subverb(\"interactive-exec\");\n     ++\t\t\ttrace2_cmd_mode(\"interactive-exec\");\n      +\t\telse\n     -+\t\t\ttrace2_cmd_subverb(action_names[action]);\n     ++\t\t\ttrace2_cmd_mode(action_names[action]);\n      +\t}\n      +\n       \tswitch (action) {\n 14:  8d47cd71ed ! 14:  00b25da38b trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh\n     @@ -89,7 +89,7 @@\n      + * [] the process-exit value.\n      + * [] the \"code\" field in the \"exit\" trace2 event.\n      + * [] the \"code\" field in the \"atexit\" trace2 event.\n     -+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n     ++ * [] the \"name\" field in the \"cmd_name\" trace2 event.\n      + * [] \"def_param\" events for all of the \"interesting\" pre-defined\n      + * config settings.\n      + */\n     @@ -109,7 +109,7 @@\n      + * Test harness can confirm:\n      + * [] the \"code\" field in the \"exit\" trace2 event.\n      + * [] the \"code\" field in the \"atexit\" trace2 event.\n     -+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n     ++ * [] the \"name\" field in the \"cmd_name\" trace2 event.\n      + * [] \"def_param\" events for all of the \"interesting\" pre-defined\n      + * config settings.\n      + */\n     @@ -130,7 +130,7 @@\n      + *\n      + * Test harness can confirm:\n      + * [] a trace2 \"error\" event for each value in argv.\n     -+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n     ++ * [] the \"name\" field in the \"cmd_name\" trace2 event.\n      + * [] (optional) the file:line in the \"exit\" event refers to this function.\n      + */\n      +static int ut_003error(int argc, const char **argv)\n     @@ -157,7 +157,7 @@\n      + * test-tool trace2 004child git -c alias.xyz=version xyz\n      + *\n      + * Test harness can confirm:\n     -+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n     ++ * [] the \"name\" field in the \"cmd_name\" trace2 event.\n      + * [] that the outer process has a single component SID (or depth \"d0\" in\n      + *    the PERF stream).\n      + * [] that \"child_start\" and \"child_exit\" events are generated for the child.\n     @@ -203,7 +203,7 @@\n      + * test-tool trace2 005exec version\n      + *\n      + * Test harness can confirm (on Windows):\n     -+ * [] the \"name\" field in the \"cmd_verb\" trace2 event.\n     ++ * [] the \"name\" field in the \"cmd_name\" trace2 event.\n      + * [] that the outer process has a single component SID (or depth \"d0\" in\n      + *    the PERF stream).\n      + * [] that \"exec\" and \"exec_result\" events are generated for the child\n     @@ -255,6 +255,7 @@\n      + */\n      +#define USAGE_PREFIX \"test-tool trace2\"\n      +\n     ++/* clang-format off */\n      +static struct unit_test ut_table[] = {\n      +\t{ ut_001return,   \"001return\", \"<exit_code>\" },\n      +\t{ ut_002exit,     \"002exit\",   \"<exit_code>\" },\n     @@ -263,11 +264,14 @@\n      +\t{ ut_005exec,     \"005exec\",   \"<git_command_args>\" },\n      +\t{ ut_006data,     \"006data\",   \"[<category> <key> <value>]+\" },\n      +};\n     ++/* clang-format on */\n      +\n     ++/* clang-format off */\n      +#define for_each_ut(k, ut_k)\t\t\t\\\n      +\tfor (k = 0, ut_k = &ut_table[k];\t\\\n      +\t     k < ARRAY_SIZE(ut_table);\t\t\\\n      +\t     k++, ut_k = &ut_table[k])\n     ++/* clang-format on */\n      +\n      +static int print_usage(void)\n      +{\n     @@ -275,11 +279,9 @@\n      +\tstruct unit_test *ut_k;\n      +\n      +\tfprintf(stderr, \"usage:\\n\");\n     -+\tfor_each_ut(k, ut_k)\n     -+\t{\n     ++\tfor_each_ut (k, ut_k)\n      +\t\tfprintf(stderr, \"\\t%s %s %s\\n\", USAGE_PREFIX, ut_k->ut_name,\n      +\t\t\tut_k->ut_usage);\n     -+\t}\n      +\n      +\treturn 129;\n      +}\n     @@ -290,14 +292,14 @@\n      + * We assume that these trace2 routines has already been called:\n      + *    [] trace2_initialize()      [common-main.c:main()]\n      + *    [] trace2_cmd_start()       [common-main.c:main()]\n     -+ *    [] trace2_cmd_verb()        [test-tool.c:cmd_main()]\n     ++ *    [] trace2_cmd_name()        [test-tool.c:cmd_main()]\n      + *    [] tracd2_cmd_list_config() [test-tool.c:cmd_main()]\n      + * So that:\n      + *    [] the various trace2 streams are open.\n      + *    [] the process SID has been created.\n      + *    [] the \"version\" event has been generated.\n      + *    [] the \"start\" event has been generated.\n     -+ *    [] the \"verb\" event has been generated.\n     ++ *    [] the \"cmd_name\" event has been generated.\n      + *    [] this writes various \"def_param\" events for interesting config values.\n      + *\n      + * We further assume that if we return (rather than exit()), trace2_cmd_exit()\n     @@ -311,13 +313,10 @@\n      +\targc--; /* skip over \"trace2\" arg */\n      +\targv++;\n      +\n     -+\tif (argc) {\n     -+\t\tfor_each_ut(k, ut_k)\n     -+\t\t{\n     ++\tif (argc)\n     ++\t\tfor_each_ut (k, ut_k)\n      +\t\t\tif (!strcmp(argv[0], ut_k->ut_name))\n      +\t\t\t\treturn ut_k->ut_fn(argc - 1, argv + 1);\n     -+\t\t}\n     -+\t}\n      +\n      +\treturn print_usage();\n      +}\n     @@ -370,7 +369,7 @@\n      +# We do confirm the following API features:\n      +# [] the 'version <v>' event\n      +# [] the 'start <argv>' event\n     -+# [] the 'cmd_verb <verb>' event\n     ++# [] the 'cmd_name <name>' event\n      +# [] the 'exit <time> code:<code>' event\n      +# [] the 'atexit <time> code:<code>' event\n      +#\n     @@ -388,7 +387,7 @@\n      +\tcat >expect <<-EOF &&\n      +\t\tversion $V\n      +\t\tstart _EXE_ trace2 001return 0\n     -+\t\tcmd_verb trace2 (trace2)\n     ++\t\tcmd_name trace2 (trace2)\n      +\t\texit elapsed:_TIME_ code:0\n      +\t\tatexit elapsed:_TIME_ code:0\n      +\tEOF\n     @@ -402,7 +401,7 @@\n      +\tcat >expect <<-EOF &&\n      +\t\tversion $V\n      +\t\tstart _EXE_ trace2 001return 1\n     -+\t\tcmd_verb trace2 (trace2)\n     ++\t\tcmd_name trace2 (trace2)\n      +\t\texit elapsed:_TIME_ code:1\n      +\t\tatexit elapsed:_TIME_ code:1\n      +\tEOF\n     @@ -420,7 +419,7 @@\n      +\tcat >expect <<-EOF &&\n      +\t\tversion $V\n      +\t\tstart _EXE_ trace2 002exit 0\n     -+\t\tcmd_verb trace2 (trace2)\n     ++\t\tcmd_name trace2 (trace2)\n      +\t\texit elapsed:_TIME_ code:0\n      +\t\tatexit elapsed:_TIME_ code:0\n      +\tEOF\n     @@ -434,7 +433,7 @@\n      +\tcat >expect <<-EOF &&\n      +\t\tversion $V\n      +\t\tstart _EXE_ trace2 002exit 1\n     -+\t\tcmd_verb trace2 (trace2)\n     ++\t\tcmd_name trace2 (trace2)\n      +\t\texit elapsed:_TIME_ code:1\n      +\t\tatexit elapsed:_TIME_ code:1\n      +\tEOF\n     @@ -452,7 +451,7 @@\n      +\tcat >expect <<-EOF &&\n      +\t\tversion $V\n      +\t\tstart _EXE_ trace2 003error '\\''hello world'\\'' '\\''this is a test'\\''\n     -+\t\tcmd_verb trace2 (trace2)\n     ++\t\tcmd_name trace2 (trace2)\n      +\t\terror hello world\n      +\t\terror this is a test\n      +\t\texit elapsed:_TIME_ code:0\n     @@ -575,7 +574,7 @@\n      +\tcat >expect <<-EOF &&\n      +\t\td0|main|version|||||$V\n      +\t\td0|main|start|||||_EXE_ trace2 001return 0\n     -+\t\td0|main|cmd_verb|||||trace2 (trace2)\n     ++\t\td0|main|cmd_name|||||trace2 (trace2)\n      +\t\td0|main|exit||_T_ABS_|||code:0\n      +\t\td0|main|atexit||_T_ABS_|||code:0\n      +\tEOF\n     @@ -589,7 +588,7 @@\n      +\tcat >expect <<-EOF &&\n      +\t\td0|main|version|||||$V\n      +\t\td0|main|start|||||_EXE_ trace2 001return 1\n     -+\t\td0|main|cmd_verb|||||trace2 (trace2)\n     ++\t\td0|main|cmd_name|||||trace2 (trace2)\n      +\t\td0|main|exit||_T_ABS_|||code:1\n      +\t\td0|main|atexit||_T_ABS_|||code:1\n      +\tEOF\n     @@ -607,7 +606,7 @@\n      +\tcat >expect <<-EOF &&\n      +\t\td0|main|version|||||$V\n      +\t\td0|main|start|||||_EXE_ trace2 003error '\\''hello world'\\'' '\\''this is a test'\\''\n     -+\t\td0|main|cmd_verb|||||trace2 (trace2)\n     ++\t\td0|main|cmd_name|||||trace2 (trace2)\n      +\t\td0|main|error|||||hello world\n      +\t\td0|main|error|||||this is a test\n      +\t\td0|main|exit||_T_ABS_|||code:0\n     @@ -628,15 +627,15 @@\n      +# Which should generate events:\n      +#    P1: version\n      +#    P1: start\n     -+#    P1: cmd_verb\n     ++#    P1: cmd_name\n      +#    P1: child_start\n      +#        P2: version\n      +#        P2: start\n     -+#        P2: cmd_verb\n     ++#        P2: cmd_name\n      +#        P2: child_start\n      +#            P3: version\n      +#            P3: start\n     -+#            P3: cmd_verb\n     ++#            P3: cmd_name\n      +#            P3: exit\n      +#            P3: atexit\n      +#        P2: child_exit\n     @@ -653,15 +652,15 @@\n      +\tcat >expect <<-EOF &&\n      +\t\td0|main|version|||||$V\n      +\t\td0|main|start|||||_EXE_ trace2 004child test-tool trace2 004child test-tool trace2 001return 0\n     -+\t\td0|main|cmd_verb|||||trace2 (trace2)\n     ++\t\td0|main|cmd_name|||||trace2 (trace2)\n      +\t\td0|main|child_start||_T_ABS_|||[ch0] class:? argv: test-tool trace2 004child test-tool trace2 001return 0\n      +\t\td1|main|version|||||$V\n      +\t\td1|main|start|||||_EXE_ trace2 004child test-tool trace2 001return 0\n     -+\t\td1|main|cmd_verb|||||trace2 (trace2/trace2)\n     ++\t\td1|main|cmd_name|||||trace2 (trace2/trace2)\n      +\t\td1|main|child_start||_T_ABS_|||[ch0] class:? argv: test-tool trace2 001return 0\n      +\t\td2|main|version|||||$V\n      +\t\td2|main|start|||||_EXE_ trace2 001return 0\n     -+\t\td2|main|cmd_verb|||||trace2 (trace2/trace2/trace2)\n     ++\t\td2|main|cmd_name|||||trace2 (trace2/trace2/trace2)\n      +\t\td2|main|exit||_T_ABS_|||code:0\n      +\t\td2|main|atexit||_T_ABS_|||code:0\n      +\t\td1|main|child_exit||_T_ABS_|_T_REL_||[ch0] pid:_PID_ code:0\n     @@ -822,8 +821,8 @@\n      +\t|      \"%s\"\n      +\t|    ],\n      +\t|    \"exit_code\":0,\n     -+\t|    \"verb\":\"trace2\",\n     -+\t|    \"verb_hierarchy\":\"trace2\",\n     ++\t|    \"hierarchy\":\"trace2\",\n     ++\t|    \"name\":\"trace2\",\n      +\t|    \"version\":\"$V\"\n      +\t|  }\n      +\t|};\n     @@ -876,8 +875,8 @@\n      +\t|      }\n      +\t|    },\n      +\t|    \"exit_code\":0,\n     -+\t|    \"verb\":\"trace2\",\n     -+\t|    \"verb_hierarchy\":\"trace2\",\n     ++\t|    \"hierarchy\":\"trace2\",\n     ++\t|    \"name\":\"trace2\",\n      +\t|    \"version\":\"$V\"\n      +\t|  },\n      +\t|  \"_SID0_/_SID1_\":{\n     @@ -904,8 +903,8 @@\n      +\t|      }\n      +\t|    },\n      +\t|    \"exit_code\":0,\n     -+\t|    \"verb\":\"trace2\",\n     -+\t|    \"verb_hierarchy\":\"trace2/trace2\",\n     ++\t|    \"hierarchy\":\"trace2/trace2\",\n     ++\t|    \"name\":\"trace2\",\n      +\t|    \"version\":\"$V\"\n      +\t|  },\n      +\t|  \"_SID0_/_SID1_/_SID2_\":{\n     @@ -916,8 +915,8 @@\n      +\t|      \"0\"\n      +\t|    ],\n      +\t|    \"exit_code\":0,\n     -+\t|    \"verb\":\"trace2\",\n     -+\t|    \"verb_hierarchy\":\"trace2/trace2/trace2\",\n     ++\t|    \"hierarchy\":\"trace2/trace2/trace2\",\n     ++\t|    \"name\":\"trace2\",\n      +\t|    \"version\":\"$V\"\n      +\t|  }\n      +\t|};\n     @@ -943,6 +942,8 @@\n      +\t|      \"0\"\n      +\t|    ],\n      +\t|    \"exit_code\":0,\n     ++\t|    \"hierarchy\":\"trace2\",\n     ++\t|    \"name\":\"trace2\",\n      +\t|    \"params\":[\n      +\t|      {\n      +\t|        \"param\":\"t0212.abc\",\n     @@ -953,8 +954,6 @@\n      +\t|        \"value\":\"hello world\"\n      +\t|      }\n      +\t|    ],\n     -+\t|    \"verb\":\"trace2\",\n     -+\t|    \"verb_hierarchy\":\"trace2\",\n      +\t|    \"version\":\"$V\"\n      +\t|  }\n      +\t|};\n     @@ -987,8 +986,8 @@\n      +\t|      }\n      +\t|    },\n      +\t|    \"exit_code\":0,\n     -+\t|    \"verb\":\"trace2\",\n     -+\t|    \"verb_hierarchy\":\"trace2\",\n     ++\t|    \"hierarchy\":\"trace2\",\n     ++\t|    \"name\":\"trace2\",\n      +\t|    \"version\":\"$V\"\n      +\t|  }\n      +\t|};\n     @@ -1138,9 +1137,9 @@\n      +\t# $processes->{$sid}->{'path'} = \"_EXE_\";\n      +    }\n      +    \n     -+    elsif ($event eq 'cmd_verb') {\n     -+\t$processes->{$sid}->{'verb'} = $line->{'name'};\n     -+\t$processes->{$sid}->{'verb_hierarchy'} = $line->{'hierarchy'};\n     ++    elsif ($event eq 'cmd_name') {\n     ++\t$processes->{$sid}->{'name'} = $line->{'name'};\n     ++\t$processes->{$sid}->{'hierarchy'} = $line->{'hierarchy'};\n      +    }\n      +\n      +    elsif ($event eq 'alias') {\n  -:  ---------- > 15:  5f77c9b633 trace2: add for_each macros to clang-format\n\n-- \ngitgitgadget\n"},{"id":"368596","messageId":"pull.108.v6.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v5.git.gitgitgadget@gmail.com","subject":"[PATCH v6 00/15] Trace2 tracing facility","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-06T17:15:51Z","receivedAt":"2019-02-06T17:16:00Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"V6 addresses: [] The remaining hdr-check warning in trace2/tr2_tls.h\n\nThere are no other outstanding comments that I'm aware of.\n\nThanks Jeff\n\n\n----------------------------------------------------------------------------\n\nV5 addresses: [] renames \"verb\" and \"subverb\" to \"cmd_name\" and \"cmd_mode\"\nin code and documentation. [] updates clang-format config to not complain\nabout my for_each macros. [] update formatting around each use of my\nfor_each macros. [] update the platform-specific process info commit to\nindicate it only handles Windows and leaves other platform for future\nefforts. [] alters title of pack-objects commit to include \"trace2:data:\" to\nmatch the other data-only commits.\n\nI think this version addresses all of the feedback received do date.\n\nThanks Jeff\n\n\n----------------------------------------------------------------------------\n\nSorry to spam the list, but here is V4. After building V3 on 3 platforms\nwithout error and submitting, the compilers on platforms 4 and 5 complained\nabout a variable declaration. (sigh) [] fix declaration after first\nstatement [] add -DNO_UNIX_SOCKETS to BASIC_CFLAGS when NO_UNIX_SOCKETS is\ndefined in the Makefile.\n\n\n----------------------------------------------------------------------------\n\nV3 addresses: [] re-fix the trace2 tests using an inline environment\nvariable rather than exporting and unsetting. [] overhaul the design\ndocument to include prototype declarations and more file format information.\n[] incorporate most of the suggestions from clang-format. [] add ability to\ntrace to a unix domain socket. [] added forward declarations suggested by\nRamsay. [] rebased onto current master to fixup conflict with\nsb/submodule-recursive-fetch-gets-the-tip that was merged yesterday.\n\n\n----------------------------------------------------------------------------\n\nV2 addresses: [] \"jh/trace2\" bad interaction with \"js/vsts-ci\" in \"pu\". []\ncoccinelle warnings in trace2/tr2_tgt_perf.c reported during CI testing.\n\n\n----------------------------------------------------------------------------\n\nThis patch series contains a greatly refactored version of my original\nTrace2 series [1] from August 2018.\n\nA new design doc in Documentation/technical/api-trace2.txt (in the first\ncommit) explains the relationship of Trace2 to the current tracing facility.\nCalls to the current tracing facility have not been changed, rather new\ntrace2 calls have been added so that both continue to work in parallel for\nthe time being.\n\n[1] https://public-inbox.org/git/pull.29.git.gitgitgadget@gmail.com/\n\nCc: gitster@pobox.comCc: peff@peff.netCc: jrnieder@gmail.com\n\nDerrick Stolee (1):\n  trace2:data: pack-objects: add trace2 regions\n\nJeff Hostetler (14):\n  trace2: Documentation/technical/api-trace2.txt\n  trace2: create new combined trace facility\n  trace2: collect Windows-specific process information\n  trace2:data: add trace2 regions to wt-status\n  trace2:data: add editor/pager child classification\n  trace2:data: add trace2 sub-process classification\n  trace2:data: add trace2 transport child classification\n  trace2:data: add trace2 hook classification\n  trace2:data: add trace2 instrumentation to index read/write\n  trace2:data: add subverb to checkout command\n  trace2:data: add subverb to reset command\n  trace2:data: add subverb for rebase\n  trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh\n  trace2: add for_each macros to clang-format\n\n .clang-format                            |    2 +-\n Documentation/technical/api-trace2.txt   | 1347 ++++++++++++++++++++++\n Makefile                                 |   15 +-\n builtin/am.c                             |    1 +\n builtin/checkout.c                       |    7 +\n builtin/pack-objects.c                   |   16 +-\n builtin/rebase.c                         |   17 +\n builtin/receive-pack.c                   |    4 +\n builtin/reset.c                          |    6 +\n builtin/submodule--helper.c              |    9 +-\n builtin/worktree.c                       |    1 +\n cache.h                                  |    1 +\n common-main.c                            |   13 +-\n compat/mingw.c                           |   11 +-\n compat/mingw.h                           |    3 +-\n compat/win32/trace2_win32_process_info.c |  101 ++\n config.c                                 |    2 +\n config.mak.uname                         |    2 +\n connect.c                                |    3 +\n editor.c                                 |    1 +\n exec-cmd.c                               |    2 +\n git-compat-util.h                        |    7 +\n git.c                                    |   65 ++\n pager.c                                  |    1 +\n read-cache.c                             |   51 +-\n remote-curl.c                            |    7 +\n repository.c                             |    2 +\n repository.h                             |    3 +\n run-command.c                            |   59 +-\n run-command.h                            |   13 +-\n sequencer.c                              |    2 +\n sh-i18n--envsubst.c                      |    3 +\n sub-process.c                            |    1 +\n submodule.c                              |   11 +-\n t/helper/test-parse-options.c            |    3 +\n t/helper/test-tool.c                     |    4 +\n t/helper/test-tool.h                     |    1 +\n t/helper/test-trace2.c                   |  273 +++++\n t/t0001-init.sh                          |    1 +\n t/t0210-trace2-normal.sh                 |  135 +++\n t/t0210/scrub_normal.perl                |   48 +\n t/t0211-trace2-perf.sh                   |  153 +++\n t/t0211/scrub_perf.perl                  |   76 ++\n t/t0212-trace2-event.sh                  |  234 ++++\n t/t0212/parse_events.perl                |  251 ++++\n trace2.c                                 |  762 ++++++++++++\n trace2.h                                 |  385 +++++++\n trace2/tr2_cfg.c                         |   90 ++\n trace2/tr2_cfg.h                         |   19 +\n trace2/tr2_cmd_name.c                    |   30 +\n trace2/tr2_cmd_name.h                    |   24 +\n trace2/tr2_dst.c                         |  197 ++++\n trace2/tr2_dst.h                         |   36 +\n trace2/tr2_sid.c                         |   67 ++\n trace2/tr2_sid.h                         |   18 +\n trace2/tr2_tbuf.c                        |   32 +\n trace2/tr2_tbuf.h                        |   23 +\n trace2/tr2_tgt.h                         |  133 +++\n trace2/tr2_tgt_event.c                   |  589 ++++++++++\n trace2/tr2_tgt_normal.c                  |  324 ++++++\n trace2/tr2_tgt_perf.c                    |  535 +++++++++\n trace2/tr2_tls.c                         |  164 +++\n trace2/tr2_tls.h                         |   97 ++\n transport-helper.c                       |    2 +\n transport.c                              |    1 +\n usage.c                                  |   31 +\n wt-status.c                              |   24 +-\n 67 files changed, 6528 insertions(+), 23 deletions(-)\n create mode 100644 Documentation/technical/api-trace2.txt\n create mode 100644 compat/win32/trace2_win32_process_info.c\n create mode 100644 t/helper/test-trace2.c\n create mode 100755 t/t0210-trace2-normal.sh\n create mode 100644 t/t0210/scrub_normal.perl\n create mode 100755 t/t0211-trace2-perf.sh\n create mode 100644 t/t0211/scrub_perf.perl\n create mode 100755 t/t0212-trace2-event.sh\n create mode 100644 t/t0212/parse_events.perl\n create mode 100644 trace2.c\n create mode 100644 trace2.h\n create mode 100644 trace2/tr2_cfg.c\n create mode 100644 trace2/tr2_cfg.h\n create mode 100644 trace2/tr2_cmd_name.c\n create mode 100644 trace2/tr2_cmd_name.h\n create mode 100644 trace2/tr2_dst.c\n create mode 100644 trace2/tr2_dst.h\n create mode 100644 trace2/tr2_sid.c\n create mode 100644 trace2/tr2_sid.h\n create mode 100644 trace2/tr2_tbuf.c\n create mode 100644 trace2/tr2_tbuf.h\n create mode 100644 trace2/tr2_tgt.h\n create mode 100644 trace2/tr2_tgt_event.c\n create mode 100644 trace2/tr2_tgt_normal.c\n create mode 100644 trace2/tr2_tgt_perf.c\n create mode 100644 trace2/tr2_tls.c\n create mode 100644 trace2/tr2_tls.h\n\n\nbase-commit: b5101f929789889c2e536d915698f58d5c5c6b7a\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-108%2Fjeffhostetler%2Fcore-trace2-2019-v0-v6\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-108/jeffhostetler/core-trace2-2019-v0-v6\nPull-Request: https://github.com/gitgitgadget/git/pull/108\n\nRange-diff vs v5:\n\n  1:  4c006f4995 =  1:  4c006f4995 trace2: Documentation/technical/api-trace2.txt\n  2:  6120ce1bbe !  2:  6bad326bbd trace2: create new combined trace facility\n     @@ -4208,7 +4208,7 @@\n      +#ifndef TR2_TLS_H\n      +#define TR2_TLS_H\n      +\n     -+struct strbuf;\n     ++#include \"strbuf.h\"\n      +\n      +/*\n      + * Arbitry limit for thread names for column alignment.\n  3:  7c987cde86 =  3:  12de7e42de trace2: collect Windows-specific process information\n  4:  cd860799f8 =  4:  5835edbd01 trace2:data: add trace2 regions to wt-status\n  5:  30bcea9435 =  5:  99d13ef478 trace2:data: add editor/pager child classification\n  6:  c869de8063 =  6:  d7ce85b702 trace2:data: add trace2 sub-process classification\n  7:  3cd525b80e =  7:  b70c289903 trace2:data: add trace2 transport child classification\n  8:  c2c1ea5ce3 =  8:  2a0da88579 trace2:data: add trace2 hook classification\n  9:  57b23542b3 =  9:  bca6a7f0d6 trace2:data: add trace2 instrumentation to index read/write\n 10:  ab74ef5f23 = 10:  68269ee060 trace2:data: pack-objects: add trace2 regions\n 11:  6a82426a83 = 11:  0191283fff trace2:data: add subverb to checkout command\n 12:  5c7f0de228 = 12:  24d8d8d768 trace2:data: add subverb to reset command\n 13:  cc61201061 = 13:  90cec071cf trace2:data: add subverb for rebase\n 14:  00b25da38b = 14:  b9f0c6fd66 trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh\n 15:  5f77c9b633 = 15:  93a25d09a1 trace2: add for_each macros to clang-format\n\n-- \ngitgitgadget\n"},{"id":"368597","messageId":"5835edbd014b126f07c0931615dc63073744aa66.1549473350.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 04/15] trace2:data: add trace2 regions to wt-status","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-06T17:15:56Z","receivedAt":"2019-02-06T17:16:01Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2_region_enter() and trace2_region_leave() calls around the\nvarious phases of a status scan.  This gives elapsed time for each\nphase in the GIT_TR2_PERF and GIT_TR2_EVENT trace target.\n\nAlso, these Trace2 calls now use s->repo rather than the_repository.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n wt-status.c | 24 ++++++++++++++++++++++--\n 1 file changed, 22 insertions(+), 2 deletions(-)\n\ndiff --git a/wt-status.c b/wt-status.c\nindex 0fe3bcd4cd..434636850e 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -748,12 +748,23 @@ static int has_unmerged(struct wt_status *s)\n \n void wt_status_collect(struct wt_status *s)\n {\n+\ttrace2_region_enter(\"status\", \"worktrees\", s->repo);\n \twt_status_collect_changes_worktree(s);\n-\tif (s->is_initial)\n+\ttrace2_region_leave(\"status\", \"worktrees\", s->repo);\n+\n+\tif (s->is_initial) {\n+\t\ttrace2_region_enter(\"status\", \"initial\", s->repo);\n \t\twt_status_collect_changes_initial(s);\n-\telse\n+\t\ttrace2_region_leave(\"status\", \"initial\", s->repo);\n+\t} else {\n+\t\ttrace2_region_enter(\"status\", \"index\", s->repo);\n \t\twt_status_collect_changes_index(s);\n+\t\ttrace2_region_leave(\"status\", \"index\", s->repo);\n+\t}\n+\n+\ttrace2_region_enter(\"status\", \"untracked\", s->repo);\n \twt_status_collect_untracked(s);\n+\ttrace2_region_leave(\"status\", \"untracked\", s->repo);\n \n \twt_status_get_state(s->repo, &s->state, s->branch && !strcmp(s->branch, \"HEAD\"));\n \tif (s->state.merge_in_progress && !has_unmerged(s))\n@@ -2291,6 +2302,13 @@ static void wt_porcelain_v2_print(struct wt_status *s)\n \n void wt_status_print(struct wt_status *s)\n {\n+\ttrace2_data_intmax(\"status\", s->repo, \"count/changed\", s->change.nr);\n+\ttrace2_data_intmax(\"status\", s->repo, \"count/untracked\",\n+\t\t\t   s->untracked.nr);\n+\ttrace2_data_intmax(\"status\", s->repo, \"count/ignored\", s->ignored.nr);\n+\n+\ttrace2_region_enter(\"status\", \"print\", s->repo);\n+\n \tswitch (s->status_format) {\n \tcase STATUS_FORMAT_SHORT:\n \t\twt_shortstatus_print(s);\n@@ -2309,6 +2327,8 @@ void wt_status_print(struct wt_status *s)\n \t\twt_longstatus_print(s);\n \t\tbreak;\n \t}\n+\n+\ttrace2_region_leave(\"status\", \"print\", s->repo);\n }\n \n /**\n-- \ngitgitgadget\n\n"},{"id":"368599","messageId":"4c006f4995e8c4c1356d9d93b6352455069e4b6d.1549473350.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 01/15] trace2: Documentation/technical/api-trace2.txt","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-06T17:15:53Z","receivedAt":"2019-02-06T17:16:03Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreated design document for Trace2 feature.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Documentation/technical/api-trace2.txt | 1347 ++++++++++++++++++++++++\n 1 file changed, 1347 insertions(+)\n create mode 100644 Documentation/technical/api-trace2.txt\n\ndiff --git a/Documentation/technical/api-trace2.txt b/Documentation/technical/api-trace2.txt\nnew file mode 100644\nindex 0000000000..6ee574a86e\n--- /dev/null\n+++ b/Documentation/technical/api-trace2.txt\n@@ -0,0 +1,1347 @@\n+= Trace2 API\n+\n+The Trace2 API can be used to print debug, performance, and telemetry\n+information to stderr or a file.  The Trace2 feature is inactive unless\n+explicitly enabled by enabling one or more Trace2 Targets.\n+\n+The Trace2 API is intended to replace the existing (Trace1)\n+printf-style tracing provided by the existing `GIT_TRACE` and\n+`GIT_TRACE_PERFORMANCE` facilities.  During initial implementation,\n+Trace2 and Trace1 may operate in parallel.\n+\n+The Trace2 API defines a set of high-level messages with known fields,\n+such as (`start`: `argv`) and (`exit`: {`exit-code`, `elapsed-time`}).\n+\n+Trace2 instrumentation throughout the Git code base sends Trace2\n+messages to the enabled Trace2 Targets.  Targets transform these\n+messages content into purpose-specific formats and write events to\n+their data streams.  In this manner, the Trace2 API can drive\n+many different types of analysis.\n+\n+Targets are defined using a VTable allowing easy extension to other\n+formats in the future.  This might be used to define a binary format,\n+for example.\n+\n+== Trace2 Targets\n+\n+Trace2 defines the following set of Trace2 Targets.\n+Format details are given in a later section.\n+\n+`GIT_TR2` (NORMAL)::\n+\n+\ta simple printf format like GIT_TRACE.\n++\n+------------\n+$ export GIT_TR2=~/log.normal\n+$ git version\n+git version 2.20.1.155.g426c96fcdb\n+------------\n++\n+------------\n+$ cat ~/log.normal\n+12:28:42.620009 common-main.c:38                  version 2.20.1.155.g426c96fcdb\n+12:28:42.620989 common-main.c:39                  start git version\n+12:28:42.621101 git.c:432                         cmd_name version (version)\n+12:28:42.621215 git.c:662                         exit elapsed:0.001227 code:0\n+12:28:42.621250 trace2/tr2_tgt_normal.c:124       atexit elapsed:0.001265 code:0\n+------------\n+\n+`GIT_TR2_PERF` (PERF)::\n+\n+\ta column-based format to replace GIT_TRACE_PERFORMANCE suitable for\n+\tdevelopment and testing, possibly to complement tools like gprof.\n++\n+------------\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git version\n+git version 2.20.1.155.g426c96fcdb\n+------------\n++\n+------------\n+$ cat ~/log.perf\n+12:28:42.620675 common-main.c:38                  | d0 | main                     | version      |     |           |           |            | 2.20.1.155.g426c96fcdb\n+12:28:42.621001 common-main.c:39                  | d0 | main                     | start        |     |           |           |            | git version\n+12:28:42.621111 git.c:432                         | d0 | main                     | cmd_name     |     |           |           |            | version (version)\n+12:28:42.621225 git.c:662                         | d0 | main                     | exit         |     |  0.001227 |           |            | code:0\n+12:28:42.621259 trace2/tr2_tgt_perf.c:211         | d0 | main                     | atexit       |     |  0.001265 |           |            | code:0\n+------------\n+\n+`GIT_TR2_EVENT` (EVENT)::\n+\n+\ta JSON-based format of event data suitable for telemetry analysis.\n++\n+------------\n+$ export GIT_TR2_EVENT=~/log.event\n+$ git version\n+git version 2.20.1.155.g426c96fcdb\n+------------\n++\n+------------\n+$ cat ~/log.event\n+{\"event\":\"version\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.620713\",\"file\":\"common-main.c\",\"line\":38,\"evt\":\"1\",\"exe\":\"2.20.1.155.g426c96fcdb\"}\n+{\"event\":\"start\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621027\",\"file\":\"common-main.c\",\"line\":39,\"argv\":[\"git\",\"version\"]}\n+{\"event\":\"cmd_name\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621122\",\"file\":\"git.c\",\"line\":432,\"name\":\"version\",\"hierarchy\":\"version\"}\n+{\"event\":\"exit\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621236\",\"file\":\"git.c\",\"line\":662,\"t_abs\":0.001227,\"code\":0}\n+{\"event\":\"atexit\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621268\",\"file\":\"trace2/tr2_tgt_event.c\",\"line\":163,\"t_abs\":0.001265,\"code\":0}\n+------------\n+\n+== Enabling a Target\n+\n+A Trace2 Target is enabled when the corresponding environment variable\n+(`GIT_TR2`, `GIT_TR2_PERF`, or `GIT_TR2_EVENT`) is set.  The following\n+values are recognized.\n+\n+`0`::\n+`false`::\n+\n+\tDisables the target.\n+\n+`1`::\n+`true`::\n+\n+\tEnables the target and writes stream to `STDERR`.\n+\n+`[2-9]`::\n+\n+\tEnables the target and writes to the already opened file descriptor.\n+\n+`<absolute-pathname>`::\n+\n+\tEnables the target, opens and writes to the file in append mode.\n+\n+`af_unix:<absolute-pathname>`::\n+\n+\tEnables the target, opens and writes to a Unix Domain Socket\n+\t(on platforms that support them).\n+\n+== Trace2 API\n+\n+All public Trace2 functions and macros are defined in `trace2.h` and\n+`trace2.c`.  All public symbols are prefixed with `trace2_`.\n+\n+There are no public Trace2 data structures.\n+\n+The Trace2 code also defines a set of private functions and data types\n+in the `trace2/` directory.  These symbols are prefixed with `tr2_`\n+and should only be used by functions in `trace2.c`.\n+\n+== Conventions for Public Functions and Macros\n+\n+The functions defined by the Trace2 API are declared and documented\n+in `trace2.h`.  It defines the API functions and wrapper macros for\n+Trace2.\n+\n+Some functions have a `_fl()` suffix to indicate that they take `file`\n+and `line-number` arguments.\n+\n+Some functions have a `_va_fl()` suffix to indicate that they also\n+take a `va_list` argument.\n+\n+Some functions have a `_printf_fl()` suffix to indicate that they also\n+take a varargs argument.\n+\n+There are CPP wrapper macros and ifdefs to hide most of these details.\n+See `trace2.h` for more details.  The following discussion will only\n+describe the simplified forms.\n+\n+== Public API\n+\n+All Trace2 API functions send a messsage to all of the active\n+Trace2 Targets.  This section describes the set of available\n+messages.\n+\n+It helps to divide these functions into groups for discussion\n+purposes.\n+\n+=== Basic Command Messages\n+\n+These are concerned with the lifetime of the overall git process.\n+\n+`void trace2_initialize()`::\n+\n+\tDetermines if any Trace2 Targets should be enabled and\n+\tinitializes the Trace2 facility.  This includes starting the\n+\telapsed time clocks and thread local storage (TLS).\n++\n+This function emits a \"version\" message containing the version of git\n+and the Trace2 protocol.\n++\n+This function should be called from `main()` as early as possible in\n+the life of the process.\n+\n+`int trace2_is_enabled()`::\n+\n+\tReturns 1 if Trace2 is enabled (at least one target is\n+\tactive).\n+\n+`void trace2_cmd_start(int argc, const char **argv)`::\n+\n+\tEmits a \"start\" message containing the process command line\n+\targuments.\n+\n+`int trace2_cmd_exit(int exit_code)`::\n+\n+\tEmits an \"exit\" message containing the process exit-code and\n+\telapsed time.\n++\n+Returns the exit-code.\n+\n+`void trace2_cmd_error(const char *fmt, va_list ap)`::\n+\n+\tEmits an \"error\" message containing a formatted error message.\n+\n+`void trace2_cmd_path(const char *pathname)`::\n+\n+\tEmits a \"cmd_path\" message with the full pathname of the\n+\tcurrent process.\n+\n+=== Command Detail Messages\n+\n+These are concerned with describing the specific Git command\n+after the command line, config, and environment are inspected.\n+\n+`void trace2_cmd_name(const char *name)`::\n+\n+\tEmits a \"cmd_name\" message with the canonical name of the\n+\tcommand, for example \"status\" or \"checkout\".\n+\n+`void trace2_cmd_mode(const char *mode)`::\n+\n+\tEmits a \"cmd_mode\" message with a qualifier name to further\n+\tdescribe the current git command.\n++\n+This message is intended to be used with git commands having multiple\n+major modes.  For example, a \"checkout\" command can checkout a new\n+branch or it can checkout a single file, so the checkout code could\n+emit a cmd_mode message of \"branch\" or \"file\".\n+\n+`void trace2_cmd_alias(const char *alias, const char **argv_expansion)`::\n+\n+\tEmits an \"alias\" message containing the alias used and the\n+\targument expansion.\n+\n+`void trace2_def_param(const char *parameter, const char *value)`::\n+\n+\tEmits a \"def_param\" message containing a key/value pair.\n++\n+This message is intended to report some global aspect of the current\n+command, such as a configuration setting or command line switch that\n+significantly affects program performance or behavior, such as\n+`core.abbrev`, `status.showUntrackedFiles`, or `--no-ahead-behind`.\n+\n+`void trace2_cmd_list_config()`::\n+\n+\tEmits a \"def_param\" messages for \"important\" configuration\n+\tsettings.\n++\n+The environment variable `GIT_TR2_CONFIG_PARAMS` can be set to a\n+list of patterns of important configuration settings, for example:\n+`core.*,remote.*.url`.  This function will iterate over all config\n+settings and emit a \"def_param\" message for each match.\n+\n+`void trace2_cmd_set_config(const char *key, const char *value)`::\n+\n+\tEmits a \"def_param\" message for a specific configuration\n+\tsetting IFF it matches the `GIT_TR2_CONFIG_PARAMS` pattern.\n++\n+This is used to hook into `git_config_set()` and catch any\n+configuration changes and update a value previously reported by\n+`trace2_cmd_list_config()`.\n+\n+`void trace2_def_repo(struct repository *repo)`::\n+\n+\tRegisters a repository with the Trace2 layer.  Assigns a\n+\tunique \"repo-id\" to `repo->trace2_repo_id`.\n++\n+Emits a \"worktree\" messages containing the repo-id and the worktree\n+pathname.\n++\n+Region and data messages (described later) may refer to this repo-id.\n++\n+The main/top-level repository will have repo-id value 1 (aka \"r1\").\n++\n+The repo-id field is in anticipation of future in-proc submodule\n+repositories.\n+\n+=== Child Process Messages\n+\n+These are concerned with the various spawned child processes,\n+including shell scripts, git commands, editors, pagers, and hooks.\n+\n+`void trace2_child_start(struct child_process *cmd)`::\n+\n+\tEmits a \"child_start\" message containing the \"child-id\",\n+\t\"child-argv\", and \"child-classification\".\n++\n+Before calling this, set `cmd->trace2_child_class` to a name\n+describing the type of child process, for example \"editor\".\n++\n+This function assigns a unique \"child-id\" to `cmd->trace2_child_id`.\n+This field is used later during the \"child_exit\" message to associate\n+it with the \"child_start\" message.\n++\n+This function should be called before spawning the child process.\n+\n+`void trace2_child_exit(struct child_proess *cmd, int child_exit_code)`::\n+\n+\tEmits a \"child_exit\" message containing the \"child-id\",\n+\tthe child's elapsed time and exit-code.\n++\n+The reported elapsed time includes the process creation overhead and\n+time spend waiting for it to exit, so it may be slightly longer than\n+the time reported by the child itself.\n++\n+This function should be called after reaping the child process.\n+\n+`int trace2_exec(const char *exe, const char **argv)`::\n+\n+\tEmits a \"exec\" message containing the \"exec-id\" and the\n+\targv of the new process.\n++\n+This function should be called before calling one of the `exec()`\n+variants, such as `execvp()`.\n++\n+This function returns a unique \"exec-id\".  This value is used later\n+if the exec() fails and a \"exec-result\" message is necessary.\n+\n+`void trace2_exec_result(int exec_id, int error_code)`::\n+\n+\tEmits a \"exec_result\" message containing the \"exec-id\"\n+\tand the error code.\n++\n+On Unix-based systems, `exec()` does not return if successful.\n+This message is used to indicate that the `exec()` failed and\n+that the current program is continuing.\n+\n+=== Git Thread Messages\n+\n+These messages are concerned with Git thread usage.\n+\n+`void trace2_thread_start(const char *thread_name)`::\n+\n+\tEmits a \"thread_start\" message.\n++\n+The `thread_name` field should be a descriptive name, such as the\n+unique name of the thread-proc.  A unique \"thread-id\" will be added\n+to the name to uniquely identify thread instances.\n++\n+Region and data messages (described later) may refer to this thread\n+name.\n++\n+This function must be called by the thread-proc of the new thread\n+(so that TLS data is properly initialized) and not by the caller\n+of `pthread_create()`.\n+\n+`void trace2_thread_exit()`::\n+\n+\tEmits a \"thread_exit\" message containing the thread name\n+\tand the thread elapsed time.\n++\n+This function must be called by the thread-proc before it returns\n+(so that the coorect TLS data is used and cleaned up.  It should\n+not be called by the caller of `pthread_join()`.\n+\n+=== Region and Data Messages\n+\n+These are concerned with recording performance data\n+over regions or spans of code.\n+\n+`void trace2_region_enter(const char *category, const char *label, const struct repository *repo)`::\n+   \n+`void trace2_region_enter_printf(const char *category, const char *label, const struct repository *repo, const char *fmt, ...)`::\n+\n+`void trace2_region_enter_printf_va(const char *category, const char *label, const struct repository *repo, const char *fmt, va_list ap)`::\n+\n+\tEmits a thread-relative \"region_enter\" message with optional\n+\tprintf string.\n++\n+This function pushes a new region nesting stack level on the current\n+thread and starts a clock for the new stack frame.\n++\n+The `category` field is an arbitrary category name used to classify\n+regions by feature area, such as \"status\" or \"index\".  At this time\n+it is only just printed along with the rest of the message.  It may\n+be used in the future to filter messages.\n++\n+The `label` field is an arbitrary label used to describe the activity\n+being started, such as \"read_recursive\" or \"do_read_index\".\n++\n+The `repo` field, if set, will be used to get the \"repo-id\", so that\n+recursive oerations can be attributed to the correct repository.\n+\n+`void trace2_region_leave(const char *category, const char *label, const struct repository *repo)`::\n+\n+`void trace2_region_leave_printf(const char *category, const char *label, const struct repository *repo, const char *fmt, ...)`::\n+\n+`void trace2_region_leave_printf_va(const char *category, const char *label, const struct repository *repo, const char *fmt, va_list ap)`::\n+\n+\tEmits a thread-relative \"region_leave\" message with optional\n+\tprintf string.\n++\n+This function pops the region nesting stack on the current thread\n+and reports the elapsed time of the stack frame.\n++\n+The `category`, `label`, and `repo` fields are the same as above.\n+The `category` and `label` do not need to match the correpsonding\n+\"region_enter\" message, but it makes the data stream easier to\n+understand.\n+\n+`void trace2_data_string(const char *category, const struct repository *repo, const char *key, const char * value)`::\n+\n+`void trace2_data_intmax(const char *category, const struct repository *repo, const char *key, intmax value)`::\n+\n+`void trace2_data_json(const char *category, const struct repository *repo, const char *key, const struct json_writer *jw)`::\n+\n+\tEmits a region- and thread-relative \"data\" or \"data_json\" message.\n++\n+This is a key/value pair message containing information about the\n+current thread, region stack, and repository.  This could be used\n+to print the number of files in a directory during a multi-threaded\n+recursive tree walk.\n+\n+`void trace2_printf(const char *fmt, ...)`::\n+\n+`void trace2_printf_va(const char *fmt, va_list ap)`::\n+\n+\tEmits a region- and thread-relative \"printf\" message.\n+\n+== Trace2 Target Formats\n+\n+=== NORMAL Format\n+\n+NORMAL format is enabled when the `GIT_TR2` environment variable is\n+set.\n+\n+Events are written as lines of the form:\n+\n+------------\n+[<time> SP <filename>:<line> SP+] <event-name> [[SP] <event-message>] LF\n+------------\n+\n+`<event-name>`::\n+\n+\tis the event name.\n+\n+`<event-message>`::\n+\tis a free-form printf message intended for human consumption.\n++\n+Note that this may contain embedded LF or CRLF characters that are\n+not escaped, so the event may spill across multiple lines.\n+\n+If `GIT_TR2_BRIEF` is true, the `time`, `filename`, and `line` fields\n+are omitted.\n+\n+This target is intended to be more of a summary (like GIT_TRACE) and\n+less detailed than the other targets.  It ignores thread, region, and\n+data messages, for example.\n+\n+=== PERF Format\n+\n+PERF format is enabled when the `GIT_TR2_PERF` environment variable\n+is set.\n+\n+Events are written as lines of the form:\n+\n+------------\n+[<time> SP <filename>:<line> SP+\n+    BAR SP] d<depth> SP\n+    BAR SP <thread-name> SP+\n+    BAR SP <event-name> SP+\n+    BAR SP [r<repo-id>] SP+\n+    BAR SP [<t_abs>] SP+\n+    BAR SP [<t_rel>] SP+\n+    BAR SP [<category>] SP+\n+    BAR SP DOTS* <perf-event-message>\n+    LF\n+------------\n+\n+`<depth>`::\n+\tis the git process depth.  This is the number of parent\n+\tgit processes.  A top-level git command has depth value \"d0\".\n+\tA child of it has depth value \"d1\".  A second level child\n+\thas depth value \"d2\" and so on.\n+\n+`<thread-name>`::\n+\tis a unique name for the thread.  The primary thread\n+\tis called \"main\".  Other thread names are of the form \"th%d:%s\"\n+\tand include a unique number and the name of the thread-proc.\n+\n+`<event-name>`::\n+\tis the event name.\n+\n+`<repo-id>`::\n+\twhen present, is a number indicating the repository\n+\tin use.  A `def_repo` event is emitted when a repository is\n+\topened.  This defines the repo-id and associated worktree.\n+\tSubsequent repo-specific events will reference this repo-id.\n++\n+Currently, this is always \"r1\" for the main repository.\n+This field is in anticipation of in-proc submodules in the future.\n+\n+`<t_abs>`::\n+\twhen present, is the absolute time in seconds since the\n+\tprogram started.\n+\n+`<t_rel>`::\n+\twhen present, is time in seconds relative to the start of\n+\tthe current region.  For a thread-exit event, it is the elapsed\n+\ttime of the thread.\n+\n+`<category>`::\n+\tis present on region and data events and is used to\n+\tindicate a broad category, such as \"index\" or \"status\".\n+\n+`<perf-event-message>`::\n+\tis a free-form printf message intended for human consumption.\n+\n+------------\n+15:33:33.532712 wt-status.c:2310                  | d0 | main                     | region_enter | r1  |  0.126064 |           | status     | label:print\n+15:33:33.532712 wt-status.c:2331                  | d0 | main                     | region_leave | r1  |  0.127568 |  0.001504 | status     | label:print\n+------------\n+\n+If `GIT_TR2_PERF_BRIEF` is true, the `time`, `file`, and `line`\n+fields are omitted.\n+\n+------------\n+d0 | main                     | region_leave | r1  |  0.011717 |  0.009122 | index      | label:preload\n+------------\n+\n+The PERF target is intended for interactive performance analysis\n+during development and is quite noisy.\n+\n+=== EVENT Format\n+\n+EVENT format is enabled when the `GIT_TR2_EVENT` environment\n+variable is set.\n+\n+Each event is a JSON-object containing multiple key/value pairs\n+written as a single line and followed by a LF.\n+\n+------------\n+'{' <key> ':' <value> [',' <key> ':' <value>]* '}' LF\n+------------\n+\n+Some key/value pairs are common to all events and some are\n+event-specific.\n+\n+==== Common Key/Value Pairs\n+\n+The following key/value pairs are common to all events:\n+\n+------------\n+{\n+\t\"event\":\"version\",\n+\t\"sid\":\"1547659722619736-11614\",\n+\t\"thread\":\"main\",\n+\t\"time\":\"2019-01-16 17:28:42.620713\",\n+\t\"file\":\"common-main.c\",\n+\t\"line\":38,\n+\t...\n+}\n+------------\n+\n+`\"event\":<event>`::\n+\tis the event name.\n+\n+`\"sid\":<sid>`::\n+\tis the session-id.  This is a unique string to identify the\n+\tprocess instance to allow all events emitted by a process to\n+\tbe identified.  A session-id is used instead of a PID because\n+\tPIDs are recycled by the OS.  For child git processes, the\n+\tsession-id is prepended with the session-id of the parent git\n+\tprocess to allow parent-child relationships to be identified\n+\tduring post-processing.\n+\n+`\"thread\":<thread>`::\n+\tis the thread name.\n+\n+`\"time\":<time>`::\n+\tis the UTC time of the event.\n+\n+`\"file\":<filename>`::\n+\tis source file generating the event.\n+\n+`\"line\":<line-number>`::\n+\tis the integer source line number generating the event.\n+\n+`\"repo\":<repo-id>`::\n+\twhen present, is the integer repo-id as described previously.\n+\n+If `GIT_TR2_EVENT_BRIEF` is true, the `file` and `line` fields are omitted\n+from all events and the `time` field is only present on the \"start\" and\n+\"atexit\" events.\n+\n+==== Event-Specific Key/Value Pairs\n+\n+`\"version\"`::\n+\tThis event gives the version of the executable and the EVENT format.\n++\n+------------\n+{\n+\t\"event\":\"version\",\n+\t...\n+\t\"evt\":\"1\",\t\t       # EVENT format version\n+\t\"exe\":\"2.20.1.155.g426c96fcdb\" # git version\n+}\n+------------\n+\n+`\"start\"`::\n+\tThis event contains the complete argv received by main().\n++\n+------------\n+{\n+\t\"event\":\"start\",\n+\t...\n+\t\"argv\":[\"git\",\"version\"]\n+}\n+------------\n+\n+`\"exit\"`::\n+\tThis event is emitted when git calls `exit()`.\n++\n+------------\n+{\n+\t\"event\":\"exit\",\n+\t...\n+\t\"t_abs\":0.001227, # elapsed time in seconds\n+\t\"code\":0\t  # exit code\n+}\n+------------\n+\n+`\"atexit\"`::\n+\tThis event is emitted by the Trace2 `atexit` routine during\n+\tfinal shutdown.  It should be the last event emitted by the\n+\tprocess.\n++\n+(The elapsed time reported here is greater than the time reported in\n+the \"exit\" event because it runs after all other atexit tasks have\n+completed.)\n++\n+------------\n+{\n+\t\"event\":\"atexit\",\n+\t...\n+\t\"t_abs\":0.001227, # elapsed time in seconds\n+\t\"code\":0          # exit code\n+}\n+------------\n+\n+`\"signal\"`::\n+\tThis event is emitted when the program is terminated by a user\n+\tsignal.  Depending on the platform, the signal event may\n+\tprevent the \"atexit\" event from being generated.\n++\n+------------\n+{\n+\t\"event\":\"signal\",\n+\t...\n+\t\"t_abs\":0.001227,  # elapsed time in seconds\n+\t\"signal\":13        # SIGTERM, SIGINT, etc.\n+}\n+------------\n+\n+`\"error\"`::\n+\tThis event is emitted when one of the `error()`, `die()`,\n+\tor `usage()` functions are called.\n++\n+------------\n+{\n+\t\"event\":\"error\",\n+\t...\n+\t\"msg\":\"invalid option: --cahced\", # formatted error message\n+\t\"fmt\":\"invalid option: %s\"\t  # error format string\n+}\n+------------\n++\n+The error event may be emitted more than once.  The format string\n+allows post-processors to group errors by type without worrying\n+about specific error arguments.\n+\n+`\"cmd_path\"`::\n+\tThis event contains the discovered full path of the git\n+\texecutable (on platforms that are configured to resolve it).\n++\n+------------\n+{\n+\t\"event\":\"cmd_path\",\n+\t...\n+\t\"path\":\"C:/work/gfw/git.exe\"\n+}\n+------------\n+\n+`\"cmd_name\"`::\n+\tThis event contains the command name for this git process\n+\tand the hierarchy of commands from parent git processes.\n++\n+------------\n+{\n+\t\"event\":\"cmd_name\",\n+\t...\n+\t\"name\":\"pack-objects\",\n+\t\"hierarchy\":\"push/pack-objects\"\n+}\n+------------\n++\n+Normally, the \"name\" field contains the canonical name of the\n+command.  When a canonical name is not available, one of\n+these special values are used:\n++\n+------------\n+\"_query_\"            # \"git --html-path\"\n+\"_run_dashed_\"       # when \"git foo\" tries to run \"git-foo\"\n+\"_run_shell_alias_\"  # alias expansion to a shell command\n+\"_run_git_alias_\"    # alias expansion to a git command\n+\"_usage_\"            # usage error\n+------------\n+\n+`\"cmd_mode\"`::\n+\tThis event, when present, describes the command variant This\n+\tevent may be emitted more than once.\n++\n+------------\n+{\n+\t\"event\":\"cmd_mode\",\n+\t...\n+\t\"name\":\"branch\"\n+}\n+------------\n++\n+The \"name\" field is an arbitrary string to describe the command mode.\n+For example, checkout can checkout a branch or an individual file.\n+And these variations typically have different performance\n+characteristics that are not comparable.\n+\n+`\"alias\"`::\n+\tThis event is present when an alias is expanded.\n++\n+------------\n+{\n+\t\"event\":\"alias\",\n+\t...\n+\t\"alias\":\"l\",\t\t # registered alias\n+\t\"argv\":[\"log\",\"--graph\"] # alias expansion\n+}\n+------------\n+\n+`\"child_start\"`::\n+\tThis event describes a child process that is about to be\n+\tspawned.\n++\n+------------\n+{\n+\t\"event\":\"child_start\",\n+\t...\n+\t\"child_id\":2,\n+\t\"child_class\":\"?\",\n+\t\"use_shell\":false,\n+\t\"argv\":[\"git\",\"rev-list\",\"--objects\",\"--stdin\",\"--not\",\"--all\",\"--quiet\"]\n+\n+\t\"hook_name\":\"<hook_name>\"  # present when child_class is \"hook\"\n+\t\"cd\":\"<path>\"\t\t   # present when cd is required\n+}\n+------------\n++\n+The \"child_id\" field can be used to match this child_start with the\n+corresponding child_exit event.\n++\n+The \"child_class\" field is a rough classification, such as \"editor\",\n+\"pager\", \"transport/*\", and \"hook\".  Unclassified children are classified\n+with \"?\".\n+\n+`\"child_exit\"`::\n+\tThis event is generated after the current process has returned\n+\tfrom the waitpid() and collected the exit information from the\n+\tchild.\n++\n+------------\n+{\n+\t\"event\":\"child_exit\",\n+\t...\n+\t\"child_id\":2,\n+\t\"pid\":14708,\t # child PID\n+\t\"code\":0,\t # child exit-code\n+\t\"t_rel\":0.110605 # observed run-time of child process\n+}\n+------------\n++\n+Note that the session-id of the child process is not available to\n+the current/spawning process, so the child's PID is reported here as\n+a hint for post-processing.  (But it is only a hint because the child\n+proces may be a shell script which doesn't have a session-id.)\n++\n+Note that the `t_rel` field contains the observed run time in seconds\n+for the child process (starting before the fork/exec/spawn and\n+stopping after the waitpid() and includes OS process creation overhead).\n+So this time will be slightly larger than the atexit time reported by\n+the child process itself.\n+\n+`\"exec\"`::\n+\tThis event is generated before git attempts to `exec()`\n+\tanother command rather than starting a child process.\n++\n+------------\n+{\n+\t\"event\":\"exec\",\n+\t...\n+\t\"exec_id\":0,\n+\t\"exe\":\"git\",\n+\t\"argv\":[\"foo\", \"bar\"]\n+}\n+------------\n++\n+The \"exec_id\" field is a command-unique id and is only useful if the\n+`exec()` fails and a corresponding exec_result event is generated.\n+\n+`\"exec_result\"`::\n+\tThis event is generated if the `exec()` fails and control\n+\treturns to the current git command.\n++\n+------------\n+{\n+\t\"event\":\"exec_result\",\n+\t...\n+\t\"exec_id\":0,\n+\t\"code\":1      # error code (errno) from exec()\n+}\n+------------\n+\n+`\"thread_start\"`::\n+\tThis event is generated when a thread is started.  It is\n+\tgenerated from *within* the new thread's thread-proc (for TLS\n+\treasons).\n++\n+------------\n+{\n+\t\"event\":\"thread_start\",\n+\t...\n+\t\"thread\":\"th02:preload_thread\" # thread name\n+}\n+------------\n+\n+`\"thread_exit\"`::\n+\tThis event is generated when a thread exits.  It is generated\n+\tfrom *within* the thread's thread-proc (for TLS reasons).\n++\n+------------\n+{\n+\t\"event\":\"thread_exit\",\n+\t...\n+\t\"thread\":\"th02:preload_thread\", # thread name\n+\t\"t_rel\":0.007328                # thread elapsed time\n+}\n+------------\n+\n+`\"def_param\"`::\n+\tThis event is generated to log a global parameter.\n++\n+------------\n+{\n+\t\"event\":\"def_param\",\n+\t...\n+\t\"param\":\"core.abbrev\",\n+\t\"value\":\"7\"\n+}\n+------------\n+\n+`\"def_repo\"`::\n+\tThis event defines a repo-id and associates it with the root\n+\tof the worktree.\n++\n+------------\n+{\n+\t\"event\":\"def_repo\",\n+\t...\n+\t\"repo\":1,\n+\t\"worktree\":\"/Users/jeffhost/work/gfw\"\n+}\n+------------\n++\n+As stated earlier, the repo-id is currently always 1, so there will\n+only be one def_repo event.  Later, if in-proc submodules are\n+supported, a def_repo event should be emitted for each submodule\n+visited.\n+\n+`\"region_enter\"`::\n+\tThis event is generated when entering a region.\n++\n+------------\n+{\n+\t\"event\":\"region_enter\",\n+\t...\n+\t\"repo\":1,                # optional\n+\t\"nesting\":1,             # current region stack depth\n+\t\"category\":\"index\",      # optional\n+\t\"label\":\"do_read_index\", # optional\n+\t\"msg\":\".git/index\"       # optional\n+}\n+------------\n++\n+The `category` field may be used in a future enhancement to\n+do category-based filtering.\n++\n+The `GIT_TR2_EVENT_NESTING` environment variable can be used to\n+filter deeply nested regions and data events.  It defaults to \"2\".\n+\n+`\"region_leave\"`::\n+\tThis event is generated when leaving a region.\n++\n+------------\n+{\n+\t\"event\":\"region_leave\",\n+\t...\n+\t\"repo\":1,                # optional\n+\t\"t_rel\":0.002876,        # time spent in region in seconds\n+\t\"nesting\":1,             # region stack depth\n+\t\"category\":\"index\",      # optional\n+\t\"label\":\"do_read_index\", # optional\n+\t\"msg\":\".git/index\"       # optional\n+}\n+------------\n+\n+`\"data\"`::\n+\tThis event is generated to log a thread- and region-local\n+\tkey/value pair.\n++\n+------------\n+{\n+\t\"event\":\"data\",\n+\t...\n+\t\"repo\":1,              # optional\n+\t\"t_abs\":0.024107,      # absolute elapsed time\n+\t\"t_rel\":0.001031,      # elapsed time in region/thread\n+\t\"nesting\":2,           # region stack depth\n+\t\"category\":\"index\",\n+\t\"key\":\"read/cache_nr\",\n+\t\"value\":\"3552\"\n+}\n+------------\n++\n+The \"value\" field may be an integer or a string.\n+\n+`\"data-json\"`::\n+\tThis event is generated to log a pre-formatted JSON string\n+\tcontaining structured data.\n++\n+------------\n+{\n+\t\"event\":\"data_json\",\n+\t...\n+\t\"repo\":1,              # optional\n+\t\"t_abs\":0.015905,\n+\t\"t_rel\":0.015905,\n+\t\"nesting\":1,\n+\t\"category\":\"process\",\n+\t\"key\":\"windows/ancestry\",\n+\t\"value\":[\"bash.exe\",\"bash.exe\"]\n+}\n+------------\n+\n+== Example Trace2 API Usage\n+\n+Here is a hypothetical usage of the Trace2 API showing the intended\n+usage (without worrying about the actual Git details).\n+\n+Initialization::\n+\n+\tInitialization happens in `main()`.  Behind the scenes, an\n+\t`atexit` and `signal` handler are registered.\n++\n+----------------\n+int main(int argc, const char **argv)\n+{\n+\tint exit_code;\n+\n+\ttrace2_initialize();\n+\ttrace2_cmd_start(argv);\n+\n+\texit_code = cmd_main(argc, argv);\n+\n+\ttrace2_cmd_exit(exit_code);\n+\n+\treturn exit_code;\n+}\n+----------------\n+\n+Command Details::\n+\n+\tAfter the basics are established, additional command\n+\tinformation can be sent to Trace2 as it is discovered.\n++\n+----------------\n+int cmd_checkout(int argc, const char **argv)\n+{\n+\ttrace2_cmd_name(\"checkout\");\n+\ttrace2_cmd_mode(\"branch\");\n+\ttrace2_def_repo(the_repository);\n+\n+\t// emit \"def_param\" messages for \"interesting\" config settings.\n+\ttrace2_cmd_list_config();\n+\n+\tif (do_something())\n+\t    trace2_cmd_error(\"Path '%s': cannot do something\", path);\n+\n+\treturn 0;\n+}\n+----------------\n+\n+Child Processes::\n+\n+\tWrap code spawning child processes.\n++\n+----------------\n+void run_child(...)\n+{\n+\tint child_exit_code;\n+\tstruct child_process cmd = CHILD_PROCESS_INIT;\n+\t...\n+\tcmd.trace2_child_class = \"editor\";\n+\n+\ttrace2_child_start(&cmd);\n+\tchild_exit_code = spawn_child_and_wait_for_it();\n+\ttrace2_child_exit(&cmd, child_exit_code);\n+}\n+----------------\n++\n+For example, the following fetch command spawned ssh, index-pack,\n+rev-list, and gc.  This example also shows that fetch took\n+5.199 seconds and of that 4.932 was in ssh.\n++\n+----------------\n+$ export GIT_TR2_BRIEF=1\n+$ export GIT_TR2=~/log.normal\n+$ git fetch origin\n+...\n+----------------\n++\n+----------------\n+$ cat ~/log.normal\n+version 2.20.1.vfs.1.1.47.g534dbe1ad1\n+start git fetch origin\n+worktree /Users/jeffhost/work/gfw\n+cmd_name fetch (fetch)\n+child_start[0] ssh git@github.com ...\n+child_start[1] git index-pack ...\n+... (Trace2 events from child processes omitted)\n+child_exit[1] pid:14707 code:0 elapsed:0.076353\n+child_exit[0] pid:14706 code:0 elapsed:4.931869\n+child_start[2] git rev-list ...\n+... (Trace2 events from child process omitted)\n+child_exit[2] pid:14708 code:0 elapsed:0.110605\n+child_start[3] git gc --auto\n+... (Trace2 events from child process omitted)\n+child_exit[3] pid:14709 code:0 elapsed:0.006240\n+exit elapsed:5.198503 code:0\n+atexit elapsed:5.198541 code:0\n+----------------\n++\n+When a git process is a (direct or indirect) child of another\n+git process, it inherits Trace2 context information.  This\n+allows the child to print the command hierarchy.  This example\n+shows gc as child[3] of fetch.  When the gc process reports\n+its name as \"gc\", it also reports the hierarchy as \"fetch/gc\".\n+(In this example, trace2 messages from the child process is\n+indented for clarity.)\n++\n+----------------\n+$ export GIT_TR2_BRIEF=1\n+$ export GIT_TR2=~/log.normal\n+$ git fetch origin\n+...\n+----------------\n++\n+----------------\n+$ cat ~/log.normal\n+version 2.20.1.160.g5676107ecd.dirty\n+start git fetch official\n+worktree /Users/jeffhost/work/gfw\n+cmd_name fetch (fetch)\n+...\n+child_start[3] git gc --auto\n+    version 2.20.1.160.g5676107ecd.dirty\n+    start /Users/jeffhost/work/gfw/git gc --auto\n+    worktree /Users/jeffhost/work/gfw\n+    cmd_name gc (fetch/gc)\n+    exit elapsed:0.001959 code:0\n+    atexit elapsed:0.001997 code:0\n+child_exit[3] pid:20303 code:0 elapsed:0.007564\n+exit elapsed:3.868938 code:0\n+atexit elapsed:3.868970 code:0\n+----------------\n+\n+Regions::\n+\n+\tRegions can be use to time an interesting section of code.\n++\n+----------------\n+void wt_status_collect(struct wt_status *s)\n+{\n+\ttrace2_region_enter(\"status\", \"worktrees\", s->repo);\n+\twt_status_collect_changes_worktree(s);\n+\ttrace2_region_leave(\"status\", \"worktrees\", s->repo);\n+\n+\ttrace2_region_enter(\"status\", \"index\", s->repo);\n+\twt_status_collect_changes_index(s);\n+\ttrace2_region_leave(\"status\", \"index\", s->repo);\n+\n+\ttrace2_region_enter(\"status\", \"untracked\", s->repo);\n+\twt_status_collect_untracked(s);\n+\ttrace2_region_leave(\"status\", \"untracked\", s->repo);\n+}\n+\n+void wt_status_print(struct wt_status *s)\n+{\n+\ttrace2_region_enter(\"status\", \"print\", s->repo);\n+\tswitch (s->status_format) {\n+\t    ...\n+\t}\n+\ttrace2_region_leave(\"status\", \"print\", s->repo);\n+}\n+----------------\n++\n+In this example, scanning for untracked files ran from +0.012568 to\n++0.027149 (since the process started) and took 0.014581 seconds.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+\n+$ cat ~/log.perf\n+d0 | main                     | version      |     |           |           |            | 2.20.1.160.g5676107ecd.dirty\n+d0 | main                     | start        |     |           |           |            | git status\n+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n+d0 | main                     | cmd_name     |     |           |           |            | status (status)\n+...\n+d0 | main                     | region_enter | r1  |  0.010988 |           | status     | label:worktrees\n+d0 | main                     | region_leave | r1  |  0.011236 |  0.000248 | status     | label:worktrees\n+d0 | main                     | region_enter | r1  |  0.011260 |           | status     | label:index\n+d0 | main                     | region_leave | r1  |  0.012542 |  0.001282 | status     | label:index\n+d0 | main                     | region_enter | r1  |  0.012568 |           | status     | label:untracked\n+d0 | main                     | region_leave | r1  |  0.027149 |  0.014581 | status     | label:untracked\n+d0 | main                     | region_enter | r1  |  0.027411 |           | status     | label:print\n+d0 | main                     | region_leave | r1  |  0.028741 |  0.001330 | status     | label:print\n+d0 | main                     | exit         |     |  0.028778 |           |            | code:0\n+d0 | main                     | atexit       |     |  0.028809 |           |            | code:0\n+----------------\n++\n+Regions may be nested.  This causes messages to be indented in the\n+PERF target, for example.\n+Elapsed times are relative to the start of the correpsonding nesting\n+level as expected.  For example, if we add region message to:\n++\n+----------------\n+static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n+\tstruct index_state *istate, const char *base, int baselen,\n+\tstruct untracked_cache_dir *untracked, int check_only,\n+\tint stop_at_first_file, const struct pathspec *pathspec)\n+{\n+\tenum path_treatment state, subdir_state, dir_state = path_none;\n+\n+\ttrace2_region_enter_printf(\"dir\", \"read_recursive\", NULL, \"%.*s\", baselen, base);\n+\t...\n+\ttrace2_region_leave_printf(\"dir\", \"read_recursive\", NULL, \"%.*s\", baselen, base);\n+\treturn dir_state;\n+}\n+----------------\n++\n+We can further investigate the time spent scanning for untracked files.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+$ cat ~/log.perf\n+d0 | main                     | version      |     |           |           |            | 2.20.1.162.gb4ccea44db.dirty\n+d0 | main                     | start        |     |           |           |            | git status\n+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n+d0 | main                     | cmd_name     |     |           |           |            | status (status)\n+...\n+d0 | main                     | region_enter | r1  |  0.015047 |           | status     | label:untracked\n+d0 | main                     | region_enter |     |  0.015132 |           | dir        | ..label:read_recursive\n+d0 | main                     | region_enter |     |  0.016341 |           | dir        | ....label:read_recursive vcs-svn/\n+d0 | main                     | region_leave |     |  0.016422 |  0.000081 | dir        | ....label:read_recursive vcs-svn/\n+d0 | main                     | region_enter |     |  0.016446 |           | dir        | ....label:read_recursive xdiff/\n+d0 | main                     | region_leave |     |  0.016522 |  0.000076 | dir        | ....label:read_recursive xdiff/\n+d0 | main                     | region_enter |     |  0.016612 |           | dir        | ....label:read_recursive git-gui/\n+d0 | main                     | region_enter |     |  0.016698 |           | dir        | ......label:read_recursive git-gui/po/\n+d0 | main                     | region_enter |     |  0.016810 |           | dir        | ........label:read_recursive git-gui/po/glossary/\n+d0 | main                     | region_leave |     |  0.016863 |  0.000053 | dir        | ........label:read_recursive git-gui/po/glossary/\n+...\n+d0 | main                     | region_enter |     |  0.031876 |           | dir        | ....label:read_recursive builtin/\n+d0 | main                     | region_leave |     |  0.032270 |  0.000394 | dir        | ....label:read_recursive builtin/\n+d0 | main                     | region_leave |     |  0.032414 |  0.017282 | dir        | ..label:read_recursive\n+d0 | main                     | region_leave | r1  |  0.032454 |  0.017407 | status     | label:untracked\n+...\n+d0 | main                     | exit         |     |  0.034279 |           |            | code:0\n+d0 | main                     | atexit       |     |  0.034322 |           |            | code:0\n+----------------\n++\n+Trace2 regions are similar to the existing trace_performance_enter()\n+and trace_performance_leave() routines, but are thread safe and\n+maintain per-thread stacks of timers.\n+\n+Data Messages::\n+\n+\tData messages added to a region.\n++\n+----------------\n+int read_index_from(struct index_state *istate, const char *path,\n+\tconst char *gitdir)\n+{\n+\ttrace2_region_enter_printf(\"index\", \"do_read_index\", the_repository, \"%s\", path);\n+\n+\t...\n+\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/version\", istate->version);\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/cache_nr\", istate->cache_nr);\n+\n+\ttrace2_region_leave_printf(\"index\", \"do_read_index\", the_repository, \"%s\", path);\n+}\n+----------------\n++\n+This example shows that the index contained 3552 entries.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+$ cat ~/log.perf\n+d0 | main                     | version      |     |           |           |            | 2.20.1.156.gf9916ae094.dirty\n+d0 | main                     | start        |     |           |           |            | git status\n+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n+d0 | main                     | cmd_name     |     |           |           |            | status (status)\n+d0 | main                     | region_enter | r1  |  0.001791 |           | index      | label:do_read_index .git/index\n+d0 | main                     | data         | r1  |  0.002494 |  0.000703 | index      | ..read/version:2\n+d0 | main                     | data         | r1  |  0.002520 |  0.000729 | index      | ..read/cache_nr:3552\n+d0 | main                     | region_leave | r1  |  0.002539 |  0.000748 | index      | label:do_read_index .git/index\n+...\n+----------------\n+\n+Thread Events::\n+\n+\tThread messages added to a thread-proc.\n++\n+For example, the multithreaded preload-index code can be\n+instrumented with a region around the thread pool and then\n+per-thread start and exit events within the threadproc.\n++\n+----------------\n+static void *preload_thread(void *_data)\n+{\n+\t// start the per-thread clock and emit a message.\n+\ttrace2_thread_start(\"preload_thread\");\n+\n+\t// report which chunk of the array this thread was assigned.\n+\ttrace2_data_intmax(\"index\", the_repository, \"offset\", p->offset);\n+\ttrace2_data_intmax(\"index\", the_repository, \"count\", nr);\n+\n+\tdo {\n+\t    ...\n+\t} while (--nr > 0);\n+\t...\n+\n+\t// report elapsed time taken by this thread.\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+void preload_index(struct index_state *index,\n+\tconst struct pathspec *pathspec,\n+\tunsigned int refresh_flags)\n+{\n+\ttrace2_region_enter(\"index\", \"preload\", the_repository);\n+\n+\tfor (i = 0; i < threads; i++) {\n+\t    ... /* create thread */\n+\t}\n+\n+\tfor (i = 0; i < threads; i++) {\n+\t    ... /* join thread */\n+\t}\n+\n+\ttrace2_region_leave(\"index\", \"preload\", the_repository);\n+}\n+----------------\n++\n+In this example preload_index() was executed by the `main` thread\n+and started the `preload` region.  Seven threads, named\n+`th01:preload_thread` through `th07:preload_thread`, were started.\n+Events from each thread are atomically appended to the shared target\n+stream as they occur so they may appear in random order with respect\n+other threads. Finally, the main thread waits for the threads to\n+finish and leaves the region.\n++\n+Data events are tagged with the active thread name.  They are used\n+to report the per-thread parameters.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+$ cat ~/log.perf\n+...\n+d0 | main                     | region_enter | r1  |  0.002595 |           | index      | label:preload\n+d0 | th01:preload_thread      | thread_start |     |  0.002699 |           |            |\n+d0 | th02:preload_thread      | thread_start |     |  0.002721 |           |            |\n+d0 | th01:preload_thread      | data         | r1  |  0.002736 |  0.000037 | index      | offset:0\n+d0 | th02:preload_thread      | data         | r1  |  0.002751 |  0.000030 | index      | offset:2032\n+d0 | th03:preload_thread      | thread_start |     |  0.002711 |           |            |\n+d0 | th06:preload_thread      | thread_start |     |  0.002739 |           |            |\n+d0 | th01:preload_thread      | data         | r1  |  0.002766 |  0.000067 | index      | count:508\n+d0 | th06:preload_thread      | data         | r1  |  0.002856 |  0.000117 | index      | offset:2540\n+d0 | th03:preload_thread      | data         | r1  |  0.002824 |  0.000113 | index      | offset:1016\n+d0 | th04:preload_thread      | thread_start |     |  0.002710 |           |            |\n+d0 | th02:preload_thread      | data         | r1  |  0.002779 |  0.000058 | index      | count:508\n+d0 | th06:preload_thread      | data         | r1  |  0.002966 |  0.000227 | index      | count:508\n+d0 | th07:preload_thread      | thread_start |     |  0.002741 |           |            |\n+d0 | th07:preload_thread      | data         | r1  |  0.003017 |  0.000276 | index      | offset:3048\n+d0 | th05:preload_thread      | thread_start |     |  0.002712 |           |            |\n+d0 | th05:preload_thread      | data         | r1  |  0.003067 |  0.000355 | index      | offset:1524\n+d0 | th05:preload_thread      | data         | r1  |  0.003090 |  0.000378 | index      | count:508\n+d0 | th07:preload_thread      | data         | r1  |  0.003037 |  0.000296 | index      | count:504\n+d0 | th03:preload_thread      | data         | r1  |  0.002971 |  0.000260 | index      | count:508\n+d0 | th04:preload_thread      | data         | r1  |  0.002983 |  0.000273 | index      | offset:508\n+d0 | th04:preload_thread      | data         | r1  |  0.007311 |  0.004601 | index      | count:508\n+d0 | th05:preload_thread      | thread_exit  |     |  0.008781 |  0.006069 |            |\n+d0 | th01:preload_thread      | thread_exit  |     |  0.009561 |  0.006862 |            |\n+d0 | th03:preload_thread      | thread_exit  |     |  0.009742 |  0.007031 |            |\n+d0 | th06:preload_thread      | thread_exit  |     |  0.009820 |  0.007081 |            |\n+d0 | th02:preload_thread      | thread_exit  |     |  0.010274 |  0.007553 |            |\n+d0 | th07:preload_thread      | thread_exit  |     |  0.010477 |  0.007736 |            |\n+d0 | th04:preload_thread      | thread_exit  |     |  0.011657 |  0.008947 |            |\n+d0 | main                     | region_leave | r1  |  0.011717 |  0.009122 | index      | label:preload\n+...\n+d0 | main                     | exit         |     |  0.029996 |           |            | code:0\n+d0 | main                     | atexit       |     |  0.030027 |           |            | code:0\n+----------------\n++\n+In this example, the preload region took 0.009122 seconds.  The 7 threads\n+took between 0.006069 and 0.008947 seconds to work on their portion of\n+the index.  Thread \"th01\" worked on 508 items at offset 0.  Thread \"th02\"\n+worked on 508 items at offset 2032.  Thread \"th04\" worked on 508 itemts\n+at offset 508.\n++\n+This example also shows that thread names are assigned in a racy manner\n+as each thread starts and allocates TLS storage.\n+\n+== Future Work\n+\n+=== Relationship to the Existing Trace Api (api-trace.txt)\n+\n+There are a few issues to resolve before we can completely\n+switch to Trace2.\n+\n+* Updating existing tests that assume GIT_TRACE format messages.\n+\n+* How to best handle custom GIT_TRACE_<key> messages?\n+\n+** The GIT_TRACE_<key> mechanism allows each <key> to write to a\n+different file (in addition to just stderr).\n+\n+** Do we want to maintain that ability or simply write to the existing\n+Trace2 targets (and convert <key> to a \"category\").\n+\n-- \ngitgitgadget\n\n"},{"id":"368598","messageId":"b70c289903d295da9fb89e587fc0d0d082b5ecc5.1549473350.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 07/15] trace2:data: add trace2 transport child classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-06T17:15:59Z","receivedAt":"2019-02-06T17:16:04Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 child classification for transport processes.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n connect.c          | 3 +++\n transport-helper.c | 2 ++\n 2 files changed, 5 insertions(+)\n\ndiff --git a/connect.c b/connect.c\nindex 24281b6082..3c6f829a05 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -1251,6 +1251,7 @@ struct child_process *git_connect(int fd[2], const char *url,\n \t\tconn = NULL;\n \t} else if (protocol == PROTO_GIT) {\n \t\tconn = git_connect_git(fd, hostandport, path, prog, version, flags);\n+\t\tconn->trace2_child_class = \"transport/git\";\n \t} else {\n \t\tstruct strbuf cmd = STRBUF_INIT;\n \t\tconst char *const *var;\n@@ -1293,9 +1294,11 @@ struct child_process *git_connect(int fd[2], const char *url,\n \t\t\t\tstrbuf_release(&cmd);\n \t\t\t\treturn NULL;\n \t\t\t}\n+\t\t\tconn->trace2_child_class = \"transport/ssh\";\n \t\t\tfill_ssh_args(conn, ssh_host, port, version, flags);\n \t\t} else {\n \t\t\ttransport_check_allowed(\"file\");\n+\t\t\tconn->trace2_child_class = \"transport/file\";\n \t\t\tif (version > 0) {\n \t\t\t\targv_array_pushf(&conn->env_array, GIT_PROTOCOL_ENVIRONMENT \"=version=%d\",\n \t\t\t\t\t\t version);\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 6cf3bb324e..a01cc0093f 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -127,6 +127,8 @@ static struct child_process *get_helper(struct transport *transport)\n \t\targv_array_pushf(&helper->env_array, \"%s=%s\",\n \t\t\t\t GIT_DIR_ENVIRONMENT, get_git_dir());\n \n+\thelper->trace2_child_class = helper->args.argv[0]; /* \"remote-<name>\" */\n+\n \tcode = start_command(helper);\n \tif (code < 0 && errno == ENOENT)\n \t\tdie(_(\"unable to find remote helper for '%s'\"), data->name);\n-- \ngitgitgadget\n\n"},{"id":"368600","messageId":"d7ce85b702599666ee4ea66b1ac3947ad60c3a82.1549473350.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 06/15] trace2:data: add trace2 sub-process classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-06T17:15:58Z","receivedAt":"2019-02-06T17:16:07Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 classification for long-running processes\nstarted in sub-process.c\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n sub-process.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/sub-process.c b/sub-process.c\nindex 8d2a1707cf..3f4af93555 100644\n--- a/sub-process.c\n+++ b/sub-process.c\n@@ -88,6 +88,7 @@ int subprocess_start(struct hashmap *hashmap, struct subprocess_entry *entry, co\n \tprocess->out = -1;\n \tprocess->clean_on_exit = 1;\n \tprocess->clean_on_exit_handler = subprocess_exit_handler;\n+\tprocess->trace2_child_class = \"subprocess\";\n \n \terr = start_command(process);\n \tif (err) {\n-- \ngitgitgadget\n\n"},{"id":"368601","messageId":"2a0da885793435286b26f9c8d58f2e36a4e873c7.1549473350.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 08/15] trace2:data: add trace2 hook classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-06T17:16:00Z","receivedAt":"2019-02-06T17:16:08Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nClassify certain child processes as hooks.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/am.c           | 1 +\n builtin/receive-pack.c | 4 ++++\n builtin/worktree.c     | 1 +\n sequencer.c            | 2 ++\n transport.c            | 1 +\n 5 files changed, 9 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 95370313b6..a81e61c1bc 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -468,6 +468,7 @@ static int run_post_rewrite_hook(const struct am_state *state)\n \n \tcp.in = xopen(am_path(state, \"rewritten\"), O_RDONLY);\n \tcp.stdout_to_stderr = 1;\n+\tcp.trace2_hook_name = \"post-rewrite\";\n \n \tret = run_command(&cp);\n \ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 33187bd8e9..6eef8575b9 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -694,6 +694,8 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,\n \tproc.argv = argv;\n \tproc.in = -1;\n \tproc.stdout_to_stderr = 1;\n+\tproc.trace2_hook_name = hook_name;\n+\n \tif (feed_state->push_options) {\n \t\tint i;\n \t\tfor (i = 0; i < feed_state->push_options->nr; i++)\n@@ -807,6 +809,7 @@ static int run_update_hook(struct command *cmd)\n \tproc.stdout_to_stderr = 1;\n \tproc.err = use_sideband ? -1 : 0;\n \tproc.argv = argv;\n+\tproc.trace2_hook_name = \"update\";\n \n \tcode = start_command(&proc);\n \tif (code)\n@@ -1190,6 +1193,7 @@ static void run_update_post_hook(struct command *commands)\n \tproc.no_stdin = 1;\n \tproc.stdout_to_stderr = 1;\n \tproc.err = use_sideband ? -1 : 0;\n+\tproc.trace2_hook_name = \"post-update\";\n \n \tif (!start_command(&proc)) {\n \t\tif (use_sideband)\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 3f9907fcc9..6cc094a453 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -402,6 +402,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\t\tcp.dir = path;\n \t\t\tcp.env = env;\n \t\t\tcp.argv = NULL;\n+\t\t\tcp.trace2_hook_name = \"post-checkout\";\n \t\t\targv_array_pushl(&cp.args, absolute_path(hook),\n \t\t\t\t\t oid_to_hex(&null_oid),\n \t\t\t\t\t oid_to_hex(&commit->object.oid),\ndiff --git a/sequencer.c b/sequencer.c\nindex 213815dbfc..c1e36196a1 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1103,6 +1103,7 @@ static int run_rewrite_hook(const struct object_id *oldoid,\n \tproc.argv = argv;\n \tproc.in = -1;\n \tproc.stdout_to_stderr = 1;\n+\tproc.trace2_hook_name = \"post-rewrite\";\n \n \tcode = start_command(&proc);\n \tif (code)\n@@ -3784,6 +3785,7 @@ static int pick_commits(struct repository *r,\n \t\t\t\thook.in = open(rebase_path_rewritten_list(),\n \t\t\t\t\tO_RDONLY);\n \t\t\t\thook.stdout_to_stderr = 1;\n+\t\t\t\thook.trace2_hook_name = \"post-rewrite\";\n \t\t\t\targv_array_push(&hook.args, post_rewrite_hook);\n \t\t\t\targv_array_push(&hook.args, \"rebase\");\n \t\t\t\t/* we don't care if this hook failed */\ndiff --git a/transport.c b/transport.c\nindex 99678153c1..ed5a733c4a 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1061,6 +1061,7 @@ static int run_pre_push_hook(struct transport *transport,\n \n \tproc.argv = argv;\n \tproc.in = -1;\n+\tproc.trace2_hook_name = \"pre-push\";\n \n \tif (start_command(&proc)) {\n \t\tfinish_command(&proc);\n-- \ngitgitgadget\n\n"},{"id":"368603","messageId":"0191283fffe37f78ac9b7e056b41cbd4f90f2a0b.1549473350.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 11/15] trace2:data: add subverb to checkout command","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-06T17:16:02Z","receivedAt":"2019-02-06T17:16:10Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/checkout.c | 7 +++++++\n 1 file changed, 7 insertions(+)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 6fadf412e8..f911c88bb4 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -262,6 +262,8 @@ static int checkout_paths(const struct checkout_opts *opts,\n \tstruct lock_file lock_file = LOCK_INIT;\n \tint nr_checkouts = 0;\n \n+\ttrace2_cmd_mode(opts->patch_mode ? \"patch\" : \"path\");\n+\n \tif (opts->track != BRANCH_TRACK_UNSPECIFIED)\n \t\tdie(_(\"'%s' cannot be used with updating paths\"), \"--track\");\n \n@@ -952,6 +954,9 @@ static int switch_branches(const struct checkout_opts *opts,\n \tvoid *path_to_free;\n \tstruct object_id rev;\n \tint flag, writeout_error = 0;\n+\n+\ttrace2_cmd_mode(\"branch\");\n+\n \tmemset(&old_branch_info, 0, sizeof(old_branch_info));\n \told_branch_info.path = path_to_free = resolve_refdup(\"HEAD\", 0, &rev, &flag);\n \tif (old_branch_info.path)\n@@ -1189,6 +1194,8 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)\n \tint status;\n \tstruct strbuf branch_ref = STRBUF_INIT;\n \n+\ttrace2_cmd_mode(\"unborn\");\n+\n \tif (!opts->new_branch)\n \t\tdie(_(\"You are on a branch yet to be born\"));\n \tstrbuf_addf(&branch_ref, \"refs/heads/%s\", opts->new_branch);\n-- \ngitgitgadget\n\n"},{"id":"368602","messageId":"24d8d8d76837e0ae921d79f4534c8bd02b3157ff.1549473350.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 12/15] trace2:data: add subverb to reset command","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-06T17:16:03Z","receivedAt":"2019-02-06T17:16:11Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/reset.c | 6 ++++++\n 1 file changed, 6 insertions(+)\n\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex 59898c972e..4e34c61401 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -340,6 +340,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \tif (patch_mode) {\n \t\tif (reset_type != NONE)\n \t\t\tdie(_(\"--patch is incompatible with --{hard,mixed,soft}\"));\n+\t\ttrace2_cmd_mode(\"patch-interactive\");\n \t\treturn run_add_interactive(rev, \"--patch=reset\", &pathspec);\n \t}\n \n@@ -356,6 +357,11 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \tif (reset_type == NONE)\n \t\treset_type = MIXED; /* by default */\n \n+\tif (pathspec.nr)\n+\t\ttrace2_cmd_mode(\"path\");\n+\telse\n+\t\ttrace2_cmd_mode(reset_type_names[reset_type]);\n+\n \tif (reset_type != SOFT && (reset_type != MIXED || get_git_work_tree()))\n \t\tsetup_work_tree();\n \n-- \ngitgitgadget\n\n"},{"id":"368604","messageId":"93a25d09a15d67ca9f139d1bc1a2fa04840fa463.1549473351.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 15/15] trace2: add for_each macros to clang-format","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-06T17:16:06Z","receivedAt":"2019-02-06T17:16:12Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n .clang-format | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/.clang-format b/.clang-format\nindex de1c8b5c77..41d4cd23fd 100644\n--- a/.clang-format\n+++ b/.clang-format\n@@ -149,7 +149,7 @@ Cpp11BracedListStyle: false\n \n # A list of macros that should be interpreted as foreach loops instead of as\n # function calls.\n-ForEachMacros: ['for_each_string_list_item']\n+ForEachMacros: ['for_each_string_list_item', 'for_each_wanted_builtin', 'for_each_builtin', 'for_each_ut']\n \n # The maximum number of consecutive empty lines to keep.\n MaxEmptyLinesToKeep: 1\n-- \ngitgitgadget\n"},{"id":"368605","messageId":"6bad326bbd27426845734d08428e6987a75d9ad9.1549473350.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 02/15] trace2: create new combined trace facility","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-06T17:15:54Z","receivedAt":"2019-02-06T17:16:13Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate a new unified tracing facility for git.  The eventual intent is to\nreplace the current trace_printf* and trace_performance* routines with a\nunified set of git_trace2* routines.\n\nIn addition to the usual printf-style API, trace2 provides higer-level\nevent verbs with fixed-fields allowing structured data to be written.\nThis makes post-processing and analysis easier for external tools.\n\nTrace2 defines 3 output targets.  These are set using the environment\nvariables \"GIT_TR2\", \"GIT_TR2_PERF\", and \"GIT_TR2_EVENT\".  These may be\nset to \"1\" or to an absolute pathname (just like the current GIT_TRACE).\n\n* GIT_TR2 is intended to be a replacement for GIT_TRACE and logs command\n  summary data.\n\n* GIT_TR2_PERF is intended as a replacement for GIT_TRACE_PERFORMANCE.\n  It extends the output with columns for the command process, thread,\n  repo, absolute and relative elapsed times.  It reports events for\n  child process start/stop, thread start/stop, and per-thread function\n  nesting.\n\n* GIT_TR2_EVENT is a new structured format. It writes event data as a\n  series of JSON records.\n\nCalls to trace2 functions log to any of the 3 output targets enabled\nwithout the need to call different trace_printf* or trace_performance*\nroutines.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                      |  14 +-\n builtin/submodule--helper.c   |   9 +-\n cache.h                       |   1 +\n common-main.c                 |  12 +-\n compat/mingw.c                |  11 +-\n compat/mingw.h                |   3 +-\n config.c                      |   2 +\n exec-cmd.c                    |   2 +\n git-compat-util.h             |   7 +\n git.c                         |  65 +++\n remote-curl.c                 |   7 +\n repository.c                  |   2 +\n repository.h                  |   3 +\n run-command.c                 |  59 ++-\n run-command.h                 |  13 +-\n sh-i18n--envsubst.c           |   3 +\n submodule.c                   |  11 +-\n t/helper/test-parse-options.c |   3 +\n t/helper/test-tool.c          |   3 +\n t/t0001-init.sh               |   1 +\n trace2.c                      | 762 ++++++++++++++++++++++++++++++++++\n trace2.h                      | 371 +++++++++++++++++\n trace2/tr2_cfg.c              |  90 ++++\n trace2/tr2_cfg.h              |  19 +\n trace2/tr2_cmd_name.c         |  30 ++\n trace2/tr2_cmd_name.h         |  24 ++\n trace2/tr2_dst.c              | 197 +++++++++\n trace2/tr2_dst.h              |  36 ++\n trace2/tr2_sid.c              |  67 +++\n trace2/tr2_sid.h              |  18 +\n trace2/tr2_tbuf.c             |  32 ++\n trace2/tr2_tbuf.h             |  23 +\n trace2/tr2_tgt.h              | 133 ++++++\n trace2/tr2_tgt_event.c        | 589 ++++++++++++++++++++++++++\n trace2/tr2_tgt_normal.c       | 324 +++++++++++++++\n trace2/tr2_tgt_perf.c         | 535 ++++++++++++++++++++++++\n trace2/tr2_tls.c              | 164 ++++++++\n trace2/tr2_tls.h              |  97 +++++\n usage.c                       |  31 ++\n 39 files changed, 3755 insertions(+), 18 deletions(-)\n create mode 100644 trace2.c\n create mode 100644 trace2.h\n create mode 100644 trace2/tr2_cfg.c\n create mode 100644 trace2/tr2_cfg.h\n create mode 100644 trace2/tr2_cmd_name.c\n create mode 100644 trace2/tr2_cmd_name.h\n create mode 100644 trace2/tr2_dst.c\n create mode 100644 trace2/tr2_dst.h\n create mode 100644 trace2/tr2_sid.c\n create mode 100644 trace2/tr2_sid.h\n create mode 100644 trace2/tr2_tbuf.c\n create mode 100644 trace2/tr2_tbuf.h\n create mode 100644 trace2/tr2_tgt.h\n create mode 100644 trace2/tr2_tgt_event.c\n create mode 100644 trace2/tr2_tgt_normal.c\n create mode 100644 trace2/tr2_tgt_perf.c\n create mode 100644 trace2/tr2_tls.c\n create mode 100644 trace2/tr2_tls.h\n\ndiff --git a/Makefile b/Makefile\nindex 6e8d017e8e..2ab7b73000 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1005,6 +1005,16 @@ LIB_OBJS += tempfile.o\n LIB_OBJS += thread-utils.o\n LIB_OBJS += tmp-objdir.o\n LIB_OBJS += trace.o\n+LIB_OBJS += trace2.o\n+LIB_OBJS += trace2/tr2_cfg.o\n+LIB_OBJS += trace2/tr2_cmd_name.o\n+LIB_OBJS += trace2/tr2_dst.o\n+LIB_OBJS += trace2/tr2_sid.o\n+LIB_OBJS += trace2/tr2_tbuf.o\n+LIB_OBJS += trace2/tr2_tgt_event.o\n+LIB_OBJS += trace2/tr2_tgt_normal.o\n+LIB_OBJS += trace2/tr2_tgt_perf.o\n+LIB_OBJS += trace2/tr2_tls.o\n LIB_OBJS += trailer.o\n LIB_OBJS += transport.o\n LIB_OBJS += transport-helper.o\n@@ -1581,7 +1591,9 @@ ifdef NO_INET_PTON\n \tLIB_OBJS += compat/inet_pton.o\n \tBASIC_CFLAGS += -DNO_INET_PTON\n endif\n-ifndef NO_UNIX_SOCKETS\n+ifdef NO_UNIX_SOCKETS\n+\tBASIC_CFLAGS += -DNO_UNIX_SOCKETS\n+else\n \tLIB_OBJS += unix-socket.o\n \tPROGRAM_OBJS += credential-cache.o\n \tPROGRAM_OBJS += credential-cache--daemon.o\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 0e140f176c..f10dc5134a 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -1815,11 +1815,10 @@ static int update_submodules(struct submodule_update_clone *suc)\n {\n \tint i;\n \n-\trun_processes_parallel(suc->max_jobs,\n-\t\t\t       update_clone_get_next_task,\n-\t\t\t       update_clone_start_failure,\n-\t\t\t       update_clone_task_finished,\n-\t\t\t       suc);\n+\trun_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task,\n+\t\t\t\t   update_clone_start_failure,\n+\t\t\t\t   update_clone_task_finished, suc, \"submodule\",\n+\t\t\t\t   \"parallel/update\");\n \n \t/*\n \t * We saved the output and put it out all at once now.\ndiff --git a/cache.h b/cache.h\nindex 009e8b3b15..3d12ffe01f 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -9,6 +9,7 @@\n #include \"gettext.h\"\n #include \"convert.h\"\n #include \"trace.h\"\n+#include \"trace2.h\"\n #include \"string-list.h\"\n #include \"pack-revindex.h\"\n #include \"hash.h\"\ndiff --git a/common-main.c b/common-main.c\nindex 3728f66b4c..6dbdc4adf2 100644\n--- a/common-main.c\n+++ b/common-main.c\n@@ -25,12 +25,18 @@ static void restore_sigpipe_to_default(void)\n \n int main(int argc, const char **argv)\n {\n+\tint result;\n+\n \t/*\n \t * Always open file descriptors 0/1/2 to avoid clobbering files\n \t * in die().  It also avoids messing up when the pipes are dup'ed\n \t * onto stdin/stdout/stderr in the child processes we spawn.\n \t */\n \tsanitize_stdfds();\n+\trestore_sigpipe_to_default();\n+\n+\ttrace2_initialize();\n+\ttrace2_cmd_start(argv);\n \n \tgit_resolve_executable_dir(argv[0]);\n \n@@ -40,7 +46,9 @@ int main(int argc, const char **argv)\n \n \tattr_start();\n \n-\trestore_sigpipe_to_default();\n+\tresult = cmd_main(argc, argv);\n+\n+\ttrace2_cmd_exit(result);\n \n-\treturn cmd_main(argc, argv);\n+\treturn result;\n }\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex b459e1a291..111fe68baf 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -1476,19 +1476,23 @@ static int try_shell_exec(const char *cmd, char *const *argv)\n \t\treturn 0;\n \tprog = path_lookup(interpr, 1);\n \tif (prog) {\n+\t\tint exec_id;\n \t\tint argc = 0;\n \t\tconst char **argv2;\n \t\twhile (argv[argc]) argc++;\n \t\tALLOC_ARRAY(argv2, argc + 1);\n \t\targv2[0] = (char *)cmd;\t/* full path to the script file */\n \t\tmemcpy(&argv2[1], &argv[1], sizeof(*argv) * argc);\n+\t\texec_id = trace2_exec(prog, argv2);\n \t\tpid = mingw_spawnv(prog, argv2, 1);\n \t\tif (pid >= 0) {\n \t\t\tint status;\n \t\t\tif (waitpid(pid, &status, 0) < 0)\n \t\t\t\tstatus = 255;\n+\t\t\ttrace2_exec_result(exec_id, status);\n \t\t\texit(status);\n \t\t}\n+\t\ttrace2_exec_result(exec_id, -1);\n \t\tpid = 1;\t/* indicate that we tried but failed */\n \t\tfree(prog);\n \t\tfree(argv2);\n@@ -1501,12 +1505,17 @@ int mingw_execv(const char *cmd, char *const *argv)\n \t/* check if git_command is a shell script */\n \tif (!try_shell_exec(cmd, argv)) {\n \t\tint pid, status;\n+\t\tint exec_id;\n \n+\t\texec_id = trace2_exec(cmd, (const char **)argv);\n \t\tpid = mingw_spawnv(cmd, (const char **)argv, 0);\n-\t\tif (pid < 0)\n+\t\tif (pid < 0) {\n+\t\t\ttrace2_exec_result(exec_id, -1);\n \t\t\treturn -1;\n+\t\t}\n \t\tif (waitpid(pid, &status, 0) < 0)\n \t\t\tstatus = 255;\n+\t\ttrace2_exec_result(exec_id, status);\n \t\texit(status);\n \t}\n \treturn -1;\ndiff --git a/compat/mingw.h b/compat/mingw.h\nindex 30d9fb3e36..4d73f8aa9d 100644\n--- a/compat/mingw.h\n+++ b/compat/mingw.h\n@@ -147,8 +147,7 @@ static inline int fcntl(int fd, int cmd, ...)\n \terrno = EINVAL;\n \treturn -1;\n }\n-/* bash cannot reliably detect negative return codes as failure */\n-#define exit(code) exit((code) & 0xff)\n+\n #define sigemptyset(x) (void)0\n static inline int sigaddset(sigset_t *set, int signum)\n { return 0; }\ndiff --git a/config.c b/config.c\nindex ff521eb27a..4beeb7795a 100644\n--- a/config.c\n+++ b/config.c\n@@ -2656,6 +2656,8 @@ int git_config_set_gently(const char *key, const char *value)\n void git_config_set(const char *key, const char *value)\n {\n \tgit_config_set_multivar(key, value, NULL, 0);\n+\n+\ttrace2_cmd_set_config(key, value);\n }\n \n /*\ndiff --git a/exec-cmd.c b/exec-cmd.c\nindex 4f81f44310..7deeab3039 100644\n--- a/exec-cmd.c\n+++ b/exec-cmd.c\n@@ -209,6 +209,8 @@ static int git_get_exec_path(struct strbuf *buf, const char *argv0)\n \t\treturn -1;\n \t}\n \n+\ttrace2_cmd_path(buf->buf);\n+\n \treturn 0;\n }\n \ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 29a19902aa..1d0aedc11f 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -1251,6 +1251,13 @@ static inline int is_missing_file_error(int errno_)\n \n extern int cmd_main(int, const char **);\n \n+/*\n+ * Intercept all calls to exit() and route them to trace2 to\n+ * optionally emit a message before calling the real exit().\n+ */\n+int trace2_cmd_exit_fl(const char *file, int line, int code);\n+#define exit(code) exit(trace2_cmd_exit_fl(__FILE__, __LINE__, (code)))\n+\n /*\n  * You can mark a stack variable with UNLEAK(var) to avoid it being\n  * reported as a leak by tools like LSAN or valgrind. The argument\ndiff --git a/git.c b/git.c\nindex 0ce0e13f0f..67ba8de9d1 100644\n--- a/git.c\n+++ b/git.c\n@@ -147,16 +147,20 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\t\tgit_set_exec_path(cmd + 1);\n \t\t\telse {\n \t\t\t\tputs(git_exec_path());\n+\t\t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\t\texit(0);\n \t\t\t}\n \t\t} else if (!strcmp(cmd, \"--html-path\")) {\n \t\t\tputs(system_path(GIT_HTML_PATH));\n+\t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"--man-path\")) {\n \t\t\tputs(system_path(GIT_MAN_PATH));\n+\t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"--info-path\")) {\n \t\t\tputs(system_path(GIT_INFO_PATH));\n+\t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"-p\") || !strcmp(cmd, \"--paginate\")) {\n \t\t\tuse_pager = 1;\n@@ -285,6 +289,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\t(*argv)++;\n \t\t\t(*argc)--;\n \t\t} else if (skip_prefix(cmd, \"--list-cmds=\", &cmd)) {\n+\t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\tif (!strcmp(cmd, \"parseopt\")) {\n \t\t\t\tstruct string_list list = STRING_LIST_INIT_DUP;\n \t\t\t\tint i;\n@@ -332,9 +337,14 @@ static int handle_alias(int *argcp, const char ***argv)\n \t\t\tcommit_pager_choice();\n \n \t\t\tchild.use_shell = 1;\n+\t\t\tchild.trace2_child_class = \"shell_alias\";\n \t\t\targv_array_push(&child.args, alias_string + 1);\n \t\t\targv_array_pushv(&child.args, (*argv) + 1);\n \n+\t\t\ttrace2_cmd_alias(alias_command, child.args.argv);\n+\t\t\ttrace2_cmd_list_config();\n+\t\t\ttrace2_cmd_name(\"_run_shell_alias_\");\n+\n \t\t\tret = run_command(&child);\n \t\t\tif (ret >= 0)   /* normal exit */\n \t\t\t\texit(ret);\n@@ -369,6 +379,9 @@ static int handle_alias(int *argcp, const char ***argv)\n \t\t/* insert after command name */\n \t\tmemcpy(new_argv + count, *argv + 1, sizeof(char *) * *argcp);\n \n+\t\ttrace2_cmd_alias(alias_command, new_argv);\n+\t\ttrace2_cmd_list_config();\n+\n \t\t*argv = new_argv;\n \t\t*argcp += count - 1;\n \n@@ -417,6 +430,8 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)\n \t\tsetup_work_tree();\n \n \ttrace_argv_printf(argv, \"trace: built-in: git\");\n+\ttrace2_cmd_name(p->cmd);\n+\ttrace2_cmd_list_config();\n \n \tvalidate_cache_entries(&the_index);\n \tstatus = p->fn(argc, argv, prefix);\n@@ -666,7 +681,14 @@ static void execv_dashed_external(const char **argv)\n \tcmd.clean_on_exit = 1;\n \tcmd.wait_after_clean = 1;\n \tcmd.silent_exec_failure = 1;\n+\tcmd.trace2_child_class = \"dashed\";\n \n+\ttrace2_cmd_name(\"_run_dashed_\");\n+\n+\t/*\n+\t * The code in run_command() logs trace2 child_start/child_exit\n+\t * events, so we do not need to report exec/exec_result events here.\n+\t */\n \ttrace_argv_printf(cmd.args.argv, \"trace: exec:\");\n \n \t/*\n@@ -676,6 +698,12 @@ static void execv_dashed_external(const char **argv)\n \t * the program.\n \t */\n \tstatus = run_command(&cmd);\n+\n+\t/*\n+\t * If the child process ran and we are now going to exit, emit a\n+\t * generic string as our trace2 command verb to indicate that we\n+\t * launched a dashed command.\n+\t */\n \tif (status >= 0)\n \t\texit(status);\n \telse if (errno != ENOENT)\n@@ -701,6 +729,43 @@ static int run_argv(int *argcp, const char ***argv)\n \t\tif (!done_alias)\n \t\t\thandle_builtin(*argcp, *argv);\n \n+#if 0 // TODO In GFW, need to amend a7924b655e940b06cb547c235d6bed9767929673 to include trace2_ and _tr2 lines.\n+\t\telse if (get_builtin(**argv)) {\n+\t\t\tstruct argv_array args = ARGV_ARRAY_INIT;\n+\t\t\tint i;\n+\n+\t\t\t/*\n+\t\t\t * The current process is committed to launching a\n+\t\t\t * child process to run the command named in (**argv)\n+\t\t\t * and exiting.  Log a generic string as the trace2\n+\t\t\t * command verb to indicate this.  Note that the child\n+\t\t\t * process will log the actual verb when it runs.\n+\t\t\t */\n+\t\t\ttrace2_cmd_name(\"_run_git_alias_\");\n+\n+\t\t\tif (get_super_prefix())\n+\t\t\t\tdie(\"%s doesn't support --super-prefix\", **argv);\n+\n+\t\t\tcommit_pager_choice();\n+\n+\t\t\targv_array_push(&args, \"git\");\n+\t\t\tfor (i = 0; i < *argcp; i++)\n+\t\t\t\targv_array_push(&args, (*argv)[i]);\n+\n+\t\t\ttrace_argv_printf(args.argv, \"trace: exec:\");\n+\n+\t\t\t/*\n+\t\t\t * if we fail because the command is not found, it is\n+\t\t\t * OK to return. Otherwise, we just pass along the status code.\n+\t\t\t */\n+\t\t\ti = run_command_v_opt_tr2(args.argv, RUN_SILENT_EXEC_FAILURE |\n+\t\t\t\t\t\t  RUN_CLEAN_ON_EXIT, \"git_alias\");\n+\t\t\tif (i >= 0 || errno != ENOENT)\n+\t\t\t\texit(i);\n+\t\t\tdie(\"could not execute builtin %s\", **argv);\n+\t\t}\n+#endif // a7924b655e940b06cb547c235d6bed9767929673\n+\n \t\t/* .. then try the external ones */\n \t\texecv_dashed_external(*argv);\n \ndiff --git a/remote-curl.c b/remote-curl.c\nindex 6ff9c66b90..ef7f4903c9 100644\n--- a/remote-curl.c\n+++ b/remote-curl.c\n@@ -1360,6 +1360,13 @@ int cmd_main(int argc, const char **argv)\n \tstring_list_init(&options.deepen_not, 1);\n \tstring_list_init(&options.push_options, 1);\n \n+\t/*\n+\t * Just report \"remote-curl\" here (folding all the various aliases\n+\t * (\"git-remote-http\", \"git-remote-https\", and etc.) here since they\n+\t * are all just copies of the same actual executable.\n+\t */\n+\ttrace2_cmd_name(\"remote-curl\");\n+\n \tremote = remote_get(argv[1]);\n \n \tif (argc > 2) {\ndiff --git a/repository.c b/repository.c\nindex 20c509a922..b8da4d0bbf 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -119,6 +119,8 @@ static int repo_init_gitdir(struct repository *repo, const char *gitdir)\n void repo_set_worktree(struct repository *repo, const char *path)\n {\n \trepo->worktree = real_pathdup(path, 1);\n+\n+\ttrace2_def_repo(repo);\n }\n \n static int read_and_verify_repository_format(struct repository_format *format,\ndiff --git a/repository.h b/repository.h\nindex 0e482b7d49..4e2ad6e0be 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -90,6 +90,9 @@ struct repository {\n \t/* Repository's current hash algorithm, as serialized on disk. */\n \tconst struct git_hash_algo *hash_algo;\n \n+\t/* A unique-id for tracing purposes. */\n+\tint trace2_repo_id;\n+\n \t/* Configurations */\n \n \t/* Indicate if a repository has a different 'commondir' from 'gitdir' */\ndiff --git a/run-command.c b/run-command.c\nindex 3db26b7b0e..3449db319b 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -219,9 +219,29 @@ static int exists_in_PATH(const char *file)\n \n int sane_execvp(const char *file, char * const argv[])\n {\n+#ifndef GIT_WINDOWS_NATIVE\n+\t/*\n+\t * execvp() doesn't return, so we all we can do is tell trace2\n+\t * what we are about to do and let it leave a hint in the log\n+\t * (unless of course the execvp() fails).\n+\t *\n+\t * we skip this for Windows because the compat layer already\n+\t * has to emulate the execvp() call anyway.\n+\t */\n+\tint exec_id = trace2_exec(file, (const char **)argv);\n+#endif\n+\n \tif (!execvp(file, argv))\n \t\treturn 0; /* cannot happen ;-) */\n \n+#ifndef GIT_WINDOWS_NATIVE\n+\t{\n+\t\tint ec = errno;\n+\t\ttrace2_exec_result(exec_id, ec);\n+\t\terrno = ec;\n+\t}\n+#endif\n+\n \t/*\n \t * When a command can't be found because one of the directories\n \t * listed in $PATH is unsearchable, execvp reports EACCES, but\n@@ -712,6 +732,7 @@ int start_command(struct child_process *cmd)\n \t\tcmd->err = fderr[0];\n \t}\n \n+\ttrace2_child_start(cmd);\n \ttrace_run_command(cmd);\n \n \tfflush(NULL);\n@@ -926,6 +947,8 @@ int start_command(struct child_process *cmd)\n #endif\n \n \tif (cmd->pid < 0) {\n+\t\ttrace2_child_exit(cmd, -1);\n+\n \t\tif (need_in)\n \t\t\tclose_pair(fdin);\n \t\telse if (cmd->in)\n@@ -964,13 +987,16 @@ int start_command(struct child_process *cmd)\n int finish_command(struct child_process *cmd)\n {\n \tint ret = wait_or_whine(cmd->pid, cmd->argv[0], 0);\n+\ttrace2_child_exit(cmd, ret);\n \tchild_process_clear(cmd);\n \treturn ret;\n }\n \n int finish_command_in_signal(struct child_process *cmd)\n {\n-\treturn wait_or_whine(cmd->pid, cmd->argv[0], 1);\n+\tint ret = wait_or_whine(cmd->pid, cmd->argv[0], 1);\n+\ttrace2_child_exit(cmd, ret);\n+\treturn ret;\n }\n \n \n@@ -992,7 +1018,18 @@ int run_command_v_opt(const char **argv, int opt)\n \treturn run_command_v_opt_cd_env(argv, opt, NULL, NULL);\n }\n \n+int run_command_v_opt_tr2(const char **argv, int opt, const char *tr2_class)\n+{\n+\treturn run_command_v_opt_cd_env_tr2(argv, opt, NULL, NULL, tr2_class);\n+}\n+\n int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env)\n+{\n+\treturn run_command_v_opt_cd_env_tr2(argv, opt, dir, env, NULL);\n+}\n+\n+int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,\n+\t\t\t\t const char *const *env, const char *tr2_class)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \tcmd.argv = argv;\n@@ -1004,6 +1041,7 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const\n \tcmd.clean_on_exit = opt & RUN_CLEAN_ON_EXIT ? 1 : 0;\n \tcmd.dir = dir;\n \tcmd.env = env;\n+\tcmd.trace2_child_class = tr2_class;\n \treturn run_command(&cmd);\n }\n \n@@ -1319,6 +1357,7 @@ int run_hook_ve(const char *const *env, const char *name, va_list args)\n \thook.env = env;\n \thook.no_stdin = 1;\n \thook.stdout_to_stderr = 1;\n+\thook.trace2_hook_name = name;\n \n \treturn run_command(&hook);\n }\n@@ -1807,3 +1846,21 @@ int run_processes_parallel(int n,\n \tpp_cleanup(&pp);\n \treturn 0;\n }\n+\n+int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task,\n+\t\t\t       start_failure_fn start_failure,\n+\t\t\t       task_finished_fn task_finished, void *pp_cb,\n+\t\t\t       const char *tr2_category, const char *tr2_label)\n+{\n+\tint result;\n+\n+\ttrace2_region_enter_printf(tr2_category, tr2_label, NULL, \"max:%d\",\n+\t\t\t\t   ((n < 1) ? online_cpus() : n));\n+\n+\tresult = run_processes_parallel(n, get_next_task, start_failure,\n+\t\t\t\t\ttask_finished, pp_cb);\n+\n+\ttrace2_region_leave(tr2_category, tr2_label, NULL);\n+\n+\treturn result;\n+}\ndiff --git a/run-command.h b/run-command.h\nindex 68f5369fc2..a6950691c0 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -10,6 +10,12 @@ struct child_process {\n \tstruct argv_array args;\n \tstruct argv_array env_array;\n \tpid_t pid;\n+\n+\tint trace2_child_id;\n+\tuint64_t trace2_child_us_start;\n+\tconst char *trace2_child_class;\n+\tconst char *trace2_hook_name;\n+\n \t/*\n \t * Using .in, .out, .err:\n \t * - Specify 0 for no redirections (child inherits stdin, stdout,\n@@ -73,12 +79,14 @@ extern int run_hook_ve(const char *const *env, const char *name, va_list args);\n #define RUN_USING_SHELL 16\n #define RUN_CLEAN_ON_EXIT 32\n int run_command_v_opt(const char **argv, int opt);\n-\n+int run_command_v_opt_tr2(const char **argv, int opt, const char *tr2_class);\n /*\n  * env (the environment) is to be formatted like environ: \"VAR=VALUE\".\n  * To unset an environment variable use just \"VAR\".\n  */\n int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env);\n+int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,\n+\t\t\t\t const char *const *env, const char *tr2_class);\n \n /**\n  * Execute the given command, sending \"in\" to its stdin, and capturing its\n@@ -220,5 +228,8 @@ int run_processes_parallel(int n,\n \t\t\t   start_failure_fn,\n \t\t\t   task_finished_fn,\n \t\t\t   void *pp_cb);\n+int run_processes_parallel_tr2(int n, get_next_task_fn, start_failure_fn,\n+\t\t\t       task_finished_fn, void *pp_cb,\n+\t\t\t       const char *tr2_category, const char *tr2_label);\n \n #endif\ndiff --git a/sh-i18n--envsubst.c b/sh-i18n--envsubst.c\nindex 09c6b445b9..cecfdd36c7 100644\n--- a/sh-i18n--envsubst.c\n+++ b/sh-i18n--envsubst.c\n@@ -14,6 +14,7 @@\n  */\n \n #include \"git-compat-util.h\"\n+#include \"trace2.h\"\n \n /* Substitution of environment variables in shell format strings.\n    Copyright (C) 2003-2007 Free Software Foundation, Inc.\n@@ -67,6 +68,8 @@ cmd_main (int argc, const char *argv[])\n   /* Default values for command line options.  */\n   /* unsigned short int show_variables = 0; */\n \n+  trace2_cmd_name(\"sh-i18n--envsubst\");\n+\n   switch (argc)\n \t{\n \tcase 1:\ndiff --git a/submodule.c b/submodule.c\nindex 7b5cea8522..a01c9dccf6 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1568,11 +1568,12 @@ int fetch_populated_submodules(struct repository *r,\n \n \tcalculate_changed_submodule_paths(r, &spf.changed_submodule_names);\n \tstring_list_sort(&spf.changed_submodule_names);\n-\trun_processes_parallel(max_parallel_jobs,\n-\t\t\t       get_next_submodule,\n-\t\t\t       fetch_start_failure,\n-\t\t\t       fetch_finish,\n-\t\t\t       &spf);\n+\trun_processes_parallel_tr2(max_parallel_jobs,\n+\t\t\t\t   get_next_submodule,\n+\t\t\t\t   fetch_start_failure,\n+\t\t\t\t   fetch_finish,\n+\t\t\t\t   &spf,\n+\t\t\t\t   \"submodule\", \"parallel/fetch\");\n \n \targv_array_clear(&spf.args);\n out:\ndiff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c\nindex 47fee660b8..cc88fba057 100644\n--- a/t/helper/test-parse-options.c\n+++ b/t/helper/test-parse-options.c\n@@ -2,6 +2,7 @@\n #include \"cache.h\"\n #include \"parse-options.h\"\n #include \"string-list.h\"\n+#include \"trace2.h\"\n \n static int boolean = 0;\n static int integer = 0;\n@@ -153,6 +154,8 @@ int cmd__parse_options(int argc, const char **argv)\n \tint i;\n \tint ret = 0;\n \n+\ttrace2_cmd_name(\"_parse_\");\n+\n \targc = parse_options(argc, (const char **)argv, prefix, options, usage, 0);\n \n \tif (length_cb.called) {\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 5b137874e1..4dbfff7dee 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -1,5 +1,6 @@\n #include \"git-compat-util.h\"\n #include \"test-tool.h\"\n+#include \"trace2.h\"\n \n struct test_cmd {\n \tconst char *name;\n@@ -80,6 +81,8 @@ int cmd_main(int argc, const char **argv)\n \t\tif (!strcmp(cmds[i].name, argv[1])) {\n \t\t\targv++;\n \t\t\targc--;\n+\t\t\ttrace2_cmd_name(cmds[i].name);\n+\t\t\ttrace2_cmd_list_config();\n \t\t\treturn cmds[i].fn(argc, argv);\n \t\t}\n \t}\ndiff --git a/t/t0001-init.sh b/t/t0001-init.sh\nindex 42a263cada..5e27604b24 100755\n--- a/t/t0001-init.sh\n+++ b/t/t0001-init.sh\n@@ -93,6 +93,7 @@ test_expect_success 'No extra GIT_* on alias scripts' '\n \t\tsed -n \\\n \t\t\t-e \"/^GIT_PREFIX=/d\" \\\n \t\t\t-e \"/^GIT_TEXTDOMAINDIR=/d\" \\\n+\t\t\t-e \"/^GIT_TR2_PARENT/d\" \\\n \t\t\t-e \"/^GIT_/s/=.*//p\" |\n \t\tsort\n \tEOF\ndiff --git a/trace2.c b/trace2.c\nnew file mode 100644\nindex 0000000000..d289965060\n--- /dev/null\n+++ b/trace2.c\n@@ -0,0 +1,762 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"json-writer.h\"\n+#include \"quote.h\"\n+#include \"run-command.h\"\n+#include \"sigchain.h\"\n+#include \"thread-utils.h\"\n+#include \"version.h\"\n+#include \"trace2/tr2_cfg.h\"\n+#include \"trace2/tr2_cmd_name.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_sid.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+static int trace2_enabled;\n+\n+static int tr2_next_child_id; /* modify under lock */\n+static int tr2_next_exec_id; /* modify under lock */\n+static int tr2_next_repo_id = 1; /* modify under lock. zero is reserved */\n+\n+/*\n+ * A table of the builtin TRACE2 targets.  Each of these may be independently\n+ * enabled or disabled.  Each TRACE2 API method will try to write an event to\n+ * *each* of the enabled targets.\n+ */\n+/* clang-format off */\n+static struct tr2_tgt *tr2_tgt_builtins[] =\n+{\n+\t&tr2_tgt_normal,\n+\t&tr2_tgt_perf,\n+\t&tr2_tgt_event,\n+\tNULL\n+};\n+/* clang-format on */\n+\n+/* clang-format off */\n+#define for_each_builtin(j, tgt_j)\t\t\t\\\n+\tfor (j = 0, tgt_j = tr2_tgt_builtins[j];\t\\\n+\t     tgt_j;\t\t\t\t\t\\\n+\t     j++, tgt_j = tr2_tgt_builtins[j])\n+/* clang-format on */\n+\n+/* clang-format off */\n+#define for_each_wanted_builtin(j, tgt_j)            \\\n+\tfor_each_builtin(j, tgt_j)                   \\\n+\t\tif (tr2_dst_trace_want(tgt_j->pdst))\n+/* clang-format on */\n+\n+/*\n+ * Force (rather than lazily) initialize any of the requested\n+ * builtin TRACE2 targets at startup (and before we've seen an\n+ * actual TRACE2 event call) so we can see if we need to setup\n+ * the TR2 and TLS machinery.\n+ *\n+ * Return the number of builtin targets enabled.\n+ */\n+static int tr2_tgt_want_builtins(void)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tint sum = 0;\n+\n+\tfor_each_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_init())\n+\t\t\tsum++;\n+\n+\treturn sum;\n+}\n+\n+/*\n+ * Properly terminate each builtin target.  Give each target\n+ * a chance to write a summary event and/or flush if necessary\n+ * and then close the fd.\n+ */\n+static void tr2_tgt_disable_builtins(void)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tfor_each_builtin (j, tgt_j)\n+\t\ttgt_j->pfn_term();\n+}\n+\n+static int tr2main_exit_code;\n+\n+/*\n+ * Our atexit routine should run after everything has finished.\n+ *\n+ * Note that events generated here might not actually appear if\n+ * we are writing to fd 1 or 2 and our atexit routine runs after\n+ * the pager's atexit routine (since it closes them to shutdown\n+ * the pipes).\n+ */\n+static void tr2main_atexit_handler(void)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Clear any unbalanced regions so that our atexit message\n+\t * does not appear nested.  This improves the appearance of\n+\t * the trace output if someone calls die(), for example.\n+\t */\n+\ttr2tls_pop_unwind_self();\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_atexit)\n+\t\t\ttgt_j->pfn_atexit(us_elapsed_absolute,\n+\t\t\t\t\t  tr2main_exit_code);\n+\n+\ttr2_tgt_disable_builtins();\n+\n+\ttr2tls_release();\n+\ttr2_sid_release();\n+\ttr2_cmd_name_release();\n+\ttr2_cfg_free_patterns();\n+\n+\ttrace2_enabled = 0;\n+}\n+\n+static void tr2main_signal_handler(int signo)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_signal)\n+\t\t\ttgt_j->pfn_signal(us_elapsed_absolute, signo);\n+\n+\tsigchain_pop(signo);\n+\traise(signo);\n+}\n+\n+void trace2_initialize_fl(const char *file, int line)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (trace2_enabled)\n+\t\treturn;\n+\n+\tif (!tr2_tgt_want_builtins())\n+\t\treturn;\n+\ttrace2_enabled = 1;\n+\n+\ttr2_sid_get();\n+\n+\tatexit(tr2main_atexit_handler);\n+\tsigchain_push(SIGPIPE, tr2main_signal_handler);\n+\ttr2tls_init();\n+\n+\t/*\n+\t * Emit 'version' message on each active builtin target.\n+\t */\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_version_fl)\n+\t\t\ttgt_j->pfn_version_fl(file, line);\n+}\n+\n+int trace2_is_enabled(void)\n+{\n+\treturn trace2_enabled;\n+}\n+\n+void trace2_cmd_start_fl(const char *file, int line, const char **argv)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_start_fl)\n+\t\t\ttgt_j->pfn_start_fl(file, line, argv);\n+}\n+\n+int trace2_cmd_exit_fl(const char *file, int line, int code)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tcode &= 0xff;\n+\n+\tif (!trace2_enabled)\n+\t\treturn code;\n+\n+\ttr2main_exit_code = code;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_exit_fl)\n+\t\t\ttgt_j->pfn_exit_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t   code);\n+\n+\treturn code;\n+}\n+\n+void trace2_cmd_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t    va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\t/*\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy (because an 'ap' can only be walked once).\n+\t */\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_error_va_fl)\n+\t\t\ttgt_j->pfn_error_va_fl(file, line, fmt, ap);\n+}\n+\n+void trace2_cmd_path_fl(const char *file, int line, const char *pathname)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_command_path_fl)\n+\t\t\ttgt_j->pfn_command_path_fl(file, line, pathname);\n+}\n+\n+void trace2_cmd_name_fl(const char *file, int line, const char *name)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tconst char *hierarchy;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\ttr2_cmd_name_append_hierarchy(name);\n+\thierarchy = tr2_cmd_name_get_hierarchy();\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_command_name_fl)\n+\t\t\ttgt_j->pfn_command_name_fl(file, line, name,\n+\t\t\t\t\t\t   hierarchy);\n+}\n+\n+void trace2_cmd_mode_fl(const char *file, int line, const char *mode)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_command_mode_fl)\n+\t\t\ttgt_j->pfn_command_mode_fl(file, line, mode);\n+}\n+\n+void trace2_cmd_alias_fl(const char *file, int line, const char *alias,\n+\t\t\t const char **argv)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_alias_fl)\n+\t\t\ttgt_j->pfn_alias_fl(file, line, alias, argv);\n+}\n+\n+void trace2_cmd_list_config_fl(const char *file, int line)\n+{\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\ttr2_cfg_list_config_fl(file, line);\n+}\n+\n+void trace2_cmd_set_config_fl(const char *file, int line, const char *key,\n+\t\t\t      const char *value)\n+{\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\ttr2_cfg_set_fl(file, line, key, value);\n+}\n+\n+void trace2_child_start_fl(const char *file, int line,\n+\t\t\t   struct child_process *cmd)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tcmd->trace2_child_id = tr2tls_locked_increment(&tr2_next_child_id);\n+\tcmd->trace2_child_us_start = us_now;\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_child_start_fl)\n+\t\t\ttgt_j->pfn_child_start_fl(file, line,\n+\t\t\t\t\t\t  us_elapsed_absolute, cmd);\n+}\n+\n+void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd,\n+\t\t\t  int child_exit_code)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_child;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tif (cmd->trace2_child_us_start)\n+\t\tus_elapsed_child = us_now - cmd->trace2_child_us_start;\n+\telse\n+\t\tus_elapsed_child = 0;\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_child_exit_fl)\n+\t\t\ttgt_j->pfn_child_exit_fl(file, line,\n+\t\t\t\t\t\t us_elapsed_absolute,\n+\t\t\t\t\t\t cmd->trace2_child_id, cmd->pid,\n+\t\t\t\t\t\t child_exit_code,\n+\t\t\t\t\t\t us_elapsed_child);\n+}\n+\n+int trace2_exec_fl(const char *file, int line, const char *exe,\n+\t\t   const char **argv)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tint exec_id;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn -1;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\texec_id = tr2tls_locked_increment(&tr2_next_exec_id);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_exec_fl)\n+\t\t\ttgt_j->pfn_exec_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t   exec_id, exe, argv);\n+\n+\treturn exec_id;\n+}\n+\n+void trace2_exec_result_fl(const char *file, int line, int exec_id, int code)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_exec_result_fl)\n+\t\t\ttgt_j->pfn_exec_result_fl(\n+\t\t\t\tfile, line, us_elapsed_absolute, exec_id, code);\n+}\n+\n+void trace2_thread_start_fl(const char *file, int line, const char *thread_name)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tif (tr2tls_is_main_thread()) {\n+\t\t/*\n+\t\t * We should only be called from the new thread's thread-proc,\n+\t\t * so this is technically a bug.  But in those cases where the\n+\t\t * main thread also runs the thread-proc function (or when we\n+\t\t * are built with threading disabled), we need to allow it.\n+\t\t *\n+\t\t * Convert this call to a region-enter so the nesting looks\n+\t\t * correct.\n+\t\t */\n+\t\ttrace2_region_enter_printf_fl(file, line, NULL, NULL, NULL,\n+\t\t\t\t\t      \"thread-proc on main: %s\",\n+\t\t\t\t\t      thread_name);\n+\t\treturn;\n+\t}\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\ttr2tls_create_self(thread_name);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_thread_start_fl)\n+\t\t\ttgt_j->pfn_thread_start_fl(file, line,\n+\t\t\t\t\t\t   us_elapsed_absolute);\n+}\n+\n+void trace2_thread_exit_fl(const char *file, int line)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_thread;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tif (tr2tls_is_main_thread()) {\n+\t\t/*\n+\t\t * We should only be called from the exiting thread's\n+\t\t * thread-proc, so this is technically a bug.  But in\n+\t\t * those cases where the main thread also runs the\n+\t\t * thread-proc function (or when we are built with\n+\t\t * threading disabled), we need to allow it.\n+\t\t *\n+\t\t * Convert this call to a region-leave so the nesting\n+\t\t * looks correct.\n+\t\t */\n+\t\ttrace2_region_leave_printf_fl(file, line, NULL, NULL, NULL,\n+\t\t\t\t\t      \"thread-proc on main\");\n+\t\treturn;\n+\t}\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Clear any unbalanced regions and then get the relative time\n+\t * for the outer-most region (which we pushed when the thread\n+\t * started).  This gives us the run time of the thread.\n+\t */\n+\ttr2tls_pop_unwind_self();\n+\tus_elapsed_thread = tr2tls_region_elasped_self(us_now);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_thread_exit_fl)\n+\t\t\ttgt_j->pfn_thread_exit_fl(file, line,\n+\t\t\t\t\t\t  us_elapsed_absolute,\n+\t\t\t\t\t\t  us_elapsed_thread);\n+\n+\ttr2tls_unset_self();\n+}\n+\n+void trace2_def_param_fl(const char *file, int line, const char *param,\n+\t\t\t const char *value)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_param_fl)\n+\t\t\ttgt_j->pfn_param_fl(file, line, param, value);\n+}\n+\n+void trace2_def_repo_fl(const char *file, int line, struct repository *repo)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tif (repo->trace2_repo_id)\n+\t\treturn;\n+\n+\trepo->trace2_repo_id = tr2tls_locked_increment(&tr2_next_repo_id);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_repo_fl)\n+\t\t\ttgt_j->pfn_repo_fl(file, line, repo);\n+}\n+\n+void trace2_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category, const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Print the region-enter message at the current nesting\n+\t * (indentation) level and then push a new level.\n+\t *\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy.\n+\t */\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_region_enter_printf_va_fl)\n+\t\t\ttgt_j->pfn_region_enter_printf_va_fl(\n+\t\t\t\tfile, line, us_elapsed_absolute, category,\n+\t\t\t\tlabel, repo, fmt, ap);\n+\n+\ttr2tls_push_self(us_now);\n+}\n+\n+void trace2_region_enter_fl(const char *file, int line, const char *category,\n+\t\t\t    const char *label, const struct repository *repo)\n+{\n+\ttrace2_region_enter_printf_va_fl(file, line, category, label, repo,\n+\t\t\t\t\t NULL, NULL);\n+}\n+\n+void trace2_region_enter_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category, const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_enter_printf_va_fl(file, line, category, label, repo, fmt,\n+\t\t\t\t\t ap);\n+\tva_end(ap);\n+}\n+\n+#ifndef HAVE_VARIADIC_MACROS\n+void trace2_region_enter_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo, const char *fmt,\n+\t\t\t\t...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_enter_printf_va_fl(NULL, 0, category, label, repo, fmt,\n+\t\t\t\t\t ap);\n+\tva_end(ap);\n+}\n+#endif\n+\n+void trace2_region_leave_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category, const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_region;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Get the elapsed time in the current region before we\n+\t * pop it off the stack.  Pop the stack.  And then print\n+\t * the perf message at the new (shallower) level so that\n+\t * it lines up with the corresponding push/enter.\n+\t */\n+\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n+\n+\ttr2tls_pop_self();\n+\n+\t/*\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy.\n+\t */\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_region_leave_printf_va_fl)\n+\t\t\ttgt_j->pfn_region_leave_printf_va_fl(\n+\t\t\t\tfile, line, us_elapsed_absolute,\n+\t\t\t\tus_elapsed_region, category, label, repo, fmt,\n+\t\t\t\tap);\n+}\n+\n+void trace2_region_leave_fl(const char *file, int line, const char *category,\n+\t\t\t    const char *label, const struct repository *repo)\n+{\n+\ttrace2_region_leave_printf_va_fl(file, line, category, label, repo,\n+\t\t\t\t\t NULL, NULL);\n+}\n+\n+void trace2_region_leave_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category, const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_leave_printf_va_fl(file, line, category, label, repo, fmt,\n+\t\t\t\t\t ap);\n+\tva_end(ap);\n+}\n+\n+#ifndef HAVE_VARIADIC_MACROS\n+void trace2_region_leave_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo, const char *fmt,\n+\t\t\t\t...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_leave_printf_va_fl(NULL, 0, category, label, repo, fmt,\n+\t\t\t\t\t ap);\n+\tva_end(ap);\n+}\n+#endif\n+\n+void trace2_data_string_fl(const char *file, int line, const char *category,\n+\t\t\t   const struct repository *repo, const char *key,\n+\t\t\t   const char *value)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_region;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_data_fl)\n+\t\t\ttgt_j->pfn_data_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t   us_elapsed_region, category, repo,\n+\t\t\t\t\t   key, value);\n+}\n+\n+void trace2_data_intmax_fl(const char *file, int line, const char *category,\n+\t\t\t   const struct repository *repo, const char *key,\n+\t\t\t   intmax_t value)\n+{\n+\tstruct strbuf buf_string = STRBUF_INIT;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tstrbuf_addf(&buf_string, \"%\" PRIdMAX, value);\n+\ttrace2_data_string_fl(file, line, category, repo, key, buf_string.buf);\n+\tstrbuf_release(&buf_string);\n+}\n+\n+void trace2_data_json_fl(const char *file, int line, const char *category,\n+\t\t\t const struct repository *repo, const char *key,\n+\t\t\t const struct json_writer *value)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_region;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_data_fl)\n+\t\t\ttgt_j->pfn_data_json_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t\tus_elapsed_region, category,\n+\t\t\t\t\t\trepo, key, value);\n+}\n+\n+void trace2_printf_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy.\n+\t */\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_printf_va_fl)\n+\t\t\ttgt_j->pfn_printf_va_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t\tfmt, ap);\n+}\n+\n+void trace2_printf_fl(const char *file, int line, const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_printf_va_fl(file, line, fmt, ap);\n+\tva_end(ap);\n+}\n+\n+#ifndef HAVE_VARIADIC_MACROS\n+void trace2_printf(const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_printf_va_fl(NULL, 0, fmt, ap);\n+\tva_end(ap);\n+}\n+#endif\ndiff --git a/trace2.h b/trace2.h\nnew file mode 100644\nindex 0000000000..fce9891f53\n--- /dev/null\n+++ b/trace2.h\n@@ -0,0 +1,371 @@\n+#ifndef TRACE2_H\n+#define TRACE2_H\n+\n+struct child_process;\n+struct repository;\n+struct json_writer;\n+\n+/*\n+ * The public TRACE2 routines are grouped into the following groups:\n+ *\n+ * [] trace2_initialize -- initialization.\n+ * [] trace2_cmd_*      -- emit command/control messages.\n+ * [] trace2_child*     -- emit child start/stop messages.\n+ * [] trace2_exec*      -- emit exec start/stop messages.\n+ * [] trace2_thread*    -- emit thread start/stop messages.\n+ * [] trace2_def*       -- emit definition/parameter mesasges.\n+ * [] trace2_region*    -- emit region nesting messages.\n+ * [] trace2_data*      -- emit region/thread/repo data messages.\n+ * [] trace2_printf*    -- legacy trace[1] messages.\n+ */\n+\n+/*\n+ * Initialize TRACE2 tracing facility if any of the builtin TRACE2\n+ * targets are enabled in the environment.  Emits a 'version' event.\n+ *\n+ * Cleanup/Termination is handled automatically by a registered\n+ * atexit() routine.\n+ */\n+void trace2_initialize_fl(const char *file, int line);\n+\n+#define trace2_initialize() trace2_initialize_fl(__FILE__, __LINE__)\n+\n+/*\n+ * Return true if trace2 is enabled.\n+ */\n+int trace2_is_enabled(void);\n+\n+/*\n+ * Emit a 'start' event with the original (unmodified) argv.\n+ */\n+void trace2_cmd_start_fl(const char *file, int line, const char **argv);\n+\n+#define trace2_cmd_start(argv) trace2_cmd_start_fl(__FILE__, __LINE__, (argv))\n+\n+/*\n+ * Emit an 'exit' event.\n+ *\n+ * Write the exit-code that will be passed to exit() or returned\n+ * from main().\n+ *\n+ * Use this prior to actually calling exit().\n+ * See \"#define exit()\" in git-compat-util.h\n+ */\n+int trace2_cmd_exit_fl(const char *file, int line, int code);\n+\n+#define trace2_cmd_exit(code) (trace2_cmd_exit_fl(__FILE__, __LINE__, (code)))\n+\n+/*\n+ * Emit an 'error' event.\n+ *\n+ * Write an error message to the TRACE2 targets.\n+ */\n+void trace2_cmd_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t    va_list ap);\n+\n+#define trace2_cmd_error_va(fmt, ap) \\\n+\ttrace2_cmd_error_va_fl(__FILE__, __LINE__, (fmt), (ap))\n+\n+/*\n+ * Emit a 'pathname' event with the canonical pathname of the current process\n+ * This gives post-processors a simple field to identify the command without\n+ * having to parse the argv.  For example, to distinguish invocations from\n+ * installed versus debug executables.\n+ */\n+void trace2_cmd_path_fl(const char *file, int line, const char *pathname);\n+\n+#define trace2_cmd_path(p) trace2_cmd_path_fl(__FILE__, __LINE__, (p))\n+\n+/*\n+ * Emit a 'cmd_name' event with the canonical name of the command.\n+ * This gives post-processors a simple field to identify the command\n+ * without having to parse the argv.\n+ */\n+void trace2_cmd_name_fl(const char *file, int line, const char *name);\n+\n+#define trace2_cmd_name(v) trace2_cmd_name_fl(__FILE__, __LINE__, (v))\n+\n+/*\n+ * Emit a 'cmd_mode' event to further describe the command being run.\n+ * For example, \"checkout\" can checkout a single file or can checkout a\n+ * different branch.  This gives post-processors a simple field to compare\n+ * equivalent commands without having to parse the argv.\n+ */\n+void trace2_cmd_mode_fl(const char *file, int line, const char *mode);\n+\n+#define trace2_cmd_mode(sv) trace2_cmd_mode_fl(__FILE__, __LINE__, (sv))\n+\n+/*\n+ * Emit an 'alias' expansion event.\n+ */\n+void trace2_cmd_alias_fl(const char *file, int line, const char *alias,\n+\t\t\t const char **argv);\n+\n+#define trace2_cmd_alias(alias, argv) \\\n+\ttrace2_cmd_alias_fl(__FILE__, __LINE__, (alias), (argv))\n+\n+/*\n+ * Emit one or more 'def_param' events for \"interesting\" configuration\n+ * settings.\n+ *\n+ * The environment variable \"GIT_TR2_CONFIG_PARAMS\" can be set to a\n+ * list of patterns considered important.  For example:\n+ *\n+ *    GIT_TR2_CONFIG_PARAMS=\"core.*,remote.*.url\"\n+ *\n+ * Note: this routine does a read-only iteration on the config data\n+ * (using read_early_config()), so it must not be called until enough\n+ * of the process environment has been established.  This includes the\n+ * location of the git and worktree directories, expansion of any \"-c\"\n+ * and \"-C\" command line options, and etc.\n+ */\n+void trace2_cmd_list_config_fl(const char *file, int line);\n+\n+#define trace2_cmd_list_config() trace2_cmd_list_config_fl(__FILE__, __LINE__)\n+\n+/*\n+ * Emit a \"def_param\" event for the given config key/value pair IF\n+ * we consider the key to be \"interesting\".\n+ *\n+ * Use this for new/updated config settings created/updated after\n+ * trace2_cmd_list_config() is called.\n+ */\n+void trace2_cmd_set_config_fl(const char *file, int line, const char *key,\n+\t\t\t      const char *value);\n+\n+#define trace2_cmd_set_config(k, v) \\\n+\ttrace2_cmd_set_config_fl(__FILE__, __LINE__, (k), (v))\n+\n+/*\n+ * Emit a 'child_start' event prior to spawning a child process.\n+ *\n+ * Before calling optionally set \"cmd->trace2_child_class\" to a string\n+ * describing the type of the child process.  For example, \"editor\" or\n+ * \"pager\".\n+ */\n+void trace2_child_start_fl(const char *file, int line,\n+\t\t\t   struct child_process *cmd);\n+\n+#define trace2_child_start(cmd) trace2_child_start_fl(__FILE__, __LINE__, (cmd))\n+\n+/*\n+ * Emit a 'child_exit' event after the child process completes.\n+ */\n+void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd,\n+\t\t\t  int child_exit_code);\n+\n+#define trace2_child_exit(cmd, code) \\\n+\ttrace2_child_exit_fl(__FILE__, __LINE__, (cmd), (code))\n+\n+/*\n+ * Emit an 'exec' event prior to calling one of exec(), execv(),\n+ * execvp(), and etc.  On Unix-derived systems, this will be the\n+ * last event emitted for the current process, unless the exec\n+ * fails.  On Windows, exec() behaves like 'child_start' and a\n+ * waitpid(), so additional events may be emitted.\n+ *\n+ * Returns the \"exec_id\".\n+ */\n+int trace2_exec_fl(const char *file, int line, const char *exe,\n+\t\t   const char **argv);\n+\n+#define trace2_exec(exe, argv) trace2_exec_fl(__FILE__, __LINE__, (exe), (argv))\n+\n+/*\n+ * Emit an 'exec_result' when possible.  On Unix-derived systems,\n+ * this should be called after exec() returns (which only happens\n+ * when there is an error starting the new process).  On Windows,\n+ * this should be called after the waitpid().\n+ *\n+ * The \"exec_id\" should be the value returned from trace2_exec().\n+ */\n+void trace2_exec_result_fl(const char *file, int line, int exec_id, int code);\n+\n+#define trace2_exec_result(id, code) \\\n+\ttrace2_exec_result_fl(__FILE__, __LINE__, (id), (code))\n+\n+/*\n+ * Emit a 'thread_start' event.  This must be called from inside the\n+ * thread-proc to set up the trace2 TLS data for the thread.\n+ *\n+ * Thread names should be descriptive, like \"preload_index\".\n+ * Thread names will be decorated with an instance number automatically.\n+ */\n+void trace2_thread_start_fl(const char *file, int line,\n+\t\t\t    const char *thread_name);\n+\n+#define trace2_thread_start(thread_name) \\\n+\ttrace2_thread_start_fl(__FILE__, __LINE__, (thread_name))\n+\n+/*\n+ * Emit a 'thread_exit' event.  This must be called from inside the\n+ * thread-proc to report thread-specific data and cleanup TLS data\n+ * for the thread.\n+ */\n+void trace2_thread_exit_fl(const char *file, int line);\n+\n+#define trace2_thread_exit() trace2_thread_exit_fl(__FILE__, __LINE__)\n+\n+/*\n+ * Emit a 'param' event.\n+ *\n+ * Write a \"<param> = <value>\" pair describing some aspect of the\n+ * run such as an important configuration setting or command line\n+ * option that significantly changes command behavior.\n+ */\n+void trace2_def_param_fl(const char *file, int line, const char *param,\n+\t\t\t const char *value);\n+\n+#define trace2_def_param(param, value) \\\n+\ttrace2_def_param_fl(__FILE__, __LINE__, (param), (value))\n+\n+/*\n+ * Tell trace2 about a newly instantiated repo object and assign\n+ * a trace2-repo-id to be used in subsequent activity events.\n+ *\n+ * Emits a 'worktree' event for this repo instance.\n+ */\n+void trace2_def_repo_fl(const char *file, int line, struct repository *repo);\n+\n+#define trace2_def_repo(repo) trace2_def_repo_fl(__FILE__, __LINE__, repo)\n+\n+/*\n+ * Emit a 'region_enter' event for <category>.<label> with optional\n+ * repo-id and printf message.\n+ *\n+ * Enter a new nesting level on the current thread and remember the\n+ * current time.  This controls the indenting of all subsequent events\n+ * on this thread.\n+ */\n+void trace2_region_enter_fl(const char *file, int line, const char *category,\n+\t\t\t    const char *label, const struct repository *repo);\n+\n+#define trace2_region_enter(category, label, repo) \\\n+\ttrace2_region_enter_fl(__FILE__, __LINE__, (category), (label), (repo))\n+\n+void trace2_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category, const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap);\n+\n+#define trace2_region_enter_printf_va(category, label, repo, fmt, ap)    \\\n+\ttrace2_region_enter_printf_va_fl(__FILE__, __LINE__, (category), \\\n+\t\t\t\t\t (label), (repo), (fmt), (ap))\n+\n+void trace2_region_enter_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category, const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...);\n+\n+#ifdef HAVE_VARIADIC_MACROS\n+#define trace2_region_enter_printf(category, label, repo, ...)                 \\\n+\ttrace2_region_enter_printf_fl(__FILE__, __LINE__, (category), (label), \\\n+\t\t\t\t      (repo), __VA_ARGS__)\n+#else\n+/* clang-format off */\n+__attribute__((format (region_enter_printf, 4, 5)))\n+void trace2_region_enter_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo, const char *fmt,\n+\t\t\t\t...);\n+/* clang-format on */\n+#endif\n+\n+/*\n+ * Emit a 'region_leave' event for <category>.<label> with optional\n+ * repo-id and printf message.\n+ *\n+ * Leave current nesting level and report the elapsed time spent\n+ * in this nesting level.\n+ */\n+void trace2_region_leave_fl(const char *file, int line, const char *category,\n+\t\t\t    const char *label, const struct repository *repo);\n+\n+#define trace2_region_leave(category, label, repo) \\\n+\ttrace2_region_leave_fl(__FILE__, __LINE__, (category), (label), (repo))\n+\n+void trace2_region_leave_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category, const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap);\n+\n+#define trace2_region_leave_printf_va(category, label, repo, fmt, ap)    \\\n+\ttrace2_region_leave_printf_va_fl(__FILE__, __LINE__, (category), \\\n+\t\t\t\t\t (label), (repo), (fmt), (ap))\n+\n+void trace2_region_leave_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category, const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...);\n+\n+#ifdef HAVE_VARIADIC_MACROS\n+#define trace2_region_leave_printf(category, label, repo, ...)                 \\\n+\ttrace2_region_leave_printf_fl(__FILE__, __LINE__, (category), (label), \\\n+\t\t\t\t      (repo), __VA_ARGS__)\n+#else\n+/* clang-format off */\n+__attribute__((format (region_leave_printf, 4, 5)))\n+void trace2_region_leave_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo, const char *fmt,\n+\t\t\t\t...);\n+/* clang-format on */\n+#endif\n+\n+/*\n+ * Emit a key-value pair 'data' event of the form <category>.<key> = <value>.\n+ * This event implicitly contains information about thread, nesting region,\n+ * and optional repo-id.\n+ *\n+ * On event-based TRACE2 targets, this generates a 'data' event suitable\n+ * for post-processing.  On printf-based TRACE2 targets, this is converted\n+ * into a fixed-format printf message.\n+ */\n+void trace2_data_string_fl(const char *file, int line, const char *category,\n+\t\t\t   const struct repository *repo, const char *key,\n+\t\t\t   const char *value);\n+\n+#define trace2_data_string(category, repo, key, value)                       \\\n+\ttrace2_data_string_fl(__FILE__, __LINE__, (category), (repo), (key), \\\n+\t\t\t      (value))\n+\n+void trace2_data_intmax_fl(const char *file, int line, const char *category,\n+\t\t\t   const struct repository *repo, const char *key,\n+\t\t\t   intmax_t value);\n+\n+#define trace2_data_intmax(category, repo, key, value)                       \\\n+\ttrace2_data_intmax_fl(__FILE__, __LINE__, (category), (repo), (key), \\\n+\t\t\t      (value))\n+\n+void trace2_data_json_fl(const char *file, int line, const char *category,\n+\t\t\t const struct repository *repo, const char *key,\n+\t\t\t const struct json_writer *jw);\n+\n+#define trace2_data_json(category, repo, key, value)                       \\\n+\ttrace2_data_json_fl(__FILE__, __LINE__, (category), (repo), (key), \\\n+\t\t\t    (value))\n+\n+/*\n+ * Emit a 'printf' event.\n+ *\n+ * Write an arbitrary formatted message to the TRACE2 targets.  These\n+ * text messages should be considered as human-readable strings without\n+ * any formatting guidelines.  Post-processors may choose to ignore\n+ * them.\n+ */\n+void trace2_printf_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t va_list ap);\n+\n+#define trace2_printf_va(fmt, ap) \\\n+\ttrace2_printf_va_fl(__FILE__, __LINE__, (fmt), (ap))\n+\n+void trace2_printf_fl(const char *file, int line, const char *fmt, ...);\n+\n+#ifdef HAVE_VARIADIC_MACROS\n+#define trace2_printf(...) trace2_printf_fl(__FILE__, __LINE__, __VA_ARGS__)\n+#else\n+/* clang-format off */\n+__attribute__((format (printf, 1, 2)))\n+void trace2_printf(const char *fmt, ...);\n+/* clang-format on */\n+#endif\n+\n+#endif /* TRACE2_H */\ndiff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c\nnew file mode 100644\nindex 0000000000..b329921ac5\n--- /dev/null\n+++ b/trace2/tr2_cfg.c\n@@ -0,0 +1,90 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"tr2_cfg.h\"\n+\n+#define TR2_ENVVAR_CFG_PARAM \"GIT_TR2_CONFIG_PARAMS\"\n+\n+static struct strbuf **tr2_cfg_patterns;\n+static int tr2_cfg_count_patterns;\n+static int tr2_cfg_loaded;\n+\n+/*\n+ * Parse a string containing a comma-delimited list of config keys\n+ * or wildcard patterns into a list of strbufs.\n+ */\n+static int tr2_cfg_load_patterns(void)\n+{\n+\tstruct strbuf **s;\n+\tconst char *envvar;\n+\n+\tif (tr2_cfg_loaded)\n+\t\treturn tr2_cfg_count_patterns;\n+\ttr2_cfg_loaded = 1;\n+\n+\tenvvar = getenv(TR2_ENVVAR_CFG_PARAM);\n+\tif (!envvar || !*envvar)\n+\t\treturn tr2_cfg_count_patterns;\n+\n+\ttr2_cfg_patterns = strbuf_split_buf(envvar, strlen(envvar), ',', -1);\n+\tfor (s = tr2_cfg_patterns; *s; s++) {\n+\t\tstruct strbuf *buf = *s;\n+\n+\t\tif (buf->len && buf->buf[buf->len - 1] == ',')\n+\t\t\tstrbuf_setlen(buf, buf->len - 1);\n+\t\tstrbuf_trim_trailing_newline(*s);\n+\t\tstrbuf_trim(*s);\n+\t}\n+\n+\ttr2_cfg_count_patterns = s - tr2_cfg_patterns;\n+\treturn tr2_cfg_count_patterns;\n+}\n+\n+void tr2_cfg_free_patterns(void)\n+{\n+\tif (tr2_cfg_patterns)\n+\t\tstrbuf_list_free(tr2_cfg_patterns);\n+\ttr2_cfg_count_patterns = 0;\n+\ttr2_cfg_loaded = 0;\n+}\n+\n+struct tr2_cfg_data {\n+\tconst char *file;\n+\tint line;\n+};\n+\n+/*\n+ * See if the given config key matches any of our patterns of interest.\n+ */\n+static int tr2_cfg_cb(const char *key, const char *value, void *d)\n+{\n+\tstruct strbuf **s;\n+\tstruct tr2_cfg_data *data = (struct tr2_cfg_data *)d;\n+\n+\tfor (s = tr2_cfg_patterns; *s; s++) {\n+\t\tstruct strbuf *buf = *s;\n+\t\tint wm = wildmatch(buf->buf, key, WM_CASEFOLD);\n+\t\tif (wm == WM_MATCH) {\n+\t\t\ttrace2_def_param_fl(data->file, data->line, key, value);\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void tr2_cfg_list_config_fl(const char *file, int line)\n+{\n+\tstruct tr2_cfg_data data = { file, line };\n+\n+\tif (tr2_cfg_load_patterns() > 0)\n+\t\tread_early_config(tr2_cfg_cb, &data);\n+}\n+\n+void tr2_cfg_set_fl(const char *file, int line, const char *key,\n+\t\t    const char *value)\n+{\n+\tstruct tr2_cfg_data data = { file, line };\n+\n+\tif (tr2_cfg_load_patterns() > 0)\n+\t\ttr2_cfg_cb(key, value, &data);\n+}\ndiff --git a/trace2/tr2_cfg.h b/trace2/tr2_cfg.h\nnew file mode 100644\nindex 0000000000..d9c98f64dd\n--- /dev/null\n+++ b/trace2/tr2_cfg.h\n@@ -0,0 +1,19 @@\n+#ifndef TR2_CFG_H\n+#define TR2_CFG_H\n+\n+/*\n+ * Iterate over all config settings and emit 'def_param' events for the\n+ * \"interesting\" ones to TRACE2.\n+ */\n+void tr2_cfg_list_config_fl(const char *file, int line);\n+\n+/*\n+ * Emit a \"def_param\" event for the given key/value pair IF we consider\n+ * the key to be \"interesting\".\n+ */\n+void tr2_cfg_set_fl(const char *file, int line, const char *key,\n+\t\t    const char *value);\n+\n+void tr2_cfg_free_patterns(void);\n+\n+#endif /* TR2_CFG_H */\ndiff --git a/trace2/tr2_cmd_name.c b/trace2/tr2_cmd_name.c\nnew file mode 100644\nindex 0000000000..e999592b4c\n--- /dev/null\n+++ b/trace2/tr2_cmd_name.c\n@@ -0,0 +1,30 @@\n+#include \"cache.h\"\n+#include \"trace2/tr2_cmd_name.h\"\n+\n+#define TR2_ENVVAR_PARENT_NAME \"GIT_TR2_PARENT_NAME\"\n+\n+static struct strbuf tr2cmdname_hierarchy = STRBUF_INIT;\n+\n+void tr2_cmd_name_append_hierarchy(const char *name)\n+{\n+\tconst char *parent_name = getenv(TR2_ENVVAR_PARENT_NAME);\n+\n+\tstrbuf_reset(&tr2cmdname_hierarchy);\n+\tif (parent_name && *parent_name) {\n+\t\tstrbuf_addstr(&tr2cmdname_hierarchy, parent_name);\n+\t\tstrbuf_addch(&tr2cmdname_hierarchy, '/');\n+\t}\n+\tstrbuf_addstr(&tr2cmdname_hierarchy, name);\n+\n+\tsetenv(TR2_ENVVAR_PARENT_NAME, tr2cmdname_hierarchy.buf, 1);\n+}\n+\n+const char *tr2_cmd_name_get_hierarchy(void)\n+{\n+\treturn tr2cmdname_hierarchy.buf;\n+}\n+\n+void tr2_cmd_name_release(void)\n+{\n+\tstrbuf_release(&tr2cmdname_hierarchy);\n+}\ndiff --git a/trace2/tr2_cmd_name.h b/trace2/tr2_cmd_name.h\nnew file mode 100644\nindex 0000000000..ab70b67a8e\n--- /dev/null\n+++ b/trace2/tr2_cmd_name.h\n@@ -0,0 +1,24 @@\n+#ifndef TR2_CMD_NAME_H\n+#define TR2_CMD_NAME_H\n+\n+/*\n+ * Append the current command name to the list being maintained\n+ * in the environment.\n+ *\n+ * The hierarchy for a top-level git command is just the current\n+ * command name.  For a child git process, the hierarchy includes the\n+ * names of the parent processes.\n+ *\n+ * The hierarchy for the current process will be exported to the\n+ * environment and inherited by child processes.\n+ */\n+void tr2_cmd_name_append_hierarchy(const char *name);\n+\n+/*\n+ * Get the command name hierarchy for the current process.\n+ */\n+const char *tr2_cmd_name_get_hierarchy(void);\n+\n+void tr2_cmd_name_release(void);\n+\n+#endif /* TR2_CMD_NAME_H */\ndiff --git a/trace2/tr2_dst.c b/trace2/tr2_dst.c\nnew file mode 100644\nindex 0000000000..b4f2b40e61\n--- /dev/null\n+++ b/trace2/tr2_dst.c\n@@ -0,0 +1,197 @@\n+#include \"cache.h\"\n+#include \"trace2/tr2_dst.h\"\n+\n+/*\n+ * If a Trace2 target cannot be opened for writing, we should issue a\n+ * warning to stderr, but this is very annoying if the target is a pipe\n+ * or socket and beyond the user's control -- especially since every\n+ * git command (and sub-command) will print the message.  So we silently\n+ * eat these warnings and just discard the trace data.\n+ *\n+ * Enable the following environment variable to see these warnings.\n+ */\n+#define TR2_ENVVAR_DST_DEBUG \"GIT_TR2_DST_DEBUG\"\n+\n+static int tr2_dst_want_warning(void)\n+{\n+\tstatic int tr2env_dst_debug = -1;\n+\n+\tif (tr2env_dst_debug == -1) {\n+\t\tconst char *env_value = getenv(TR2_ENVVAR_DST_DEBUG);\n+\t\tif (!env_value || !*env_value)\n+\t\t\ttr2env_dst_debug = 0;\n+\t\telse\n+\t\t\ttr2env_dst_debug = atoi(env_value) > 0;\n+\t}\n+\n+\treturn tr2env_dst_debug;\n+}\n+\n+void tr2_dst_trace_disable(struct tr2_dst *dst)\n+{\n+\tif (dst->need_close)\n+\t\tclose(dst->fd);\n+\tdst->fd = 0;\n+\tdst->initialized = 1;\n+\tdst->need_close = 0;\n+}\n+\n+static int tr2_dst_try_path(struct tr2_dst *dst, const char *tgt_value)\n+{\n+\tint fd = open(tgt_value, O_WRONLY | O_APPEND | O_CREAT, 0666);\n+\tif (fd == -1) {\n+\t\tif (tr2_dst_want_warning())\n+\t\t\twarning(\"trace2: could not open '%s' for '%s' tracing: %s\",\n+\t\t\t\ttgt_value, dst->env_var_name, strerror(errno));\n+\n+\t\ttr2_dst_trace_disable(dst);\n+\t\treturn 0;\n+\t}\n+\n+\tdst->fd = fd;\n+\tdst->need_close = 1;\n+\tdst->initialized = 1;\n+\n+\treturn dst->fd;\n+}\n+\n+#ifndef NO_UNIX_SOCKETS\n+#define PREFIX_AF_UNIX \"af_unix:\"\n+#define PREFIX_AF_UNIX_LEN (8)\n+\n+static int tr2_dst_try_unix_domain_socket(struct tr2_dst *dst,\n+\t\t\t\t\t  const char *tgt_value)\n+{\n+\tint fd;\n+\tstruct sockaddr_un sa;\n+\tconst char *path = tgt_value + PREFIX_AF_UNIX_LEN;\n+\tint path_len = strlen(path);\n+\n+\tif (!is_absolute_path(path) || path_len >= sizeof(sa.sun_path)) {\n+\t\tif (tr2_dst_want_warning())\n+\t\t\twarning(\"trace2: invalid AF_UNIX path '%s' for '%s' tracing\",\n+\t\t\t\tpath, dst->env_var_name);\n+\n+\t\ttr2_dst_trace_disable(dst);\n+\t\treturn 0;\n+\t}\n+\n+\tsa.sun_family = AF_UNIX;\n+\tstrlcpy(sa.sun_path, path, sizeof(sa.sun_path));\n+\tif ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1 ||\n+\t    connect(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {\n+\t\tif (tr2_dst_want_warning())\n+\t\t\twarning(\"trace2: could not connect to socket '%s' for '%s' tracing: %s\",\n+\t\t\t\tpath, dst->env_var_name, strerror(errno));\n+\n+\t\ttr2_dst_trace_disable(dst);\n+\t\treturn 0;\n+\t}\n+\n+\tdst->fd = fd;\n+\tdst->need_close = 1;\n+\tdst->initialized = 1;\n+\n+\treturn dst->fd;\n+}\n+#endif\n+\n+static void tr2_dst_malformed_warning(struct tr2_dst *dst,\n+\t\t\t\t      const char *tgt_value)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf, \"trace2: unknown trace value for '%s': '%s'\",\n+\t\t    dst->env_var_name, tgt_value);\n+\tstrbuf_addstr(\n+\t\t&buf,\n+\t\t\"\\n         If you want to trace into a file, then please set it\"\n+\t\t\"\\n         to an absolute pathname.\");\n+#ifndef NO_UNIX_SOCKETS\n+\tstrbuf_addstr(\n+\t\t&buf,\n+\t\t\"\\n         If you want to trace to a unix domain socket, prefix\"\n+\t\t\"\\n         the absolute pathname with \\\"af_unix:\\\".\");\n+#endif\n+\n+\twarning(\"%s\", buf.buf);\n+\n+\tstrbuf_release(&buf);\n+}\n+\n+int tr2_dst_get_trace_fd(struct tr2_dst *dst)\n+{\n+\tconst char *tgt_value;\n+\n+\t/* don't open twice */\n+\tif (dst->initialized)\n+\t\treturn dst->fd;\n+\n+\tdst->initialized = 1;\n+\n+\ttgt_value = getenv(dst->env_var_name);\n+\n+\tif (!tgt_value || !strcmp(tgt_value, \"\") || !strcmp(tgt_value, \"0\") ||\n+\t    !strcasecmp(tgt_value, \"false\")) {\n+\t\tdst->fd = 0;\n+\t\treturn dst->fd;\n+\t}\n+\n+\tif (!strcmp(tgt_value, \"1\") || !strcasecmp(tgt_value, \"true\")) {\n+\t\tdst->fd = STDERR_FILENO;\n+\t\treturn dst->fd;\n+\t}\n+\n+\tif (strlen(tgt_value) == 1 && isdigit(*tgt_value)) {\n+\t\tdst->fd = atoi(tgt_value);\n+\t\treturn dst->fd;\n+\t}\n+\n+\tif (is_absolute_path(tgt_value))\n+\t\treturn tr2_dst_try_path(dst, tgt_value);\n+\n+#ifndef NO_UNIX_SOCKETS\n+\tif (!strncmp(tgt_value, PREFIX_AF_UNIX, PREFIX_AF_UNIX_LEN))\n+\t\treturn tr2_dst_try_unix_domain_socket(dst, tgt_value);\n+#endif\n+\n+\t/* Always warn about malformed values. */\n+\ttr2_dst_malformed_warning(dst, tgt_value);\n+\ttr2_dst_trace_disable(dst);\n+\treturn 0;\n+}\n+\n+int tr2_dst_trace_want(struct tr2_dst *dst)\n+{\n+\treturn !!tr2_dst_get_trace_fd(dst);\n+}\n+\n+void tr2_dst_write_line(struct tr2_dst *dst, struct strbuf *buf_line)\n+{\n+\tint fd = tr2_dst_get_trace_fd(dst);\n+\n+\tstrbuf_complete_line(buf_line); /* ensure final NL on buffer */\n+\n+\t/*\n+\t * We do not use write_in_full() because we do not want\n+\t * a short-write to try again.  We are using O_APPEND mode\n+\t * files and the kernel handles the atomic seek+write. If\n+\t * another thread or git process is concurrently writing to\n+\t * this fd or file, our remainder-write may not be contiguous\n+\t * with our initial write of this message.  And that will\n+\t * confuse readers.  So just don't bother.\n+\t *\n+\t * It is assumed that TRACE2 messages are short enough that\n+\t * the system can write them in 1 attempt and we won't see\n+\t * a short-write.\n+\t *\n+\t * If we get an IO error, just close the trace dst.\n+\t */\n+\tif (write(fd, buf_line->buf, buf_line->len) >= 0)\n+\t\treturn;\n+\n+\tif (tr2_dst_want_warning())\n+\t\twarning(\"unable to write trace to '%s': %s\", dst->env_var_name,\n+\t\t\tstrerror(errno));\n+\ttr2_dst_trace_disable(dst);\n+}\ndiff --git a/trace2/tr2_dst.h b/trace2/tr2_dst.h\nnew file mode 100644\nindex 0000000000..9a64f05b02\n--- /dev/null\n+++ b/trace2/tr2_dst.h\n@@ -0,0 +1,36 @@\n+#ifndef TR2_DST_H\n+#define TR2_DST_H\n+\n+struct strbuf;\n+\n+struct tr2_dst {\n+\tconst char *const env_var_name;\n+\tint fd;\n+\tunsigned int initialized : 1;\n+\tunsigned int need_close : 1;\n+};\n+\n+/*\n+ * Disable TRACE2 on the destination.  In TRACE2 a destination (DST)\n+ * wraps a file descriptor; it is associated with a TARGET which\n+ * defines the formatting.\n+ */\n+void tr2_dst_trace_disable(struct tr2_dst *dst);\n+\n+/*\n+ * Return the file descriptor for the DST.\n+ * If 0, the dst is closed or disabled.\n+ */\n+int tr2_dst_get_trace_fd(struct tr2_dst *dst);\n+\n+/*\n+ * Return true if the DST is opened for writing.\n+ */\n+int tr2_dst_trace_want(struct tr2_dst *dst);\n+\n+/*\n+ * Write a single line/message to the trace file.\n+ */\n+void tr2_dst_write_line(struct tr2_dst *dst, struct strbuf *buf_line);\n+\n+#endif /* TR2_DST_H */\ndiff --git a/trace2/tr2_sid.c b/trace2/tr2_sid.c\nnew file mode 100644\nindex 0000000000..984524a43c\n--- /dev/null\n+++ b/trace2/tr2_sid.c\n@@ -0,0 +1,67 @@\n+#include \"cache.h\"\n+#include \"trace2/tr2_sid.h\"\n+\n+#define TR2_ENVVAR_PARENT_SID \"GIT_TR2_PARENT_SID\"\n+\n+static struct strbuf tr2sid_buf = STRBUF_INIT;\n+static int tr2sid_nr_git_parents;\n+\n+/*\n+ * Compute a \"unique\" session id (SID) for the current process.  This allows\n+ * all events from this process to have a single label (much like a PID).\n+ *\n+ * Export this into our environment so that all child processes inherit it.\n+ *\n+ * If we were started by another git instance, use our parent's SID as a\n+ * prefix.  (This lets us track parent/child relationships even if there\n+ * is an intermediate shell process.)\n+ *\n+ * Additionally, count the number of nested git processes.\n+ */\n+static void tr2_sid_compute(void)\n+{\n+\tuint64_t us_now;\n+\tconst char *parent_sid;\n+\n+\tif (tr2sid_buf.len)\n+\t\treturn;\n+\n+\tparent_sid = getenv(TR2_ENVVAR_PARENT_SID);\n+\tif (parent_sid && *parent_sid) {\n+\t\tconst char *p;\n+\t\tfor (p = parent_sid; *p; p++)\n+\t\t\tif (*p == '/')\n+\t\t\t\ttr2sid_nr_git_parents++;\n+\n+\t\tstrbuf_addstr(&tr2sid_buf, parent_sid);\n+\t\tstrbuf_addch(&tr2sid_buf, '/');\n+\t\ttr2sid_nr_git_parents++;\n+\t}\n+\n+\tus_now = getnanotime() / 1000;\n+\tstrbuf_addf(&tr2sid_buf, \"%\" PRIuMAX \"-%\" PRIdMAX, (uintmax_t)us_now,\n+\t\t    (intmax_t)getpid());\n+\n+\tsetenv(TR2_ENVVAR_PARENT_SID, tr2sid_buf.buf, 1);\n+}\n+\n+const char *tr2_sid_get(void)\n+{\n+\tif (!tr2sid_buf.len)\n+\t\ttr2_sid_compute();\n+\n+\treturn tr2sid_buf.buf;\n+}\n+\n+int tr2_sid_depth(void)\n+{\n+\tif (!tr2sid_buf.len)\n+\t\ttr2_sid_compute();\n+\n+\treturn tr2sid_nr_git_parents;\n+}\n+\n+void tr2_sid_release(void)\n+{\n+\tstrbuf_release(&tr2sid_buf);\n+}\ndiff --git a/trace2/tr2_sid.h b/trace2/tr2_sid.h\nnew file mode 100644\nindex 0000000000..9bef321708\n--- /dev/null\n+++ b/trace2/tr2_sid.h\n@@ -0,0 +1,18 @@\n+#ifndef TR2_SID_H\n+#define TR2_SID_H\n+\n+/*\n+ * Get our session id. Compute if necessary.\n+ */\n+const char *tr2_sid_get(void);\n+\n+/*\n+ * Get our process depth.  A top-level git process invoked from the\n+ * command line will have depth=0.  A child git process will have\n+ * depth=1 and so on.\n+ */\n+int tr2_sid_depth(void);\n+\n+void tr2_sid_release(void);\n+\n+#endif /* TR2_SID_H */\ndiff --git a/trace2/tr2_tbuf.c b/trace2/tr2_tbuf.c\nnew file mode 100644\nindex 0000000000..0844910423\n--- /dev/null\n+++ b/trace2/tr2_tbuf.c\n@@ -0,0 +1,32 @@\n+#include \"cache.h\"\n+#include \"tr2_tbuf.h\"\n+\n+void tr2_tbuf_local_time(struct tr2_tbuf *tb)\n+{\n+\tstruct timeval tv;\n+\tstruct tm tm;\n+\ttime_t secs;\n+\n+\tgettimeofday(&tv, NULL);\n+\tsecs = tv.tv_sec;\n+\tlocaltime_r(&secs, &tm);\n+\n+\txsnprintf(tb->buf, sizeof(tb->buf), \"%02d:%02d:%02d.%06ld\", tm.tm_hour,\n+\t\t  tm.tm_min, tm.tm_sec, (long)tv.tv_usec);\n+}\n+\n+void tr2_tbuf_utc_time(struct tr2_tbuf *tb)\n+{\n+\tstruct timeval tv;\n+\tstruct tm tm;\n+\ttime_t secs;\n+\n+\tgettimeofday(&tv, NULL);\n+\tsecs = tv.tv_sec;\n+\tgmtime_r(&secs, &tm);\n+\n+\txsnprintf(tb->buf, sizeof(tb->buf),\n+\t\t  \"%4d-%02d-%02d %02d:%02d:%02d.%06ld\", tm.tm_year + 1900,\n+\t\t  tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,\n+\t\t  (long)tv.tv_usec);\n+}\ndiff --git a/trace2/tr2_tbuf.h b/trace2/tr2_tbuf.h\nnew file mode 100644\nindex 0000000000..9cdefa3957\n--- /dev/null\n+++ b/trace2/tr2_tbuf.h\n@@ -0,0 +1,23 @@\n+#ifndef TR2_TBUF_H\n+#define TR2_TBUF_H\n+\n+/*\n+ * A simple wrapper around a fixed buffer to avoid C syntax\n+ * quirks and the need to pass around an additional size_t\n+ * argument.\n+ */\n+struct tr2_tbuf {\n+\tchar buf[32];\n+};\n+\n+/*\n+ * Fill buffer with formatted local time string.\n+ */\n+void tr2_tbuf_local_time(struct tr2_tbuf *tb);\n+\n+/*\n+ * Fill buffer with formatted UTC time string.\n+ */\n+void tr2_tbuf_utc_time(struct tr2_tbuf *tb);\n+\n+#endif /* TR2_TBUF_H */\ndiff --git a/trace2/tr2_tgt.h b/trace2/tr2_tgt.h\nnew file mode 100644\nindex 0000000000..297bb8ffbe\n--- /dev/null\n+++ b/trace2/tr2_tgt.h\n@@ -0,0 +1,133 @@\n+#ifndef TR2_TGT_H\n+#define TR2_TGT_H\n+\n+struct child_process;\n+struct repository;\n+struct json_writer;\n+\n+/*\n+ * Function prototypes for a TRACE2 \"target\" vtable.\n+ */\n+\n+typedef int(tr2_tgt_init_t)(void);\n+typedef void(tr2_tgt_term_t)(void);\n+\n+typedef void(tr2_tgt_evt_version_fl_t)(const char *file, int line);\n+\n+typedef void(tr2_tgt_evt_start_fl_t)(const char *file, int line,\n+\t\t\t\t     const char **argv);\n+typedef void(tr2_tgt_evt_exit_fl_t)(const char *file, int line,\n+\t\t\t\t    uint64_t us_elapsed_absolute, int code);\n+typedef void(tr2_tgt_evt_signal_t)(uint64_t us_elapsed_absolute, int signo);\n+typedef void(tr2_tgt_evt_atexit_t)(uint64_t us_elapsed_absolute, int code);\n+\n+typedef void(tr2_tgt_evt_error_va_fl_t)(const char *file, int line,\n+\t\t\t\t\tconst char *fmt, va_list ap);\n+\n+typedef void(tr2_tgt_evt_command_path_fl_t)(const char *file, int line,\n+\t\t\t\t\t    const char *command_path);\n+typedef void(tr2_tgt_evt_command_name_fl_t)(const char *file, int line,\n+\t\t\t\t\t    const char *name,\n+\t\t\t\t\t    const char *hierarchy);\n+typedef void(tr2_tgt_evt_command_mode_fl_t)(const char *file, int line,\n+\t\t\t\t\t    const char *mode);\n+\n+typedef void(tr2_tgt_evt_alias_fl_t)(const char *file, int line,\n+\t\t\t\t     const char *alias, const char **argv);\n+\n+typedef void(tr2_tgt_evt_child_start_fl_t)(const char *file, int line,\n+\t\t\t\t\t   uint64_t us_elapsed_absolute,\n+\t\t\t\t\t   const struct child_process *cmd);\n+typedef void(tr2_tgt_evt_child_exit_fl_t)(const char *file, int line,\n+\t\t\t\t\t  uint64_t us_elapsed_absolute, int cid,\n+\t\t\t\t\t  int pid, int code,\n+\t\t\t\t\t  uint64_t us_elapsed_child);\n+\n+typedef void(tr2_tgt_evt_thread_start_fl_t)(const char *file, int line,\n+\t\t\t\t\t    uint64_t us_elapsed_absolute);\n+typedef void(tr2_tgt_evt_thread_exit_fl_t)(const char *file, int line,\n+\t\t\t\t\t   uint64_t us_elapsed_absolute,\n+\t\t\t\t\t   uint64_t us_elapsed_thread);\n+\n+typedef void(tr2_tgt_evt_exec_fl_t)(const char *file, int line,\n+\t\t\t\t    uint64_t us_elapsed_absolute, int exec_id,\n+\t\t\t\t    const char *exe, const char **argv);\n+typedef void(tr2_tgt_evt_exec_result_fl_t)(const char *file, int line,\n+\t\t\t\t\t   uint64_t us_elapsed_absolute,\n+\t\t\t\t\t   int exec_id, int code);\n+\n+typedef void(tr2_tgt_evt_param_fl_t)(const char *file, int line,\n+\t\t\t\t     const char *param, const char *value);\n+\n+typedef void(tr2_tgt_evt_repo_fl_t)(const char *file, int line,\n+\t\t\t\t    const struct repository *repo);\n+\n+typedef void(tr2_tgt_evt_region_enter_printf_va_fl_t)(\n+\tconst char *file, int line, uint64_t us_elapsed_absolute,\n+\tconst char *category, const char *label, const struct repository *repo,\n+\tconst char *fmt, va_list ap);\n+typedef void(tr2_tgt_evt_region_leave_printf_va_fl_t)(\n+\tconst char *file, int line, uint64_t us_elapsed_absolute,\n+\tuint64_t us_elapsed_region, const char *category, const char *label,\n+\tconst struct repository *repo, const char *fmt, va_list ap);\n+\n+typedef void(tr2_tgt_evt_data_fl_t)(const char *file, int line,\n+\t\t\t\t    uint64_t us_elapsed_absolute,\n+\t\t\t\t    uint64_t us_elapsed_region,\n+\t\t\t\t    const char *category,\n+\t\t\t\t    const struct repository *repo,\n+\t\t\t\t    const char *key, const char *value);\n+typedef void(tr2_tgt_evt_data_json_fl_t)(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t uint64_t us_elapsed_region,\n+\t\t\t\t\t const char *category,\n+\t\t\t\t\t const struct repository *repo,\n+\t\t\t\t\t const char *key,\n+\t\t\t\t\t const struct json_writer *value);\n+\n+typedef void(tr2_tgt_evt_printf_va_fl_t)(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t const char *fmt, va_list ap);\n+\n+/*\n+ * \"vtable\" for a TRACE2 target.  Use NULL if a target does not want\n+ * to emit that message.\n+ */\n+/* clang-format off */\n+struct tr2_tgt {\n+\tstruct tr2_dst                          *pdst;\n+\n+\ttr2_tgt_init_t                          *pfn_init;\n+\ttr2_tgt_term_t                          *pfn_term;\n+\n+\ttr2_tgt_evt_version_fl_t                *pfn_version_fl;\n+\ttr2_tgt_evt_start_fl_t                  *pfn_start_fl;\n+\ttr2_tgt_evt_exit_fl_t                   *pfn_exit_fl;\n+\ttr2_tgt_evt_signal_t                    *pfn_signal;\n+\ttr2_tgt_evt_atexit_t                    *pfn_atexit;\n+\ttr2_tgt_evt_error_va_fl_t               *pfn_error_va_fl;\n+\ttr2_tgt_evt_command_path_fl_t           *pfn_command_path_fl;\n+\ttr2_tgt_evt_command_name_fl_t           *pfn_command_name_fl;\n+\ttr2_tgt_evt_command_mode_fl_t           *pfn_command_mode_fl;\n+\ttr2_tgt_evt_alias_fl_t                  *pfn_alias_fl;\n+\ttr2_tgt_evt_child_start_fl_t            *pfn_child_start_fl;\n+\ttr2_tgt_evt_child_exit_fl_t             *pfn_child_exit_fl;\n+\ttr2_tgt_evt_thread_start_fl_t           *pfn_thread_start_fl;\n+\ttr2_tgt_evt_thread_exit_fl_t            *pfn_thread_exit_fl;\n+\ttr2_tgt_evt_exec_fl_t                   *pfn_exec_fl;\n+\ttr2_tgt_evt_exec_result_fl_t            *pfn_exec_result_fl;\n+\ttr2_tgt_evt_param_fl_t                  *pfn_param_fl;\n+\ttr2_tgt_evt_repo_fl_t                   *pfn_repo_fl;\n+\ttr2_tgt_evt_region_enter_printf_va_fl_t *pfn_region_enter_printf_va_fl;\n+\ttr2_tgt_evt_region_leave_printf_va_fl_t *pfn_region_leave_printf_va_fl;\n+\ttr2_tgt_evt_data_fl_t                   *pfn_data_fl;\n+\ttr2_tgt_evt_data_json_fl_t              *pfn_data_json_fl;\n+\ttr2_tgt_evt_printf_va_fl_t              *pfn_printf_va_fl;\n+};\n+/* clang-format on */\n+\n+extern struct tr2_tgt tr2_tgt_event;\n+extern struct tr2_tgt tr2_tgt_normal;\n+extern struct tr2_tgt tr2_tgt_perf;\n+\n+#endif /* TR2_TGT_H */\ndiff --git a/trace2/tr2_tgt_event.c b/trace2/tr2_tgt_event.c\nnew file mode 100644\nindex 0000000000..da8380b5af\n--- /dev/null\n+++ b/trace2/tr2_tgt_event.c\n@@ -0,0 +1,589 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"json-writer.h\"\n+#include \"run-command.h\"\n+#include \"version.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_tbuf.h\"\n+#include \"trace2/tr2_sid.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+static struct tr2_dst tr2dst_event = { \"GIT_TR2_EVENT\", 0, 0, 0 };\n+\n+/*\n+ * The version number of the JSON data generated by the EVENT target\n+ * in this source file.  Update this if you make a significant change\n+ * to the JSON fields or message structure.  You probably do not need\n+ * to update this if you just add another call to one of the existing\n+ * TRACE2 API methods.\n+ */\n+#define TR2_EVENT_VERSION \"1\"\n+\n+/*\n+ * Region nesting limit for messages written to the event target.\n+ *\n+ * The \"region_enter\" and \"region_leave\" messages (especially recursive\n+ * messages such as those produced while diving the worktree or index)\n+ * are primarily intended for the performance target during debugging.\n+ *\n+ * Some of the outer-most messages, however, may be of interest to the\n+ * event target.  Set this environment variable to a larger integer for\n+ * more detail in the event target.\n+ */\n+#define TR2_ENVVAR_EVENT_NESTING \"GIT_TR2_EVENT_NESTING\"\n+static int tr2env_event_nesting_wanted = 2;\n+\n+/*\n+ * Set this environment variable to true to omit the <time>, <file>, and\n+ * <line> fields from most events.\n+ */\n+#define TR2_ENVVAR_EVENT_BRIEF \"GIT_TR2_EVENT_BRIEF\"\n+static int tr2env_event_brief;\n+\n+static int fn_init(void)\n+{\n+\tint want = tr2_dst_trace_want(&tr2dst_event);\n+\tint want_nesting;\n+\tint want_brief;\n+\tchar *nesting;\n+\tchar *brief;\n+\n+\tif (!want)\n+\t\treturn want;\n+\n+\tnesting = getenv(TR2_ENVVAR_EVENT_NESTING);\n+\tif (nesting && ((want_nesting = atoi(nesting)) > 0))\n+\t\ttr2env_event_nesting_wanted = want_nesting;\n+\n+\tbrief = getenv(TR2_ENVVAR_EVENT_BRIEF);\n+\tif (brief && ((want_brief = atoi(brief)) > 0))\n+\t\ttr2env_event_brief = want_brief;\n+\n+\treturn want;\n+}\n+\n+static void fn_term(void)\n+{\n+\ttr2_dst_trace_disable(&tr2dst_event);\n+}\n+\n+/*\n+ * Append common key-value pairs to the currently open JSON object.\n+ *     \"event:\"<event_name>\"\n+ *      \"sid\":\"<sid>\"\n+ *   \"thread\":\"<thread_name>\"\n+ *     \"time\":\"<time>\"\n+ *     \"file\":\"<filename>\"\n+ *     \"line\":<line_number>\n+ *     \"repo\":<repo_id>\n+ */\n+static void event_fmt_prepare(const char *event_name, const char *file,\n+\t\t\t      int line, const struct repository *repo,\n+\t\t\t      struct json_writer *jw)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tstruct tr2_tbuf tb_now;\n+\n+\tjw_object_string(jw, \"event\", event_name);\n+\tjw_object_string(jw, \"sid\", tr2_sid_get());\n+\tjw_object_string(jw, \"thread\", ctx->thread_name.buf);\n+\n+\t/*\n+\t * In brief mode, only emit <time> on these 2 event types.\n+\t */\n+\tif (!tr2env_event_brief || !strcmp(event_name, \"version\") ||\n+\t    !strcmp(event_name, \"atexit\")) {\n+\t\ttr2_tbuf_utc_time(&tb_now);\n+\t\tjw_object_string(jw, \"time\", tb_now.buf);\n+\t}\n+\n+\tif (!tr2env_event_brief && file && *file) {\n+\t\tjw_object_string(jw, \"file\", file);\n+\t\tjw_object_intmax(jw, \"line\", line);\n+\t}\n+\n+\tif (repo)\n+\t\tjw_object_intmax(jw, \"repo\", repo->trace2_repo_id);\n+}\n+\n+static void fn_version_fl(const char *file, int line)\n+{\n+\tconst char *event_name = \"version\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"evt\", TR2_EVENT_VERSION);\n+\tjw_object_string(&jw, \"exe\", git_version_string);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_start_fl(const char *file, int line, const char **argv)\n+{\n+\tconst char *event_name = \"start\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tjw_array_argv(&jw, argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_exit_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int code)\n+{\n+\tconst char *event_name = \"exit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_signal(uint64_t us_elapsed_absolute, int signo)\n+{\n+\tconst char *event_name = \"signal\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, __FILE__, __LINE__, NULL, &jw);\n+\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\tjw_object_intmax(&jw, \"signo\", signo);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_atexit(uint64_t us_elapsed_absolute, int code)\n+{\n+\tconst char *event_name = \"atexit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, __FILE__, __LINE__, NULL, &jw);\n+\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void maybe_add_string_va(struct json_writer *jw, const char *field_name,\n+\t\t\t\tconst char *fmt, va_list ap)\n+{\n+\tif (fmt && *fmt && ap) {\n+\t\tva_list copy_ap;\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\n+\t\tva_copy(copy_ap, ap);\n+\t\tstrbuf_vaddf(&buf, fmt, copy_ap);\n+\t\tva_end(copy_ap);\n+\n+\t\tjw_object_string(jw, field_name, buf.buf);\n+\t\tstrbuf_release(&buf);\n+\t\treturn;\n+\t}\n+\n+\tif (fmt && *fmt) {\n+\t\tjw_object_string(jw, field_name, fmt);\n+\t\treturn;\n+\t}\n+}\n+\n+static void fn_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t   va_list ap)\n+{\n+\tconst char *event_name = \"error\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tmaybe_add_string_va(&jw, \"msg\", fmt, ap);\n+\t/*\n+\t * Also emit the format string as a field in case\n+\t * post-processors want to aggregate common error\n+\t * messages by type without argument fields (such\n+\t * as pathnames or branch names) cluttering it up.\n+\t */\n+\tif (fmt && *fmt)\n+\t\tjw_object_string(&jw, \"fmt\", fmt);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_command_path_fl(const char *file, int line, const char *pathname)\n+{\n+\tconst char *event_name = \"cmd_path\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"path\", pathname);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_command_name_fl(const char *file, int line,\n+\t\t\t       const char *name,\n+\t\t\t       const char *hierarchy)\n+{\n+\tconst char *event_name = \"cmd_name\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"name\", name);\n+\tif (hierarchy && *hierarchy)\n+\t\tjw_object_string(&jw, \"hierarchy\", hierarchy);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_command_mode_fl(const char *file, int line, const char *mode)\n+{\n+\tconst char *event_name = \"cmd_mode\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"name\", mode);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_alias_fl(const char *file, int line, const char *alias,\n+\t\t\tconst char **argv)\n+{\n+\tconst char *event_name = \"alias\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"alias\", alias);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tjw_array_argv(&jw, argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_child_start_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      const struct child_process *cmd)\n+{\n+\tconst char *event_name = \"child_start\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"child_id\", cmd->trace2_child_id);\n+\tif (cmd->trace2_hook_name) {\n+\t\tjw_object_string(&jw, \"child_class\", \"hook\");\n+\t\tjw_object_string(&jw, \"hook_name\", cmd->trace2_hook_name);\n+\t} else {\n+\t\tconst char *child_class =\n+\t\t\tcmd->trace2_child_class ? cmd->trace2_child_class : \"?\";\n+\t\tjw_object_string(&jw, \"child_class\", child_class);\n+\t}\n+\tif (cmd->dir)\n+\t\tjw_object_string(&jw, \"cd\", cmd->dir);\n+\tjw_object_bool(&jw, \"use_shell\", cmd->use_shell);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tif (cmd->git_cmd)\n+\t\tjw_array_string(&jw, \"git\");\n+\tjw_array_argv(&jw, cmd->argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_child_exit_fl(const char *file, int line,\n+\t\t\t     uint64_t us_elapsed_absolute, int cid, int pid,\n+\t\t\t     int code, uint64_t us_elapsed_child)\n+{\n+\tconst char *event_name = \"child_exit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_rel = (double)us_elapsed_child / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"child_id\", cid);\n+\tjw_object_intmax(&jw, \"pid\", pid);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\n+\tjw_release(&jw);\n+}\n+\n+static void fn_thread_start_fl(const char *file, int line,\n+\t\t\t       uint64_t us_elapsed_absolute)\n+{\n+\tconst char *event_name = \"thread_start\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_thread_exit_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      uint64_t us_elapsed_thread)\n+{\n+\tconst char *event_name = \"thread_exit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_rel = (double)us_elapsed_thread / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int exec_id, const char *exe, const char **argv)\n+{\n+\tconst char *event_name = \"exec\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"exec_id\", exec_id);\n+\tif (exe)\n+\t\tjw_object_string(&jw, \"exe\", exe);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tjw_array_argv(&jw, argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_exec_result_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute, int exec_id,\n+\t\t\t      int code)\n+{\n+\tconst char *event_name = \"exec_result\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"exec_id\", exec_id);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_param_fl(const char *file, int line, const char *param,\n+\t\t\tconst char *value)\n+{\n+\tconst char *event_name = \"def_param\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"param\", param);\n+\tjw_object_string(&jw, \"value\", value);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_repo_fl(const char *file, int line,\n+\t\t       const struct repository *repo)\n+{\n+\tconst char *event_name = \"def_repo\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\tjw_object_string(&jw, \"worktree\", repo->worktree);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t const char *category,\n+\t\t\t\t\t const char *label,\n+\t\t\t\t\t const struct repository *repo,\n+\t\t\t\t\t const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_enter\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tif (category)\n+\t\t\tjw_object_string(&jw, \"category\", category);\n+\t\tif (label)\n+\t\t\tjw_object_string(&jw, \"label\", label);\n+\t\tmaybe_add_string_va(&jw, \"msg\", fmt, ap);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+static void fn_region_leave_printf_va_fl(\n+\tconst char *file, int line, uint64_t us_elapsed_absolute,\n+\tuint64_t us_elapsed_region, const char *category, const char *label,\n+\tconst struct repository *repo, const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_leave\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\t\tdouble t_rel = (double)us_elapsed_region / 1000000.0;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tif (category)\n+\t\t\tjw_object_string(&jw, \"category\", category);\n+\t\tif (label)\n+\t\t\tjw_object_string(&jw, \"label\", label);\n+\t\tmaybe_add_string_va(&jw, \"msg\", fmt, ap);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+static void fn_data_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       uint64_t us_elapsed_region, const char *category,\n+\t\t       const struct repository *repo, const char *key,\n+\t\t       const char *value)\n+{\n+\tconst char *event_name = \"data\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\t\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\t\tdouble t_rel = (double)us_elapsed_region / 1000000.0;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\t\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tjw_object_string(&jw, \"category\", category);\n+\t\tjw_object_string(&jw, \"key\", key);\n+\t\tjw_object_string(&jw, \"value\", value);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+static void fn_data_json_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute,\n+\t\t\t    uint64_t us_elapsed_region, const char *category,\n+\t\t\t    const struct repository *repo, const char *key,\n+\t\t\t    const struct json_writer *value)\n+{\n+\tconst char *event_name = \"data_json\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\t\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\t\tdouble t_rel = (double)us_elapsed_region / 1000000.0;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\t\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tjw_object_string(&jw, \"category\", category);\n+\t\tjw_object_string(&jw, \"key\", key);\n+\t\tjw_object_sub_jw(&jw, \"value\", value);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+struct tr2_tgt tr2_tgt_event = {\n+\t&tr2dst_event,\n+\n+\tfn_init,\n+\tfn_term,\n+\n+\tfn_version_fl,\n+\tfn_start_fl,\n+\tfn_exit_fl,\n+\tfn_signal,\n+\tfn_atexit,\n+\tfn_error_va_fl,\n+\tfn_command_path_fl,\n+\tfn_command_name_fl,\n+\tfn_command_mode_fl,\n+\tfn_alias_fl,\n+\tfn_child_start_fl,\n+\tfn_child_exit_fl,\n+\tfn_thread_start_fl,\n+\tfn_thread_exit_fl,\n+\tfn_exec_fl,\n+\tfn_exec_result_fl,\n+\tfn_param_fl,\n+\tfn_repo_fl,\n+\tfn_region_enter_printf_va_fl,\n+\tfn_region_leave_printf_va_fl,\n+\tfn_data_fl,\n+\tfn_data_json_fl,\n+\tNULL, /* printf */\n+};\ndiff --git a/trace2/tr2_tgt_normal.c b/trace2/tr2_tgt_normal.c\nnew file mode 100644\nindex 0000000000..3babf2af02\n--- /dev/null\n+++ b/trace2/tr2_tgt_normal.c\n@@ -0,0 +1,324 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"run-command.h\"\n+#include \"quote.h\"\n+#include \"version.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_tbuf.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+static struct tr2_dst tr2dst_normal = { \"GIT_TR2\", 0, 0, 0 };\n+\n+/*\n+ * Set this environment variable to true to omit the \"<time> <file>:<line>\"\n+ * fields from each line written to the builtin normal target.\n+ *\n+ * Unit tests may want to use this to help with testing.\n+ */\n+#define TR2_ENVVAR_NORMAL_BRIEF \"GIT_TR2_BRIEF\"\n+static int tr2env_normal_brief;\n+\n+#define TR2FMT_NORMAL_FL_WIDTH (50)\n+\n+static int fn_init(void)\n+{\n+\tint want = tr2_dst_trace_want(&tr2dst_normal);\n+\tint want_brief;\n+\tchar *brief;\n+\n+\tif (!want)\n+\t\treturn want;\n+\n+\tbrief = getenv(TR2_ENVVAR_NORMAL_BRIEF);\n+\tif (brief && *brief &&\n+\t    ((want_brief = git_parse_maybe_bool(brief)) != -1))\n+\t\ttr2env_normal_brief = want_brief;\n+\n+\treturn want;\n+}\n+\n+static void fn_term(void)\n+{\n+\ttr2_dst_trace_disable(&tr2dst_normal);\n+}\n+\n+static void normal_fmt_prepare(const char *file, int line, struct strbuf *buf)\n+{\n+\tstrbuf_setlen(buf, 0);\n+\n+\tif (!tr2env_normal_brief) {\n+\t\tstruct tr2_tbuf tb_now;\n+\n+\t\ttr2_tbuf_local_time(&tb_now);\n+\t\tstrbuf_addstr(buf, tb_now.buf);\n+\t\tstrbuf_addch(buf, ' ');\n+\n+\t\tif (file && *file)\n+\t\t\tstrbuf_addf(buf, \"%s:%d \", file, line);\n+\t\twhile (buf->len < TR2FMT_NORMAL_FL_WIDTH)\n+\t\t\tstrbuf_addch(buf, ' ');\n+\t}\n+}\n+\n+static void normal_io_write_fl(const char *file, int line,\n+\t\t\t       const struct strbuf *buf_payload)\n+{\n+\tstruct strbuf buf_line = STRBUF_INIT;\n+\n+\tnormal_fmt_prepare(file, line, &buf_line);\n+\tstrbuf_addbuf(&buf_line, buf_payload);\n+\ttr2_dst_write_line(&tr2dst_normal, &buf_line);\n+\tstrbuf_release(&buf_line);\n+}\n+\n+static void fn_version_fl(const char *file, int line)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"version %s\", git_version_string);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_start_fl(const char *file, int line, const char **argv)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"start \");\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exit_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int code)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"exit elapsed:%.6f code:%d\", elapsed, code);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_signal(uint64_t us_elapsed_absolute, int signo)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"signal elapsed:%.6f code:%d\", elapsed,\n+\t\t    signo);\n+\tnormal_io_write_fl(__FILE__, __LINE__, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_atexit(uint64_t us_elapsed_absolute, int code)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"atexit elapsed:%.6f code:%d\", elapsed, code);\n+\tnormal_io_write_fl(__FILE__, __LINE__, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void maybe_append_string_va(struct strbuf *buf, const char *fmt,\n+\t\t\t\t   va_list ap)\n+{\n+\tif (fmt && *fmt && ap) {\n+\t\tva_list copy_ap;\n+\n+\t\tva_copy(copy_ap, ap);\n+\t\tstrbuf_vaddf(buf, fmt, copy_ap);\n+\t\tva_end(copy_ap);\n+\t\treturn;\n+\t}\n+\n+\tif (fmt && *fmt) {\n+\t\tstrbuf_addstr(buf, fmt);\n+\t\treturn;\n+\t}\n+}\n+\n+static void fn_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t   va_list ap)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"error \");\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_path_fl(const char *file, int line, const char *pathname)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"cmd_path %s\", pathname);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_name_fl(const char *file, int line,\n+\t\t\t       const char *name,\n+\t\t\t       const char *hierarchy)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"cmd_name %s\", name);\n+\tif (hierarchy && *hierarchy)\n+\t\tstrbuf_addf(&buf_payload, \" (%s)\", hierarchy);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_mode_fl(const char *file, int line, const char *mode)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"cmd_mode %s\", mode);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_alias_fl(const char *file, int line, const char *alias,\n+\t\t\tconst char **argv)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"alias %s ->\", alias);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_start_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      const struct child_process *cmd)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"child_start[%d] \", cmd->trace2_child_id);\n+\n+\tif (cmd->dir) {\n+\t\tstrbuf_addstr(&buf_payload, \" cd\");\n+\t\tsq_quote_buf_pretty(&buf_payload, cmd->dir);\n+\t\tstrbuf_addstr(&buf_payload, \"; \");\n+\t}\n+\n+\t/*\n+\t * TODO if (cmd->env) { Consider dumping changes to environment. }\n+\t * See trace_add_env() in run-command.c as used by original trace.c\n+\t */\n+\n+\tif (cmd->git_cmd)\n+\t\tstrbuf_addstr(&buf_payload, \"git\");\n+\tsq_quote_argv_pretty(&buf_payload, cmd->argv);\n+\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_exit_fl(const char *file, int line,\n+\t\t\t     uint64_t us_elapsed_absolute, int cid, int pid,\n+\t\t\t     int code, uint64_t us_elapsed_child)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_child / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"child_exit[%d] pid:%d code:%d elapsed:%.6f\",\n+\t\t    cid, pid, code, elapsed);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int exec_id, const char *exe, const char **argv)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"exec[%d] \", exec_id);\n+\tif (exe)\n+\t\tstrbuf_addstr(&buf_payload, exe);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_result_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute, int exec_id,\n+\t\t\t      int code)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"exec_result[%d] code:%d\", exec_id, code);\n+\tif (code > 0)\n+\t\tstrbuf_addf(&buf_payload, \" err:%s\", strerror(code));\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_param_fl(const char *file, int line, const char *param,\n+\t\t\tconst char *value)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"def_param %s=%s\", param, value);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_repo_fl(const char *file, int line,\n+\t\t       const struct repository *repo)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"worktree \");\n+\tsq_quote_buf_pretty(&buf_payload, repo->worktree);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_printf_va_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute, const char *fmt,\n+\t\t\t    va_list ap)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+struct tr2_tgt tr2_tgt_normal = {\n+\t&tr2dst_normal,\n+\n+\tfn_init,\n+\tfn_term,\n+\n+\tfn_version_fl,\n+\tfn_start_fl,\n+\tfn_exit_fl,\n+\tfn_signal,\n+\tfn_atexit,\n+\tfn_error_va_fl,\n+\tfn_command_path_fl,\n+\tfn_command_name_fl,\n+\tfn_command_mode_fl,\n+\tfn_alias_fl,\n+\tfn_child_start_fl,\n+\tfn_child_exit_fl,\n+\tNULL, /* thread_start */\n+\tNULL, /* thread_exit */\n+\tfn_exec_fl,\n+\tfn_exec_result_fl,\n+\tfn_param_fl,\n+\tfn_repo_fl,\n+\tNULL, /* region_enter */\n+\tNULL, /* region_leave */\n+\tNULL, /* data */\n+\tNULL, /* data_json */\n+\tfn_printf_va_fl,\n+};\ndiff --git a/trace2/tr2_tgt_perf.c b/trace2/tr2_tgt_perf.c\nnew file mode 100644\nindex 0000000000..7b0cf169a4\n--- /dev/null\n+++ b/trace2/tr2_tgt_perf.c\n@@ -0,0 +1,535 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"run-command.h\"\n+#include \"quote.h\"\n+#include \"version.h\"\n+#include \"json-writer.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_sid.h\"\n+#include \"trace2/tr2_tbuf.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+static struct tr2_dst tr2dst_perf = { \"GIT_TR2_PERF\", 0, 0, 0 };\n+\n+/*\n+ * Set this environment variable to true to omit the \"<time> <file>:<line>\"\n+ * fields from each line written to the builtin performance target.\n+ *\n+ * Unit tests may want to use this to help with testing.\n+ */\n+#define TR2_ENVVAR_PERF_BRIEF \"GIT_TR2_PERF_BRIEF\"\n+static int tr2env_perf_brief;\n+\n+#define TR2FMT_PERF_FL_WIDTH (50)\n+#define TR2FMT_PERF_MAX_EVENT_NAME (12)\n+#define TR2FMT_PERF_REPO_WIDTH (4)\n+#define TR2FMT_PERF_CATEGORY_WIDTH (10)\n+\n+#define TR2_DOTS_BUFFER_SIZE (100)\n+#define TR2_INDENT (2)\n+#define TR2_INDENT_LENGTH(ctx) (((ctx)->nr_open_regions - 1) * TR2_INDENT)\n+\n+static struct strbuf dots = STRBUF_INIT;\n+\n+static int fn_init(void)\n+{\n+\tint want = tr2_dst_trace_want(&tr2dst_perf);\n+\tint want_brief;\n+\tchar *brief;\n+\n+\tif (!want)\n+\t\treturn want;\n+\n+\tstrbuf_addchars(&dots, '.', TR2_DOTS_BUFFER_SIZE);\n+\n+\tbrief = getenv(TR2_ENVVAR_PERF_BRIEF);\n+\tif (brief && *brief &&\n+\t    ((want_brief = git_parse_maybe_bool(brief)) != -1))\n+\t\ttr2env_perf_brief = want_brief;\n+\n+\treturn want;\n+}\n+\n+static void fn_term(void)\n+{\n+\ttr2_dst_trace_disable(&tr2dst_perf);\n+\n+\tstrbuf_release(&dots);\n+}\n+\n+/*\n+ * Format trace line prefix in human-readable classic format for\n+ * the performance target:\n+ *     \"[<time> [<file>:<line>] <bar>] <nr_parents> <bar>\n+ *         <thread_name> <bar> <event_name> <bar> [<repo>] <bar>\n+ *         [<elapsed_absolute>] [<elapsed_relative>] <bar>\n+ *         [<category>] <bar> [<dots>] \"\n+ */\n+static void perf_fmt_prepare(const char *event_name,\n+\t\t\t     struct tr2tls_thread_ctx *ctx, const char *file,\n+\t\t\t     int line, const struct repository *repo,\n+\t\t\t     uint64_t *p_us_elapsed_absolute,\n+\t\t\t     uint64_t *p_us_elapsed_relative,\n+\t\t\t     const char *category, struct strbuf *buf)\n+{\n+\tint len;\n+\n+\tstrbuf_setlen(buf, 0);\n+\n+\tif (!tr2env_perf_brief) {\n+\t\tstruct tr2_tbuf tb_now;\n+\n+\t\ttr2_tbuf_local_time(&tb_now);\n+\t\tstrbuf_addstr(buf, tb_now.buf);\n+\t\tstrbuf_addch(buf, ' ');\n+\n+\t\tif (file && *file)\n+\t\t\tstrbuf_addf(buf, \"%s:%d \", file, line);\n+\t\twhile (buf->len < TR2FMT_PERF_FL_WIDTH)\n+\t\t\tstrbuf_addch(buf, ' ');\n+\n+\t\tstrbuf_addstr(buf, \"| \");\n+\t}\n+\n+\tstrbuf_addf(buf, \"d%d | \", tr2_sid_depth());\n+\tstrbuf_addf(buf, \"%-*s | %-*s | \", TR2_MAX_THREAD_NAME,\n+\t\t    ctx->thread_name.buf, TR2FMT_PERF_MAX_EVENT_NAME,\n+\t\t    event_name);\n+\n+\tlen = buf->len + TR2FMT_PERF_REPO_WIDTH;\n+\tif (repo)\n+\t\tstrbuf_addf(buf, \"r%d \", repo->trace2_repo_id);\n+\twhile (buf->len < len)\n+\t\tstrbuf_addch(buf, ' ');\n+\tstrbuf_addstr(buf, \"| \");\n+\n+\tif (p_us_elapsed_absolute)\n+\t\tstrbuf_addf(buf, \"%9.6f | \",\n+\t\t\t    ((double)(*p_us_elapsed_absolute)) / 1000000.0);\n+\telse\n+\t\tstrbuf_addf(buf, \"%9s | \", \" \");\n+\n+\tif (p_us_elapsed_relative)\n+\t\tstrbuf_addf(buf, \"%9.6f | \",\n+\t\t\t    ((double)(*p_us_elapsed_relative)) / 1000000.0);\n+\telse\n+\t\tstrbuf_addf(buf, \"%9s | \", \" \");\n+\n+\tstrbuf_addf(buf, \"%-*s | \", TR2FMT_PERF_CATEGORY_WIDTH,\n+\t\t    (category ? category : \"\"));\n+\n+\tif (ctx->nr_open_regions > 0) {\n+\t\tint len_indent = TR2_INDENT_LENGTH(ctx);\n+\t\twhile (len_indent > dots.len) {\n+\t\t\tstrbuf_addbuf(buf, &dots);\n+\t\t\tlen_indent -= dots.len;\n+\t\t}\n+\t\tstrbuf_addf(buf, \"%.*s\", len_indent, dots.buf);\n+\t}\n+}\n+\n+static void perf_io_write_fl(const char *file, int line, const char *event_name,\n+\t\t\t     const struct repository *repo,\n+\t\t\t     uint64_t *p_us_elapsed_absolute,\n+\t\t\t     uint64_t *p_us_elapsed_relative,\n+\t\t\t     const char *category,\n+\t\t\t     const struct strbuf *buf_payload)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tstruct strbuf buf_line = STRBUF_INIT;\n+\n+\tperf_fmt_prepare(event_name, ctx, file, line, repo,\n+\t\t\t p_us_elapsed_absolute, p_us_elapsed_relative, category,\n+\t\t\t &buf_line);\n+\tstrbuf_addbuf(&buf_line, buf_payload);\n+\ttr2_dst_write_line(&tr2dst_perf, &buf_line);\n+\tstrbuf_release(&buf_line);\n+}\n+\n+static void fn_version_fl(const char *file, int line)\n+{\n+\tconst char *event_name = \"version\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, git_version_string);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_start_fl(const char *file, int line, const char **argv)\n+{\n+\tconst char *event_name = \"start\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exit_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int code)\n+{\n+\tconst char *event_name = \"exit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"code:%d\", code);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_signal(uint64_t us_elapsed_absolute, int signo)\n+{\n+\tconst char *event_name = \"signal\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"signo:%d\", signo);\n+\n+\tperf_io_write_fl(__FILE__, __LINE__, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_atexit(uint64_t us_elapsed_absolute, int code)\n+{\n+\tconst char *event_name = \"atexit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"code:%d\", code);\n+\n+\tperf_io_write_fl(__FILE__, __LINE__, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void maybe_append_string_va(struct strbuf *buf, const char *fmt,\n+\t\t\t\t   va_list ap)\n+{\n+\tif (fmt && *fmt && ap) {\n+\t\tva_list copy_ap;\n+\n+\t\tva_copy(copy_ap, ap);\n+\t\tstrbuf_vaddf(buf, fmt, copy_ap);\n+\t\tva_end(copy_ap);\n+\t\treturn;\n+\t}\n+\n+\tif (fmt && *fmt) {\n+\t\tstrbuf_addstr(buf, fmt);\n+\t\treturn;\n+\t}\n+}\n+\n+static void fn_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t   va_list ap)\n+{\n+\tconst char *event_name = \"error\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_path_fl(const char *file, int line, const char *pathname)\n+{\n+\tconst char *event_name = \"cmd_path\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, pathname);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_name_fl(const char *file, int line,\n+\t\t\t       const char *name,\n+\t\t\t       const char *hierarchy)\n+{\n+\tconst char *event_name = \"cmd_name\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, name);\n+\tif (hierarchy && *hierarchy)\n+\t\tstrbuf_addf(&buf_payload, \" (%s)\", hierarchy);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_mode_fl(const char *file, int line, const char *mode)\n+{\n+\tconst char *event_name = \"cmd_mode\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, mode);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_alias_fl(const char *file, int line, const char *alias,\n+\t\t\tconst char **argv)\n+{\n+\tconst char *event_name = \"alias\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"alias:%s argv:\", alias);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_start_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      const struct child_process *cmd)\n+{\n+\tconst char *event_name = \"child_start\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tif (cmd->trace2_hook_name) {\n+\t\tstrbuf_addf(&buf_payload, \"[ch%d] class:hook hook:%s\",\n+\t\t\t    cmd->trace2_child_id, cmd->trace2_hook_name);\n+\t} else {\n+\t\tconst char *child_class =\n+\t\t\tcmd->trace2_child_class ? cmd->trace2_child_class : \"?\";\n+\t\tstrbuf_addf(&buf_payload, \"[ch%d] class:%s\",\n+\t\t\t    cmd->trace2_child_id, child_class);\n+\t}\n+\n+\tif (cmd->dir) {\n+\t\tstrbuf_addstr(&buf_payload, \" cd:\");\n+\t\tsq_quote_buf_pretty(&buf_payload, cmd->dir);\n+\t}\n+\n+\tstrbuf_addstr(&buf_payload, \" argv:\");\n+\tif (cmd->git_cmd)\n+\t\tstrbuf_addstr(&buf_payload, \" git\");\n+\tsq_quote_argv_pretty(&buf_payload, cmd->argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_exit_fl(const char *file, int line,\n+\t\t\t     uint64_t us_elapsed_absolute, int cid, int pid,\n+\t\t\t     int code, uint64_t us_elapsed_child)\n+{\n+\tconst char *event_name = \"child_exit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"[ch%d] pid:%d code:%d\", cid, pid, code);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_child, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_thread_start_fl(const char *file, int line,\n+\t\t\t       uint64_t us_elapsed_absolute)\n+{\n+\tconst char *event_name = \"thread_start\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_thread_exit_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      uint64_t us_elapsed_thread)\n+{\n+\tconst char *event_name = \"thread_exit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_thread, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int exec_id, const char *exe, const char **argv)\n+{\n+\tconst char *event_name = \"exec\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"id:%d \", exec_id);\n+\tstrbuf_addstr(&buf_payload, \"argv:\");\n+\tif (exe)\n+\t\tstrbuf_addf(&buf_payload, \" %s\", exe);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_result_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute, int exec_id,\n+\t\t\t      int code)\n+{\n+\tconst char *event_name = \"exec_result\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"id:%d code:%d\", exec_id, code);\n+\tif (code > 0)\n+\t\tstrbuf_addf(&buf_payload, \" err:%s\", strerror(code));\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_param_fl(const char *file, int line, const char *param,\n+\t\t\tconst char *value)\n+{\n+\tconst char *event_name = \"def_param\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"%s:%s\", param, value);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_repo_fl(const char *file, int line,\n+\t\t       const struct repository *repo)\n+{\n+\tconst char *event_name = \"def_repo\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"worktree:\");\n+\tsq_quote_buf_pretty(&buf_payload, repo->worktree);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t const char *category,\n+\t\t\t\t\t const char *label,\n+\t\t\t\t\t const struct repository *repo,\n+\t\t\t\t\t const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_enter\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tif (label)\n+\t\tstrbuf_addf(&buf_payload, \"label:%s \", label);\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,\n+\t\t\t NULL, category, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_region_leave_printf_va_fl(\n+\tconst char *file, int line, uint64_t us_elapsed_absolute,\n+\tuint64_t us_elapsed_region, const char *category, const char *label,\n+\tconst struct repository *repo, const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_leave\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tif (label)\n+\t\tstrbuf_addf(&buf_payload, \"label:%s \", label);\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_region, category, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_data_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       uint64_t us_elapsed_region, const char *category,\n+\t\t       const struct repository *repo, const char *key,\n+\t\t       const char *value)\n+{\n+\tconst char *event_name = \"data\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"%s:%s\", key, value);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_region, category, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_data_json_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute,\n+\t\t\t    uint64_t us_elapsed_region, const char *category,\n+\t\t\t    const struct repository *repo, const char *key,\n+\t\t\t    const struct json_writer *value)\n+{\n+\tconst char *event_name = \"data_json\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"%s:%s\", key, value->json.buf);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_region, category, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_printf_va_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute, const char *fmt,\n+\t\t\t    va_list ap)\n+{\n+\tconst char *event_name = \"printf\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+struct tr2_tgt tr2_tgt_perf = {\n+\t&tr2dst_perf,\n+\n+\tfn_init,\n+\tfn_term,\n+\n+\tfn_version_fl,\n+\tfn_start_fl,\n+\tfn_exit_fl,\n+\tfn_signal,\n+\tfn_atexit,\n+\tfn_error_va_fl,\n+\tfn_command_path_fl,\n+\tfn_command_name_fl,\n+\tfn_command_mode_fl,\n+\tfn_alias_fl,\n+\tfn_child_start_fl,\n+\tfn_child_exit_fl,\n+\tfn_thread_start_fl,\n+\tfn_thread_exit_fl,\n+\tfn_exec_fl,\n+\tfn_exec_result_fl,\n+\tfn_param_fl,\n+\tfn_repo_fl,\n+\tfn_region_enter_printf_va_fl,\n+\tfn_region_leave_printf_va_fl,\n+\tfn_data_fl,\n+\tfn_data_json_fl,\n+\tfn_printf_va_fl,\n+};\ndiff --git a/trace2/tr2_tls.c b/trace2/tr2_tls.c\nnew file mode 100644\nindex 0000000000..8e65b0361d\n--- /dev/null\n+++ b/trace2/tr2_tls.c\n@@ -0,0 +1,164 @@\n+#include \"cache.h\"\n+#include \"thread-utils.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+/*\n+ * Initialize size of the thread stack for nested regions.\n+ * This is used to store nested region start times.  Note that\n+ * this stack is per-thread and not per-trace-key.\n+ */\n+#define TR2_REGION_NESTING_INITIAL_SIZE (100)\n+\n+static struct tr2tls_thread_ctx *tr2tls_thread_main;\n+static uint64_t tr2tls_us_start_main;\n+\n+static pthread_mutex_t tr2tls_mutex;\n+static pthread_key_t tr2tls_key;\n+\n+static int tr2_next_thread_id; /* modify under lock */\n+\n+struct tr2tls_thread_ctx *tr2tls_create_self(const char *thread_name)\n+{\n+\tuint64_t us_now = getnanotime() / 1000;\n+\tstruct tr2tls_thread_ctx *ctx = xcalloc(1, sizeof(*ctx));\n+\n+\t/*\n+\t * Implicitly \"tr2tls_push_self()\" to capture the thread's start\n+\t * time in array_us_start[0].  For the main thread this gives us the\n+\t * application run time.\n+\t */\n+\tctx->alloc = TR2_REGION_NESTING_INITIAL_SIZE;\n+\tctx->array_us_start = (uint64_t *)xcalloc(ctx->alloc, sizeof(uint64_t));\n+\tctx->array_us_start[ctx->nr_open_regions++] = us_now;\n+\n+\tctx->thread_id = tr2tls_locked_increment(&tr2_next_thread_id);\n+\n+\tstrbuf_init(&ctx->thread_name, 0);\n+\tif (ctx->thread_id)\n+\t\tstrbuf_addf(&ctx->thread_name, \"th%02d:\", ctx->thread_id);\n+\tstrbuf_addstr(&ctx->thread_name, thread_name);\n+\tif (ctx->thread_name.len > TR2_MAX_THREAD_NAME)\n+\t\tstrbuf_setlen(&ctx->thread_name, TR2_MAX_THREAD_NAME);\n+\n+\tpthread_setspecific(tr2tls_key, ctx);\n+\n+\treturn ctx;\n+}\n+\n+struct tr2tls_thread_ctx *tr2tls_get_self(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = pthread_getspecific(tr2tls_key);\n+\n+\t/*\n+\t * If the thread-proc did not call trace2_thread_start(), we won't\n+\t * have any TLS data associated with the current thread.  Fix it\n+\t * here and silently continue.\n+\t */\n+\tif (!ctx)\n+\t\tctx = tr2tls_create_self(\"unknown\");\n+\n+\treturn ctx;\n+}\n+\n+int tr2tls_is_main_thread(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = pthread_getspecific(tr2tls_key);\n+\n+\treturn ctx == tr2tls_thread_main;\n+}\n+\n+void tr2tls_unset_self(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx;\n+\n+\tctx = tr2tls_get_self();\n+\n+\tpthread_setspecific(tr2tls_key, NULL);\n+\n+\tfree(ctx->array_us_start);\n+\tfree(ctx);\n+}\n+\n+void tr2tls_push_self(uint64_t us_now)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\n+\tALLOC_GROW(ctx->array_us_start, ctx->nr_open_regions + 1, ctx->alloc);\n+\tctx->array_us_start[ctx->nr_open_regions++] = us_now;\n+}\n+\n+void tr2tls_pop_self(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\n+\tif (!ctx->nr_open_regions)\n+\t\tBUG(\"no open regions in thread '%s'\", ctx->thread_name.buf);\n+\n+\tctx->nr_open_regions--;\n+}\n+\n+void tr2tls_pop_unwind_self(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\n+\twhile (ctx->nr_open_regions > 1)\n+\t\ttr2tls_pop_self();\n+}\n+\n+uint64_t tr2tls_region_elasped_self(uint64_t us)\n+{\n+\tstruct tr2tls_thread_ctx *ctx;\n+\tuint64_t us_start;\n+\n+\tctx = tr2tls_get_self();\n+\tif (!ctx->nr_open_regions)\n+\t\treturn 0;\n+\n+\tus_start = ctx->array_us_start[ctx->nr_open_regions - 1];\n+\n+\treturn us - us_start;\n+}\n+\n+uint64_t tr2tls_absolute_elapsed(uint64_t us)\n+{\n+\tif (!tr2tls_thread_main)\n+\t\treturn 0;\n+\n+\treturn us - tr2tls_us_start_main;\n+}\n+\n+void tr2tls_init(void)\n+{\n+\tpthread_key_create(&tr2tls_key, NULL);\n+\tinit_recursive_mutex(&tr2tls_mutex);\n+\n+\ttr2tls_thread_main = tr2tls_create_self(\"main\");\n+\t/*\n+\t * Keep a copy of the absolute start time of the main thread\n+\t * in a fixed variable since other threads need to access it.\n+\t * This also eliminates the need to lock accesses to the main\n+\t * thread's array (because of reallocs).\n+\t */\n+\ttr2tls_us_start_main = tr2tls_thread_main->array_us_start[0];\n+}\n+\n+void tr2tls_release(void)\n+{\n+\ttr2tls_unset_self();\n+\ttr2tls_thread_main = NULL;\n+\n+\tpthread_mutex_destroy(&tr2tls_mutex);\n+\tpthread_key_delete(tr2tls_key);\n+}\n+\n+int tr2tls_locked_increment(int *p)\n+{\n+\tint current_value;\n+\n+\tpthread_mutex_lock(&tr2tls_mutex);\n+\tcurrent_value = *p;\n+\t*p = current_value + 1;\n+\tpthread_mutex_unlock(&tr2tls_mutex);\n+\n+\treturn current_value;\n+}\ndiff --git a/trace2/tr2_tls.h b/trace2/tr2_tls.h\nnew file mode 100644\nindex 0000000000..bb80e3f8e7\n--- /dev/null\n+++ b/trace2/tr2_tls.h\n@@ -0,0 +1,97 @@\n+#ifndef TR2_TLS_H\n+#define TR2_TLS_H\n+\n+#include \"strbuf.h\"\n+\n+/*\n+ * Arbitry limit for thread names for column alignment.\n+ */\n+#define TR2_MAX_THREAD_NAME (24)\n+\n+struct tr2tls_thread_ctx {\n+\tstruct strbuf thread_name;\n+\tuint64_t *array_us_start;\n+\tint alloc;\n+\tint nr_open_regions; /* plays role of \"nr\" in ALLOC_GROW */\n+\tint thread_id;\n+};\n+\n+/*\n+ * Create TLS data for the current thread.  This gives us a place to\n+ * put per-thread data, such as thread start time, function nesting\n+ * and a per-thread label for our messages.\n+ *\n+ * We assume the first thread is \"main\".  Other threads are given\n+ * non-zero thread-ids to help distinguish messages from concurrent\n+ * threads.\n+ *\n+ * Truncate the thread name if necessary to help with column alignment\n+ * in printf-style messages.\n+ *\n+ * In this and all following functions the term \"self\" refers to the\n+ * current thread.\n+ */\n+struct tr2tls_thread_ctx *tr2tls_create_self(const char *thread_name);\n+\n+/*\n+ * Get our TLS data.\n+ */\n+struct tr2tls_thread_ctx *tr2tls_get_self(void);\n+\n+/*\n+ * return true if the current thread is the main thread.\n+ */\n+int tr2tls_is_main_thread(void);\n+\n+/*\n+ * Free our TLS data.\n+ */\n+void tr2tls_unset_self(void);\n+\n+/*\n+ * Begin a new nested region and remember the start time.\n+ */\n+void tr2tls_push_self(uint64_t us_now);\n+\n+/*\n+ * End the innermost nested region.\n+ */\n+void tr2tls_pop_self(void);\n+\n+/*\n+ * Pop any extra (above the first) open regions on the current\n+ * thread and discard.  During a thread-exit, we should only\n+ * have region[0] that was pushed in trace2_thread_start() if\n+ * the thread exits normally.\n+ */\n+void tr2tls_pop_unwind_self(void);\n+\n+/*\n+ * Compute the elapsed time since the innermost region in the\n+ * current thread started and the given time (usually now).\n+ */\n+uint64_t tr2tls_region_elasped_self(uint64_t us);\n+\n+/*\n+ * Compute the elapsed time since the main thread started\n+ * and the given time (usually now).  This is assumed to\n+ * be the absolute run time of the process.\n+ */\n+uint64_t tr2tls_absolute_elapsed(uint64_t us);\n+\n+/*\n+ * Initialize the tr2 TLS system.\n+ */\n+void tr2tls_init(void);\n+\n+/*\n+ * Free all tr2 TLS resources.\n+ */\n+void tr2tls_release(void);\n+\n+/*\n+ * Protected increment of an integer.\n+ */\n+int tr2tls_locked_increment(int *p);\n+\n+#endif /* TR2_TLS_H */\ndiff --git a/usage.c b/usage.c\nindex cc803336bd..2fdb20086b 100644\n--- a/usage.c\n+++ b/usage.c\n@@ -22,17 +22,48 @@ void vreportf(const char *prefix, const char *err, va_list params)\n static NORETURN void usage_builtin(const char *err, va_list params)\n {\n \tvreportf(\"usage: \", err, params);\n+\n+\t/*\n+\t * When we detect a usage error *before* the command dispatch in\n+\t * cmd_main(), we don't know what verb to report.  Force it to this\n+\t * to facilitate post-processing.\n+\t */\n+\ttrace2_cmd_name(\"_usage_\");\n+\n+\t/*\n+\t * Currently, the (err, params) are usually just the static usage\n+\t * string which isn't very useful here.  Usually, the call site\n+\t * manually calls fprintf(stderr,...) with the actual detailed\n+\t * syntax error before calling usage().\n+\t *\n+\t * TODO It would be nice to update the call sites to pass both\n+\t * the static usage string and the detailed error message.\n+\t */\n+\n \texit(129);\n }\n \n static NORETURN void die_builtin(const char *err, va_list params)\n {\n+\t/*\n+\t * We call this trace2 function first and expect it to va_copy 'params'\n+\t * before using it (because an 'ap' can only be walked once).\n+\t */\n+\ttrace2_cmd_error_va(err, params);\n+\n \tvreportf(\"fatal: \", err, params);\n+\n \texit(128);\n }\n \n static void error_builtin(const char *err, va_list params)\n {\n+\t/*\n+\t * We call this trace2 function first and expect it to va_copy 'params'\n+\t * before using it (because an 'ap' can only be walked once).\n+\t */\n+\ttrace2_cmd_error_va(err, params);\n+\n \tvreportf(\"error: \", err, params);\n }\n \n-- \ngitgitgadget\n\n"},{"id":"368606","messageId":"b9f0c6fd66ea1ed8570fe2f79a7a2ca1d69c0f51.1549473350.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 14/15] trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-06T17:16:05Z","receivedAt":"2019-02-06T17:16:16Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate unit tests for Trace2.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                  |   1 +\n t/helper/test-tool.c      |   1 +\n t/helper/test-tool.h      |   1 +\n t/helper/test-trace2.c    | 273 ++++++++++++++++++++++++++++++++++++++\n t/t0210-trace2-normal.sh  | 135 +++++++++++++++++++\n t/t0210/scrub_normal.perl |  48 +++++++\n t/t0211-trace2-perf.sh    | 153 +++++++++++++++++++++\n t/t0211/scrub_perf.perl   |  76 +++++++++++\n t/t0212-trace2-event.sh   | 234 ++++++++++++++++++++++++++++++++\n t/t0212/parse_events.perl | 251 +++++++++++++++++++++++++++++++++++\n 10 files changed, 1173 insertions(+)\n create mode 100644 t/helper/test-trace2.c\n create mode 100755 t/t0210-trace2-normal.sh\n create mode 100644 t/t0210/scrub_normal.perl\n create mode 100755 t/t0211-trace2-perf.sh\n create mode 100644 t/t0211/scrub_perf.perl\n create mode 100755 t/t0212-trace2-event.sh\n create mode 100644 t/t0212/parse_events.perl\n\ndiff --git a/Makefile b/Makefile\nindex 2ab7b73000..040f5846d2 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -762,6 +762,7 @@ TEST_BUILTINS_OBJS += test-string-list.o\n TEST_BUILTINS_OBJS += test-submodule-config.o\n TEST_BUILTINS_OBJS += test-submodule-nested-repo-config.o\n TEST_BUILTINS_OBJS += test-subprocess.o\n+TEST_BUILTINS_OBJS += test-trace2.o\n TEST_BUILTINS_OBJS += test-urlmatch-normalization.o\n TEST_BUILTINS_OBJS += test-wildmatch.o\n TEST_BUILTINS_OBJS += test-windows-named-pipe.o\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 4dbfff7dee..25b1294353 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -51,6 +51,7 @@ static struct test_cmd cmds[] = {\n \t{ \"submodule-config\", cmd__submodule_config },\n \t{ \"submodule-nested-repo-config\", cmd__submodule_nested_repo_config },\n \t{ \"subprocess\", cmd__subprocess },\n+\t{ \"trace2\", cmd__trace2 },\n \t{ \"urlmatch-normalization\", cmd__urlmatch_normalization },\n \t{ \"wildmatch\", cmd__wildmatch },\n #ifdef GIT_WINDOWS_NATIVE\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex ca5c88edb2..61d2938ce5 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -46,6 +46,7 @@ int cmd__string_list(int argc, const char **argv);\n int cmd__submodule_config(int argc, const char **argv);\n int cmd__submodule_nested_repo_config(int argc, const char **argv);\n int cmd__subprocess(int argc, const char **argv);\n+int cmd__trace2(int argc, const char **argv);\n int cmd__urlmatch_normalization(int argc, const char **argv);\n int cmd__wildmatch(int argc, const char **argv);\n #ifdef GIT_WINDOWS_NATIVE\ndiff --git a/t/helper/test-trace2.c b/t/helper/test-trace2.c\nnew file mode 100644\nindex 0000000000..197819c872\n--- /dev/null\n+++ b/t/helper/test-trace2.c\n@@ -0,0 +1,273 @@\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"argv-array.h\"\n+#include \"run-command.h\"\n+#include \"exec-cmd.h\"\n+#include \"config.h\"\n+\n+typedef int(fn_unit_test)(int argc, const char **argv);\n+\n+struct unit_test {\n+\tfn_unit_test *ut_fn;\n+\tconst char *ut_name;\n+\tconst char *ut_usage;\n+};\n+\n+#define MyOk 0\n+#define MyError 1\n+\n+static int get_i(int *p_value, const char *data)\n+{\n+\tchar *endptr;\n+\n+\tif (!data || !*data)\n+\t\treturn MyError;\n+\n+\t*p_value = strtol(data, &endptr, 10);\n+\tif (*endptr || errno == ERANGE)\n+\t\treturn MyError;\n+\n+\treturn MyOk;\n+}\n+\n+/*\n+ * Cause process to exit with the requested value via \"return\".\n+ *\n+ * Rely on test-tool.c:cmd_main() to call trace2_cmd_exit()\n+ * with our result.\n+ *\n+ * Test harness can confirm:\n+ * [] the process-exit value.\n+ * [] the \"code\" field in the \"exit\" trace2 event.\n+ * [] the \"code\" field in the \"atexit\" trace2 event.\n+ * [] the \"name\" field in the \"cmd_name\" trace2 event.\n+ * [] \"def_param\" events for all of the \"interesting\" pre-defined\n+ * config settings.\n+ */\n+static int ut_001return(int argc, const char **argv)\n+{\n+\tint rc;\n+\n+\tif (get_i(&rc, argv[0]))\n+\t\tdie(\"expect <exit_code>\");\n+\n+\treturn rc;\n+}\n+\n+/*\n+ * Cause the process to exit with the requested value via \"exit()\".\n+ *\n+ * Test harness can confirm:\n+ * [] the \"code\" field in the \"exit\" trace2 event.\n+ * [] the \"code\" field in the \"atexit\" trace2 event.\n+ * [] the \"name\" field in the \"cmd_name\" trace2 event.\n+ * [] \"def_param\" events for all of the \"interesting\" pre-defined\n+ * config settings.\n+ */\n+static int ut_002exit(int argc, const char **argv)\n+{\n+\tint rc;\n+\n+\tif (get_i(&rc, argv[0]))\n+\t\tdie(\"expect <exit_code>\");\n+\n+\texit(rc);\n+}\n+\n+/*\n+ * Send an \"error\" event with each value in argv.  Normally, git only issues\n+ * a single \"error\" event immediately before issuing an \"exit\" event (such\n+ * as in die() or BUG()), but multiple \"error\" events are allowed.\n+ *\n+ * Test harness can confirm:\n+ * [] a trace2 \"error\" event for each value in argv.\n+ * [] the \"name\" field in the \"cmd_name\" trace2 event.\n+ * [] (optional) the file:line in the \"exit\" event refers to this function.\n+ */\n+static int ut_003error(int argc, const char **argv)\n+{\n+\tint k;\n+\n+\tif (!argv[0] || !*argv[0])\n+\t\tdie(\"expect <error_message>\");\n+\n+\tfor (k = 0; k < argc; k++)\n+\t\terror(\"%s\", argv[k]);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Run a child process and wait for it to finish and exit with its return code.\n+ * test-tool trace2 004child [<child-command-line>]\n+ *\n+ * For example:\n+ * test-tool trace2 004child git version\n+ * test-tool trace2 004child test-tool trace2 001return 0\n+ * test-tool trace2 004child test-tool trace2 004child test-tool trace2 004child\n+ * test-tool trace2 004child git -c alias.xyz=version xyz\n+ *\n+ * Test harness can confirm:\n+ * [] the \"name\" field in the \"cmd_name\" trace2 event.\n+ * [] that the outer process has a single component SID (or depth \"d0\" in\n+ *    the PERF stream).\n+ * [] that \"child_start\" and \"child_exit\" events are generated for the child.\n+ * [] if the child process is an instrumented executable:\n+ *    [] that \"version\", \"start\", ..., \"exit\", and \"atexit\" events are\n+ *       generated by the child process.\n+ *    [] that the child process events have a multiple component SID (or\n+ *       depth \"dN+1\" in the PERF stream).\n+ * [] that the child exit code is propagated to the parent process \"exit\"\n+ *    and \"atexit\" events..\n+ * [] (optional) that the \"t_abs\" field in the child process \"atexit\" event\n+ *    is less than the \"t_rel\" field in the \"child_exit\" event of the parent\n+ *    process.\n+ * [] if the child process is like the alias example above,\n+ *    [] (optional) the child process attempts to run \"git-xyx\" as a dashed\n+ *       command.\n+ *    [] the child process emits an \"alias\" event with \"xyz\" => \"version\"\n+ *    [] the child process runs \"git version\" as a child process.\n+ *    [] the child process has a 3 component SID (or depth \"d2\" in the PERF\n+ *       stream).\n+ */\n+static int ut_004child(int argc, const char **argv)\n+{\n+\tint result;\n+\n+\t/*\n+\t * Allow empty <child_command_line> so we can do arbitrarily deep\n+\t * command nesting and let the last one be null.\n+\t */\n+\tif (!argc)\n+\t\treturn 0;\n+\n+\tresult = run_command_v_opt(argv, 0);\n+\texit(result);\n+}\n+\n+/*\n+ * Exec a git command.  This may either create a child process (Windows)\n+ * or replace the existing process.\n+ * test-tool trace2 005exec <git_command_args>\n+ *\n+ * For example:\n+ * test-tool trace2 005exec version\n+ *\n+ * Test harness can confirm (on Windows):\n+ * [] the \"name\" field in the \"cmd_name\" trace2 event.\n+ * [] that the outer process has a single component SID (or depth \"d0\" in\n+ *    the PERF stream).\n+ * [] that \"exec\" and \"exec_result\" events are generated for the child\n+ *    process (since the Windows compatibility layer fakes an exec() with\n+ *    a CreateProcess(), WaitForSingleObject(), and exit()).\n+ * [] that the child process has multiple component SID (or depth \"dN+1\"\n+ *    in the PERF stream).\n+ *\n+ * Test harness can confirm (on platforms with a real exec() function):\n+ * [] TODO talk about process replacement and how it affects SID.\n+ */\n+static int ut_005exec(int argc, const char **argv)\n+{\n+\tint result;\n+\n+\tif (!argc)\n+\t\treturn 0;\n+\n+\tresult = execv_git_cmd(argv);\n+\treturn result;\n+}\n+\n+static int ut_006data(int argc, const char **argv)\n+{\n+\tconst char *usage_error =\n+\t\t\"expect <cat0> <k0> <v0> [<cat1> <k1> <v1> [...]]\";\n+\n+\tif (argc % 3 != 0)\n+\t\tdie(\"%s\", usage_error);\n+\n+\twhile (argc) {\n+\t\tif (!argv[0] || !*argv[0] || !argv[1] || !*argv[1] ||\n+\t\t    !argv[2] || !*argv[2])\n+\t\t\tdie(\"%s\", usage_error);\n+\n+\t\ttrace2_data_string(argv[0], the_repository, argv[1], argv[2]);\n+\t\targv += 3;\n+\t\targc -= 3;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Usage:\n+ *     test-tool trace2 <ut_name_1> <ut_usage_1>\n+ *     test-tool trace2 <ut_name_2> <ut_usage_2>\n+ *     ...\n+ */\n+#define USAGE_PREFIX \"test-tool trace2\"\n+\n+/* clang-format off */\n+static struct unit_test ut_table[] = {\n+\t{ ut_001return,   \"001return\", \"<exit_code>\" },\n+\t{ ut_002exit,     \"002exit\",   \"<exit_code>\" },\n+\t{ ut_003error,    \"003error\",  \"<error_message>+\" },\n+\t{ ut_004child,    \"004child\",  \"[<child_command_line>]\" },\n+\t{ ut_005exec,     \"005exec\",   \"<git_command_args>\" },\n+\t{ ut_006data,     \"006data\",   \"[<category> <key> <value>]+\" },\n+};\n+/* clang-format on */\n+\n+/* clang-format off */\n+#define for_each_ut(k, ut_k)\t\t\t\\\n+\tfor (k = 0, ut_k = &ut_table[k];\t\\\n+\t     k < ARRAY_SIZE(ut_table);\t\t\\\n+\t     k++, ut_k = &ut_table[k])\n+/* clang-format on */\n+\n+static int print_usage(void)\n+{\n+\tint k;\n+\tstruct unit_test *ut_k;\n+\n+\tfprintf(stderr, \"usage:\\n\");\n+\tfor_each_ut (k, ut_k)\n+\t\tfprintf(stderr, \"\\t%s %s %s\\n\", USAGE_PREFIX, ut_k->ut_name,\n+\t\t\tut_k->ut_usage);\n+\n+\treturn 129;\n+}\n+\n+/*\n+ * Issue various trace2 events for testing.\n+ *\n+ * We assume that these trace2 routines has already been called:\n+ *    [] trace2_initialize()      [common-main.c:main()]\n+ *    [] trace2_cmd_start()       [common-main.c:main()]\n+ *    [] trace2_cmd_name()        [test-tool.c:cmd_main()]\n+ *    [] tracd2_cmd_list_config() [test-tool.c:cmd_main()]\n+ * So that:\n+ *    [] the various trace2 streams are open.\n+ *    [] the process SID has been created.\n+ *    [] the \"version\" event has been generated.\n+ *    [] the \"start\" event has been generated.\n+ *    [] the \"cmd_name\" event has been generated.\n+ *    [] this writes various \"def_param\" events for interesting config values.\n+ *\n+ * We further assume that if we return (rather than exit()), trace2_cmd_exit()\n+ * will be called by test-tool.c:cmd_main().\n+ */\n+int cmd__trace2(int argc, const char **argv)\n+{\n+\tint k;\n+\tstruct unit_test *ut_k;\n+\n+\targc--; /* skip over \"trace2\" arg */\n+\targv++;\n+\n+\tif (argc)\n+\t\tfor_each_ut (k, ut_k)\n+\t\t\tif (!strcmp(argv[0], ut_k->ut_name))\n+\t\t\t\treturn ut_k->ut_fn(argc - 1, argv + 1);\n+\n+\treturn print_usage();\n+}\ndiff --git a/t/t0210-trace2-normal.sh b/t/t0210-trace2-normal.sh\nnew file mode 100755\nindex 0000000000..03a0aedb1d\n--- /dev/null\n+++ b/t/t0210-trace2-normal.sh\n@@ -0,0 +1,135 @@\n+#!/bin/sh\n+\n+test_description='test trace2 facility (normal target)'\n+. ./test-lib.sh\n+\n+# Add t/helper directory to PATH so that we can use a relative\n+# path to run nested instances of test-tool.exe (see 004child).\n+# This helps with HEREDOC comparisons later.\n+TTDIR=\"$GIT_BUILD_DIR/t/helper/\" && export TTDIR\n+PATH=\"$TTDIR:$PATH\" && export PATH\n+\n+# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe\n+# Warning: to do the actual diff/comparison, so the HEREDOCs here\n+# Warning: only cover our actual calls to test-tool and/or git.\n+# Warning: So you may see extra lines in artifact files when\n+# Warning: interactively debugging.\n+\n+# Turn off any inherited trace2 settings for this test.\n+unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT\n+unset GIT_TR2_BRIEF\n+unset GIT_TR2_CONFIG_PARAMS\n+\n+V=$(git version | sed -e 's/^git version //') && export V\n+\n+# There are multiple trace2 targets: normal, perf, and event.\n+# Trace2 events will/can be written to each active target (subject\n+# to whatever filtering that target decides to do).\n+# This script tests the normal target in isolation.\n+#\n+# Defer setting GIT_TR2 until the actual command line we want to test\n+# because hidden git and test-tool commands run by the test harness\n+# can contaminate our output.\n+\n+# Enable \"brief\" feature which turns off \"<clock> <file>:<line> \" prefix.\n+GIT_TR2_BRIEF=1 && export GIT_TR2_BRIEF\n+\n+# Basic tests of the trace2 normal stream.  Since this stream is used\n+# primarily with printf-style debugging/tracing, we do limited testing\n+# here.\n+#\n+# We do confirm the following API features:\n+# [] the 'version <v>' event\n+# [] the 'start <argv>' event\n+# [] the 'cmd_name <name>' event\n+# [] the 'exit <time> code:<code>' event\n+# [] the 'atexit <time> code:<code>' event\n+#\n+# Fields of the form _FIELD_ are tokens that have been replaced (such\n+# as the elapsed time).\n+\n+# Verb 001return\n+#\n+# Implicit return from cmd_<verb> function propagates <code>.\n+\n+test_expect_success 'normal stream, return code 0' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\tGIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 001return 0\n+\t\tcmd_name trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:0\n+\t\tatexit elapsed:_TIME_ code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'normal stream, return code 1' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\ttest_must_fail env GIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 001return 1 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 001return 1\n+\t\tcmd_name trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:1\n+\t\tatexit elapsed:_TIME_ code:1\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 002exit\n+#\n+# Explicit exit(code) from within cmd_<verb> propagates <code>.\n+\n+test_expect_success 'normal stream, exit code 0' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\tGIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 002exit 0 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 002exit 0\n+\t\tcmd_name trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:0\n+\t\tatexit elapsed:_TIME_ code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'normal stream, exit code 1' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\ttest_must_fail env GIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 002exit 1 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 002exit 1\n+\t\tcmd_name trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:1\n+\t\tatexit elapsed:_TIME_ code:1\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 003error\n+#\n+# To the above, add multiple 'error <msg>' events\n+\n+test_expect_success 'normal stream, error event' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\tGIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 003error \"hello world\" \"this is a test\" &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 003error '\\''hello world'\\'' '\\''this is a test'\\''\n+\t\tcmd_name trace2 (trace2)\n+\t\terror hello world\n+\t\terror this is a test\n+\t\texit elapsed:_TIME_ code:0\n+\t\tatexit elapsed:_TIME_ code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t0210/scrub_normal.perl b/t/t0210/scrub_normal.perl\nnew file mode 100644\nindex 0000000000..c65d1a815e\n--- /dev/null\n+++ b/t/t0210/scrub_normal.perl\n@@ -0,0 +1,48 @@\n+#!/usr/bin/perl\n+#\n+# Scrub the variable fields from the normal trace2 output to\n+# make testing easier.\n+\n+use strict;\n+use warnings;\n+\n+my $float = '[0-9]*\\.[0-9]+([eE][-+]?[0-9]+)?';\n+\n+# This code assumes that the trace2 data was written with bare\n+# turned on (which omits the \"<clock> <file>:<line>\" prefix.\n+\n+while (<>) {\n+    # Various messages include an elapsed time in the middle\n+    # of the message.  Replace the time with a placeholder to\n+    # simplify our HEREDOC in the test script.\n+    s/elapsed:$float/elapsed:_TIME_/g;\n+\n+    my $line = $_;\n+\n+    # we expect:\n+    #    start <argv0> [<argv1> [<argv2> [...]]]\n+    #\n+    # where argv0 might be a relative or absolute path, with\n+    # or without quotes, and platform dependent.  Replace argv0\n+    # with a token for HEREDOC matching in the test script.\n+\n+    if ($line =~ m/^start/) {\n+\t$line =~ /^start\\s+(.*)/;\n+\tmy $argv = $1;\n+\t$argv =~ m/(\\'[^\\']*\\'|[^ ]+)\\s+(.*)/;\n+\tmy $argv_0 = $1;\n+\tmy $argv_rest = $2;\n+\n+\tprint \"start _EXE_ $argv_rest\\n\";\n+    }\n+    elsif ($line =~ m/^cmd_path/) {\n+\t# Likewise, the 'cmd_path' message breaks out argv[0].\n+\t#\n+\t# This line is only emitted when RUNTIME_PREFIX is defined,\n+\t# so just omit it for testing purposes.\n+\t# print \"cmd_path _EXE_\\n\";\n+    }\n+    else {\n+\tprint \"$line\";\n+    }\n+}\ndiff --git a/t/t0211-trace2-perf.sh b/t/t0211-trace2-perf.sh\nnew file mode 100755\nindex 0000000000..953e2f7847\n--- /dev/null\n+++ b/t/t0211-trace2-perf.sh\n@@ -0,0 +1,153 @@\n+#!/bin/sh\n+\n+test_description='test trace2 facility (perf target)'\n+. ./test-lib.sh\n+\n+# Add t/helper directory to PATH so that we can use a relative\n+# path to run nested instances of test-tool.exe (see 004child).\n+# This helps with HEREDOC comparisons later.\n+TTDIR=\"$GIT_BUILD_DIR/t/helper/\" && export TTDIR\n+PATH=\"$TTDIR:$PATH\" && export PATH\n+\n+# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe\n+# Warning: to do the actual diff/comparison, so the HEREDOCs here\n+# Warning: only cover our actual calls to test-tool and/or git.\n+# Warning: So you may see extra lines in artifact files when\n+# Warning: interactively debugging.\n+\n+# Turn off any inherited trace2 settings for this test.\n+unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT\n+unset GIT_TR2_PERF_BRIEF\n+unset GIT_TR2_CONFIG_PARAMS\n+\n+V=$(git version | sed -e 's/^git version //') && export V\n+\n+# There are multiple trace2 targets: normal, perf, and event.\n+# Trace2 events will/can be written to each active target (subject\n+# to whatever filtering that target decides to do).\n+# Test each target independently.\n+#\n+# Defer setting GIT_TR2_PERF until the actual command we want to\n+# test because hidden git and test-tool commands in the test\n+# harness can contaminate our output.\n+\n+# Enable \"brief\" feature which turns off the prefix:\n+#     \"<clock> <file>:<line> | <nr_parents> | \"\n+GIT_TR2_PERF_BRIEF=1 && export GIT_TR2_PERF_BRIEF\n+\n+# Repeat some of the t0210 tests using the perf target stream instead of\n+# the normal stream.\n+#\n+# Tokens here of the form _FIELD_ have been replaced in the observed output.\n+\n+# Verb 001return\n+#\n+# Implicit return from cmd_<verb> function propagates <code>.\n+\n+test_expect_success 'perf stream, return code 0' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 001return 0\n+\t\td0|main|cmd_name|||||trace2 (trace2)\n+\t\td0|main|exit||_T_ABS_|||code:0\n+\t\td0|main|atexit||_T_ABS_|||code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'perf stream, return code 1' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\ttest_must_fail env GIT_TR2_PERF=\"$(pwd)/trace.perf\" test-tool trace2 001return 1 &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 001return 1\n+\t\td0|main|cmd_name|||||trace2 (trace2)\n+\t\td0|main|exit||_T_ABS_|||code:1\n+\t\td0|main|atexit||_T_ABS_|||code:1\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 003error\n+#\n+# To the above, add multiple 'error <msg>' events\n+\n+test_expect_success 'perf stream, error event' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" test-tool trace2 003error \"hello world\" \"this is a test\" &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 003error '\\''hello world'\\'' '\\''this is a test'\\''\n+\t\td0|main|cmd_name|||||trace2 (trace2)\n+\t\td0|main|error|||||hello world\n+\t\td0|main|error|||||this is a test\n+\t\td0|main|exit||_T_ABS_|||code:0\n+\t\td0|main|atexit||_T_ABS_|||code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 004child\n+#\n+# Test nested spawning of child processes.\n+#\n+# Conceptually, this looks like:\n+#    P1: TT trace2 004child\n+#    P2: |--- TT trace2 004child\n+#    P3:      |--- TT trace2 001return 0\n+#\n+# Which should generate events:\n+#    P1: version\n+#    P1: start\n+#    P1: cmd_name\n+#    P1: child_start\n+#        P2: version\n+#        P2: start\n+#        P2: cmd_name\n+#        P2: child_start\n+#            P3: version\n+#            P3: start\n+#            P3: cmd_name\n+#            P3: exit\n+#            P3: atexit\n+#        P2: child_exit\n+#        P2: exit\n+#        P2: atexit\n+#    P1: child_exit\n+#    P1: exit\n+#    P1: atexit\n+\n+test_expect_success 'perf stream, child processes' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" test-tool trace2 004child test-tool trace2 004child test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 004child test-tool trace2 004child test-tool trace2 001return 0\n+\t\td0|main|cmd_name|||||trace2 (trace2)\n+\t\td0|main|child_start||_T_ABS_|||[ch0] class:? argv: test-tool trace2 004child test-tool trace2 001return 0\n+\t\td1|main|version|||||$V\n+\t\td1|main|start|||||_EXE_ trace2 004child test-tool trace2 001return 0\n+\t\td1|main|cmd_name|||||trace2 (trace2/trace2)\n+\t\td1|main|child_start||_T_ABS_|||[ch0] class:? argv: test-tool trace2 001return 0\n+\t\td2|main|version|||||$V\n+\t\td2|main|start|||||_EXE_ trace2 001return 0\n+\t\td2|main|cmd_name|||||trace2 (trace2/trace2/trace2)\n+\t\td2|main|exit||_T_ABS_|||code:0\n+\t\td2|main|atexit||_T_ABS_|||code:0\n+\t\td1|main|child_exit||_T_ABS_|_T_REL_||[ch0] pid:_PID_ code:0\n+\t\td1|main|exit||_T_ABS_|||code:0\n+\t\td1|main|atexit||_T_ABS_|||code:0\n+\t\td0|main|child_exit||_T_ABS_|_T_REL_||[ch0] pid:_PID_ code:0\n+\t\td0|main|exit||_T_ABS_|||code:0\n+\t\td0|main|atexit||_T_ABS_|||code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t0211/scrub_perf.perl b/t/t0211/scrub_perf.perl\nnew file mode 100644\nindex 0000000000..351af7844e\n--- /dev/null\n+++ b/t/t0211/scrub_perf.perl\n@@ -0,0 +1,76 @@\n+#!/usr/bin/perl\n+#\n+# Scrub the variable fields from the perf trace2 output to\n+# make testing easier.\n+\n+use strict;\n+use warnings;\n+\n+my $qpath = '\\'[^\\']*\\'|[^ ]*';\n+\n+my $col_depth=0;\n+my $col_thread=1;\n+my $col_event=2;\n+my $col_repo=3;\n+my $col_t_abs=4;\n+my $col_t_rel=5;\n+my $col_category=6;\n+my $col_rest=7;\n+\n+# This code assumes that the trace2 data was written with bare\n+# turned on (which omits the \"<clock> <file>:<line> | <parents>\"\n+# prefix.\n+\n+while (<>) {\n+    my @tokens = split /\\|/;\n+\n+    foreach my $col (@tokens) { $col =~ s/^\\s+|\\s+$//g; }\n+\n+    if ($tokens[$col_event] =~ m/^start/) {\n+\t# The 'start' message lists the contents of argv in $col_rest.\n+\t# On some platforms (Windows), argv[0] is *sometimes* a canonical\n+\t# absolute path to the EXE rather than the value passed in the\n+\t# shell script.  Replace it with a placeholder to simplify our\n+\t# HEREDOC in the test script.\n+\tmy $argv0;\n+\tmy $argvRest;\n+\t$tokens[$col_rest] =~ s/^($qpath)\\W*(.*)/_EXE_ $2/;\n+    }\n+    elsif ($tokens[$col_event] =~ m/cmd_path/) {\n+\t# Likewise, the 'cmd_path' message breaks out argv[0].\n+\t#\n+\t# This line is only emitted when RUNTIME_PREFIX is defined,\n+\t# so just omit it for testing purposes.\n+\t# $tokens[$col_rest] = \"_EXE_\";\n+\tgoto SKIP_LINE;\n+    }\n+    elsif ($tokens[$col_event] =~ m/child_exit/) {\n+\t$tokens[$col_rest] =~ s/ pid:\\d* / pid:_PID_ /;\n+    }\n+    elsif ($tokens[$col_event] =~ m/data/) {\n+\tif ($tokens[$col_category] =~ m/process/) {\n+\t    # 'data' and 'data_json' events containing 'process'\n+\t    # category data are assumed to be platform-specific\n+\t    # and highly variable.  Just omit them.\n+\t    goto SKIP_LINE;\n+\t}\n+    }\n+\n+    # t_abs and t_rel are either blank or a float.  Replace the float\n+    # with a constant for matching the HEREDOC in the test script.\n+    if ($tokens[$col_t_abs] =~ m/\\d/) {\n+\t$tokens[$col_t_abs] = \"_T_ABS_\";\n+    }\n+    if ($tokens[$col_t_rel] =~ m/\\d/) {\n+\t$tokens[$col_t_rel] = \"_T_REL_\";\n+    }\n+\n+    my $out;\n+\n+    $out = join('|', @tokens);\n+    print \"$out\\n\";\n+\n+  SKIP_LINE:\n+}\n+\n+\ndiff --git a/t/t0212-trace2-event.sh b/t/t0212-trace2-event.sh\nnew file mode 100755\nindex 0000000000..ee3ab7ffa0\n--- /dev/null\n+++ b/t/t0212-trace2-event.sh\n@@ -0,0 +1,234 @@\n+#!/bin/sh\n+\n+test_description='test trace2 facility'\n+. ./test-lib.sh\n+\n+# Add t/helper directory to PATH so that we can use a relative\n+# path to run nested instances of test-tool.exe (see 004child).\n+# This helps with HEREDOC comparisons later.\n+TTDIR=\"$GIT_BUILD_DIR/t/helper/\" && export TTDIR\n+PATH=\"$TTDIR:$PATH\" && export PATH\n+\n+# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe\n+# Warning: to do the actual diff/comparison, so the HEREDOCs here\n+# Warning: only cover our actual calls to test-tool and/or git.\n+# Warning: So you may see extra lines in artifact files when\n+# Warning: interactively debugging.\n+\n+# Turn off any inherited trace2 settings for this test.\n+unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT\n+unset GIT_TR2_BARE\n+unset GIT_TR2_CONFIG_PARAMS\n+\n+V=$(git version | sed -e 's/^git version //') && export V\n+\n+# There are multiple trace2 targets: normal, perf, and event.\n+# Trace2 events will/can be written to each active target (subject\n+# to whatever filtering that target decides to do).\n+# Test each target independently.\n+#\n+# Defer setting GIT_TR2_PERF until the actual command we want to\n+# test because hidden git and test-tool commands in the test\n+# harness can contaminate our output.\n+\n+# We don't bother repeating the 001return and 002exit tests, since they\n+# have coverage in the normal and perf targets.\n+\n+# Verb 003error\n+#\n+# To the above, add multiple 'error <msg>' events\n+\n+test_expect_success 'event stream, error event' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 003error \"hello world\" \"this is a test\" &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"003error\",\n+\t|      \"hello world\",\n+\t|      \"this is a test\"\n+\t|    ],\n+\t|    \"errors\":[\n+\t|      \"%s\",\n+\t|      \"%s\"\n+\t|    ],\n+\t|    \"exit_code\":0,\n+\t|    \"hierarchy\":\"trace2\",\n+\t|    \"name\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 004child\n+#\n+# Test nested spawning of child processes.\n+#\n+# Conceptually, this looks like:\n+#    P1: TT trace2 004child\n+#    P2: |--- TT trace2 004child\n+#    P3:      |--- TT trace2 001return 0\n+\n+test_expect_success 'event stream, return code 0' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 004child test-tool trace2 004child test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"004child\",\n+\t|      \"test-tool\",\n+\t|      \"trace2\",\n+\t|      \"004child\",\n+\t|      \"test-tool\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"child\":{\n+\t|      \"0\":{\n+\t|        \"child_argv\":[\n+\t|          \"_EXE_\",\n+\t|          \"trace2\",\n+\t|          \"004child\",\n+\t|          \"test-tool\",\n+\t|          \"trace2\",\n+\t|          \"001return\",\n+\t|          \"0\"\n+\t|        ],\n+\t|        \"child_class\":\"?\",\n+\t|        \"child_code\":0,\n+\t|        \"use_shell\":0\n+\t|      }\n+\t|    },\n+\t|    \"exit_code\":0,\n+\t|    \"hierarchy\":\"trace2\",\n+\t|    \"name\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  },\n+\t|  \"_SID0_/_SID1_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"004child\",\n+\t|      \"test-tool\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"child\":{\n+\t|      \"0\":{\n+\t|        \"child_argv\":[\n+\t|          \"_EXE_\",\n+\t|          \"trace2\",\n+\t|          \"001return\",\n+\t|          \"0\"\n+\t|        ],\n+\t|        \"child_class\":\"?\",\n+\t|        \"child_code\":0,\n+\t|        \"use_shell\":0\n+\t|      }\n+\t|    },\n+\t|    \"exit_code\":0,\n+\t|    \"hierarchy\":\"trace2/trace2\",\n+\t|    \"name\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  },\n+\t|  \"_SID0_/_SID1_/_SID2_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"exit_code\":0,\n+\t|    \"hierarchy\":\"trace2/trace2/trace2\",\n+\t|    \"name\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Test listing of all \"interesting\" config settings.\n+\n+test_expect_success 'event stream, list config' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tgit config --local t0212.abc 1 &&\n+\tgit config --local t0212.def \"hello world\" &&\n+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" GIT_TR2_CONFIG_PARAMS=\"t0212.*\" test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"exit_code\":0,\n+\t|    \"hierarchy\":\"trace2\",\n+\t|    \"name\":\"trace2\",\n+\t|    \"params\":[\n+\t|      {\n+\t|        \"param\":\"t0212.abc\",\n+\t|        \"value\":\"1\"\n+\t|      },\n+\t|      {\n+\t|        \"param\":\"t0212.def\",\n+\t|        \"value\":\"hello world\"\n+\t|      }\n+\t|    ],\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'basic trace2_data' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 006data test_category k1 v1 test_category k2 v2 &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"006data\",\n+\t|      \"test_category\",\n+\t|      \"k1\",\n+\t|      \"v1\",\n+\t|      \"test_category\",\n+\t|      \"k2\",\n+\t|      \"v2\"\n+\t|    ],\n+\t|    \"data\":{\n+\t|      \"test_category\":{\n+\t|        \"k1\":\"v1\",\n+\t|        \"k2\":\"v2\"\n+\t|      }\n+\t|    },\n+\t|    \"exit_code\":0,\n+\t|    \"hierarchy\":\"trace2\",\n+\t|    \"name\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t0212/parse_events.perl b/t/t0212/parse_events.perl\nnew file mode 100644\nindex 0000000000..a2776ba216\n--- /dev/null\n+++ b/t/t0212/parse_events.perl\n@@ -0,0 +1,251 @@\n+#!/usr/bin/perl\n+#\n+# Parse event stream and convert individual events into a summary\n+# record for the process.\n+#\n+# Git.exe generates one or more \"event\" records for each API method,\n+# such as \"start <argv>\" and \"exit <code>\", during the life of the git\n+# process.  Additionally, the input may contain interleaved events\n+# from multiple concurrent git processes and/or multiple threads from\n+# within a git process.\n+#\n+# Accumulate events for each process (based on its unique SID) in a\n+# dictionary and emit process summary records.\n+#\n+# Convert some of the variable fields (such as elapsed time) into\n+# placeholders (or omit them) to make HEREDOC comparisons easier in\n+# the test scripts.\n+#\n+# We may also omit fields not (currently) useful for testing purposes.\n+\n+use strict;\n+use warnings;\n+use JSON::PP;\n+use Data::Dumper;\n+use Getopt::Long;\n+\n+# The version of the trace2 event target format that we understand.\n+# This is reported in the 'version' event in the 'evt' field.\n+# It comes from the GIT_TR2_EVENT_VERSION macro in trace2/tr2_tgt_event.c\n+my $evt_version = '1';\n+\n+my $show_children = 1;\n+my $show_exec     = 1;\n+my $show_threads  = 1;\n+\n+# A hack to generate test HEREDOC data for pasting into the test script.\n+# Usage:\n+#    cd \"t/trash directory.t0212-trace2-event\"\n+#    $TT trace ... >trace.event\n+#    VV=$(../../git.exe version | sed -e 's/^git version //')\n+#    perl ../t0212/parse_events.perl --HEREDOC --VERSION=$VV <trace.event >heredoc\n+# Then paste heredoc into your new test.\n+\n+my $gen_heredoc = 0;\n+my $gen_version = '';\n+\n+GetOptions(\"children!\" => \\$show_children,\n+\t   \"exec!\"     => \\$show_exec,\n+\t   \"threads!\"  => \\$show_threads,\n+\t   \"HEREDOC!\"  => \\$gen_heredoc,\n+\t   \"VERSION=s\" => \\$gen_version    )\n+    or die(\"Error in command line arguments\\n\");\n+\n+\n+# SIDs contains timestamps and PIDs of the process and its parents.\n+# This makes it difficult to match up in a HEREDOC in the test script.\n+# Build a map from actual SIDs to predictable constant values and yet\n+# keep the parent/child relationships.  For example:\n+# {..., \"sid\":\"1539706952458276-8652\", ...}\n+# {..., \"sid\":\"1539706952458276-8652/1539706952649493-15452\", ...}\n+# becomes:\n+# {..., \"sid\":\"_SID1_\", ...}\n+# {..., \"sid\":\"_SID1_/_SID2_\", ...}\n+my $sid_map;\n+my $sid_count = 0;\n+\n+my $processes;\n+\n+while (<>) {\n+    my $line = decode_json( $_ );\n+\n+    my $sid = \"\";\n+    my $sid_sep = \"\";\n+\n+    my $raw_sid = $line->{'sid'};\n+    my @raw_sid_parts = split /\\//, $raw_sid;\n+    foreach my $raw_sid_k (@raw_sid_parts) {\n+\tif (!exists $sid_map->{$raw_sid_k}) {\n+\t    $sid_map->{$raw_sid_k} = '_SID' . $sid_count . '_';\n+\t    $sid_count++;\n+\t}\n+\t$sid = $sid . $sid_sep . $sid_map->{$raw_sid_k};\n+\t$sid_sep = '/';\n+    }\n+    \n+    my $event = $line->{'event'};\n+\n+    if ($event eq 'version') {\n+\t$processes->{$sid}->{'version'} = $line->{'exe'};\n+\tif ($gen_heredoc == 1 && $gen_version eq $line->{'exe'}) {\n+\t    # If we are generating data FOR the test script, replace\n+\t    # the reported git.exe version with a reference to an\n+\t    # environment variable.  When our output is pasted into\n+\t    # the test script, it will then be expanded in future\n+\t    # test runs to the THEN current version of git.exe.\n+\t    # We assume that the test script uses env var $V.\n+\t    $processes->{$sid}->{'version'} = \"\\$V\";\n+\t}\n+    }\n+\n+    elsif ($event eq 'start') {\n+\t$processes->{$sid}->{'argv'} = $line->{'argv'};\n+\t$processes->{$sid}->{'argv'}[0] = \"_EXE_\";\n+    }\n+\n+    elsif ($event eq 'exit') {\n+\t$processes->{$sid}->{'exit_code'} = $line->{'code'};\n+    }\n+\n+    elsif ($event eq 'atexit') {\n+\t$processes->{$sid}->{'exit_code'} = $line->{'code'};\n+    }\n+\n+    elsif ($event eq 'error') {\n+\t# For HEREDOC purposes, use the error message format string if\n+\t# available, rather than the formatted message (which probably\n+\t# has an absolute pathname).\n+\tif (exists $line->{'fmt'}) {\n+\t    push( @{$processes->{$sid}->{'errors'}}, $line->{'fmt'} );\n+\t}\n+\telsif (exists $line->{'msg'}) {\n+\t    push( @{$processes->{$sid}->{'errors'}}, $line->{'msg'} );\n+\t}\n+    }\n+\n+    elsif ($event eq 'cmd_path') {\n+\t## $processes->{$sid}->{'path'} = $line->{'path'};\n+\t#\n+\t# Like in the 'start' event, we need to replace the value of\n+\t# argv[0] with a token for HEREDOC purposes.  However, the\n+\t# event is only emitted when RUNTIME_PREFIX is defined, so\n+\t# just omit it for testing purposes.\n+\t# $processes->{$sid}->{'path'} = \"_EXE_\";\n+    }\n+    \n+    elsif ($event eq 'cmd_name') {\n+\t$processes->{$sid}->{'name'} = $line->{'name'};\n+\t$processes->{$sid}->{'hierarchy'} = $line->{'hierarchy'};\n+    }\n+\n+    elsif ($event eq 'alias') {\n+\t$processes->{$sid}->{'alias'}->{'key'} = $line->{'alias'};\n+\t$processes->{$sid}->{'alias'}->{'argv'} = $line->{'argv'};\n+    }\n+\n+    elsif ($event eq 'def_param') {\n+\tmy $kv;\n+\t$kv->{'param'} = $line->{'param'};\n+\t$kv->{'value'} = $line->{'value'};\n+\tpush( @{$processes->{$sid}->{'params'}}, $kv );\n+    }\n+\n+    elsif ($event eq 'child_start') {\n+\tif ($show_children == 1) {\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_class'} = $line->{'child_class'};\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_argv'} = $line->{'argv'};\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_argv'}[0] = \"_EXE_\";\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'use_shell'} = $line->{'use_shell'} ? 1 : 0;\n+\t}\n+    }\n+\n+    elsif ($event eq 'child_exit') {\n+\tif ($show_children == 1) {\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_code'} = $line->{'code'};\n+\t}\n+    }\n+\n+    # TODO decide what information we want to test from thread events.\n+\n+    elsif ($event eq 'thread_start') {\n+\tif ($show_threads == 1) {\n+\t}\n+    }\n+\n+    elsif ($event eq 'thread_exit') {\n+\tif ($show_threads == 1) {\n+\t}\n+    }\n+\n+    # TODO decide what information we want to test from exec events.\n+\n+    elsif ($event eq 'exec') {\n+\tif ($show_exec == 1) {\n+\t}\n+    }\n+\n+    elsif ($event eq 'exec_result') {\n+\tif ($show_exec == 1) {\n+\t}\n+    }\n+\n+    elsif ($event eq 'def_param') {\n+\t# Accumulate parameter key/value pairs by key rather than in an array\n+\t# so that we get overwrite (last one wins) effects.\n+\t$processes->{$sid}->{'params'}->{$line->{'param'}} = $line->{'value'};\n+    }\n+\n+    elsif ($event eq 'def_repo') {\n+\t# $processes->{$sid}->{'repos'}->{$line->{'repo'}} = $line->{'worktree'};\n+\t$processes->{$sid}->{'repos'}->{$line->{'repo'}} = \"_WORKTREE_\";\n+    }\n+\n+    # A series of potentially nested and threaded region and data events\n+    # is fundamentally incompatibile with the type of summary record we\n+    # are building in this script.  Since they are intended for\n+    # perf-trace-like analysis rather than a result summary, we ignore\n+    # most of them here.\n+\n+    # elsif ($event eq 'region_enter') {\n+    # }\n+    # elsif ($event eq 'region_leave') {\n+    # }\n+\n+    elsif ($event eq 'data') {\n+\tmy $cat = $line->{'category'};\n+\tif ($cat eq 'test_category') {\n+\t    \n+\t    my $key = $line->{'key'};\n+\t    my $value = $line->{'value'};\n+\t    $processes->{$sid}->{'data'}->{$cat}->{$key} = $value;\n+\t}\n+    }\n+\n+    # This trace2 target does not emit 'printf' events.\n+    #\n+    # elsif ($event eq 'printf') {\n+    # }\n+}\n+\n+# Dump the resulting hash into something that we can compare against\n+# in the test script.  These options make Dumper output look a little\n+# bit like JSON.  Also convert variable references of the form \"$VAR*\"\n+# so that the matching HEREDOC doesn't need to escape it.\n+\n+$Data::Dumper::Sortkeys = 1;\n+$Data::Dumper::Indent = 1;\n+$Data::Dumper::Purity = 1;\n+$Data::Dumper::Pair = ':';\n+\n+my $out = Dumper($processes);\n+$out =~ s/'/\"/g;\n+$out =~ s/\\$VAR/VAR/g;\n+\n+# Finally, if we're running this script to generate (manually confirmed)\n+# data to add to the test script, guard the indentation.\n+\n+if ($gen_heredoc == 1) {\n+    $out =~ s/^/\\t\\|/gms;\n+}\n+\n+print $out;\n-- \ngitgitgadget\n\n"},{"id":"368607","messageId":"bca6a7f0d6d58a1f0384933200c2e7f7fc4c7b42.1549473350.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 09/15] trace2:data: add trace2 instrumentation to index read/write","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-06T17:16:00Z","receivedAt":"2019-02-06T17:16:19Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 events to measure reading and writing the index.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n read-cache.c | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 50 insertions(+), 1 deletion(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex bfff271a3d..240531d70d 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -2232,6 +2232,16 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)\n \t\tload_index_extensions(&p);\n \t}\n \tmunmap((void *)mmap, mmap_size);\n+\n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/version\",\n+\t\t\t   istate->version);\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/cache_nr\",\n+\t\t\t   istate->cache_nr);\n+\n \treturn istate->cache_nr;\n \n unmap:\n@@ -2263,9 +2273,17 @@ int read_index_from(struct index_state *istate, const char *path,\n \tif (istate->initialized)\n \t\treturn istate->cache_nr;\n \n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_region_enter_printf(\"index\", \"do_read_index\", the_repository,\n+\t\t\t\t   \"%s\", path);\n \ttrace_performance_enter();\n \tret = do_read_index(istate, path, 0);\n \ttrace_performance_leave(\"read cache %s\", path);\n+\ttrace2_region_leave_printf(\"index\", \"do_read_index\", the_repository,\n+\t\t\t\t   \"%s\", path);\n \n \tsplit_index = istate->split_index;\n \tif (!split_index || is_null_oid(&split_index->base_oid)) {\n@@ -2281,7 +2299,11 @@ int read_index_from(struct index_state *istate, const char *path,\n \n \tbase_oid_hex = oid_to_hex(&split_index->base_oid);\n \tbase_path = xstrfmt(\"%s/sharedindex.%s\", gitdir, base_oid_hex);\n+\ttrace2_region_enter_printf(\"index\", \"shared/do_read_index\",\n+\t\t\t\t   the_repository, \"%s\", base_path);\n \tret = do_read_index(split_index->base, base_path, 1);\n+\ttrace2_region_leave_printf(\"index\", \"shared/do_read_index\",\n+\t\t\t\t   the_repository, \"%s\", base_path);\n \tif (!oideq(&split_index->base_oid, &split_index->base->oid))\n \t\tdie(_(\"broken index, expect %s in %s, got %s\"),\n \t\t    base_oid_hex, base_path,\n@@ -2988,6 +3010,16 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \tistate->timestamp.sec = (unsigned int)st.st_mtime;\n \tistate->timestamp.nsec = ST_MTIME_NSEC(st);\n \ttrace_performance_since(start, \"write index, changed mask = %x\", istate->cache_changed);\n+\n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_data_intmax(\"index\", the_repository, \"write/version\",\n+\t\t\t   istate->version);\n+\ttrace2_data_intmax(\"index\", the_repository, \"write/cache_nr\",\n+\t\t\t   istate->cache_nr);\n+\n \treturn 0;\n }\n \n@@ -3007,7 +3039,18 @@ static int commit_locked_index(struct lock_file *lk)\n static int do_write_locked_index(struct index_state *istate, struct lock_file *lock,\n \t\t\t\t unsigned flags)\n {\n-\tint ret = do_write_index(istate, lock->tempfile, 0);\n+\tint ret;\n+\n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_region_enter_printf(\"index\", \"do_write_index\", the_repository,\n+\t\t\t\t   \"%s\", lock->tempfile->filename.buf);\n+\tret = do_write_index(istate, lock->tempfile, 0);\n+\ttrace2_region_leave_printf(\"index\", \"do_write_index\", the_repository,\n+\t\t\t\t   \"%s\", lock->tempfile->filename.buf);\n+\n \tif (ret)\n \t\treturn ret;\n \tif (flags & COMMIT_LOCK)\n@@ -3092,7 +3135,13 @@ static int write_shared_index(struct index_state *istate,\n \tint ret;\n \n \tmove_cache_to_base_index(istate);\n+\n+\ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n+\t\t\t\t   the_repository, \"%s\", (*temp)->filename.buf);\n \tret = do_write_index(si->base, *temp, 1);\n+\ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n+\t\t\t\t   the_repository, \"%s\", (*temp)->filename.buf);\n+\n \tif (ret)\n \t\treturn ret;\n \tret = adjust_shared_perm(get_tempfile_path(*temp));\n-- \ngitgitgadget\n\n"},{"id":"368608","messageId":"90cec071cfdbd723b27fdbb474532682b579ea9d.1549473350.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 13/15] trace2:data: add subverb for rebase","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-06T17:16:04Z","receivedAt":"2019-02-06T17:16:19Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/rebase.c | 17 +++++++++++++++++\n 1 file changed, 17 insertions(+)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 774264bae8..f5ac4fe2ea 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -850,6 +850,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tACTION_EDIT_TODO,\n \t\tACTION_SHOW_CURRENT_PATCH,\n \t} action = NO_ACTION;\n+\tstatic const char *action_names[] = { N_(\"undefined\"),\n+\t\t\t\t\t      N_(\"continue\"),\n+\t\t\t\t\t      N_(\"skip\"),\n+\t\t\t\t\t      N_(\"abort\"),\n+\t\t\t\t\t      N_(\"quit\"),\n+\t\t\t\t\t      N_(\"edit_todo\"),\n+\t\t\t\t\t      N_(\"show_current_patch\"),\n+\t\t\t\t\t      NULL };\n \tconst char *gpg_sign = NULL;\n \tstruct string_list exec = STRING_LIST_INIT_NODUP;\n \tconst char *rebase_merges = NULL;\n@@ -1039,6 +1047,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tdie(_(\"The --edit-todo action can only be used during \"\n \t\t      \"interactive rebase.\"));\n \n+\tif (trace2_is_enabled()) {\n+\t\tif (is_interactive(&options))\n+\t\t\ttrace2_cmd_mode(\"interactive\");\n+\t\telse if (exec.nr)\n+\t\t\ttrace2_cmd_mode(\"interactive-exec\");\n+\t\telse\n+\t\t\ttrace2_cmd_mode(action_names[action]);\n+\t}\n+\n \tswitch (action) {\n \tcase ACTION_CONTINUE: {\n \t\tstruct object_id head;\n-- \ngitgitgadget\n\n"},{"id":"368609","messageId":"99d13ef478b7f004a36795cc28bd2e0beadfec58.1549473350.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 05/15] trace2:data: add editor/pager child classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-06T17:15:57Z","receivedAt":"2019-02-06T17:16:21Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 process classification for editor and pager\nchild processes.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n editor.c | 1 +\n pager.c  | 1 +\n 2 files changed, 2 insertions(+)\n\ndiff --git a/editor.c b/editor.c\nindex c985eee1f9..71547674ab 100644\n--- a/editor.c\n+++ b/editor.c\n@@ -78,6 +78,7 @@ static int launch_specified_editor(const char *editor, const char *path,\n \t\tp.argv = args;\n \t\tp.env = env;\n \t\tp.use_shell = 1;\n+\t\tp.trace2_child_class = \"editor\";\n \t\tif (start_command(&p) < 0)\n \t\t\treturn error(\"unable to start editor '%s'\", editor);\n \ndiff --git a/pager.c b/pager.c\nindex a768797fcf..4168460ae9 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -100,6 +100,7 @@ void prepare_pager_args(struct child_process *pager_process, const char *pager)\n \targv_array_push(&pager_process->args, pager);\n \tpager_process->use_shell = 1;\n \tsetup_pager_env(&pager_process->env_array);\n+\tpager_process->trace2_child_class = \"pager\";\n }\n \n void setup_pager(void)\n-- \ngitgitgadget\n\n"},{"id":"368610","messageId":"68269ee0609b162b9d585d8b1792fa5145224b69.1549473350.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 10/15] trace2:data: pack-objects: add trace2 regions","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-06T17:16:01Z","receivedAt":"2019-02-06T17:16:23Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWhen studying the performance of 'git push' we would like to know\nhow much time is spent at various parts of the command. One area\nthat could cause performance trouble is 'git pack-objects'.\n\nAdd trace2 regions around the three main actions taken in this\ncommand:\n\n1. Enumerate objects.\n2. Prepare pack.\n3. Write pack-file.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/pack-objects.c | 16 +++++++++++++++-\n 1 file changed, 15 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 0a70d04604..8a64c2868e 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -33,6 +33,7 @@\n #include \"object-store.h\"\n #include \"dir.h\"\n #include \"midx.h\"\n+#include \"trace2.h\"\n \n #define IN_PACK(obj) oe_in_pack(&to_pack, obj)\n #define SIZE(obj) oe_size(&to_pack, obj)\n@@ -3472,6 +3473,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n+\ttrace2_region_enter(\"pack-objects\", \"enumerate-objects\",\n+\t\t\t    the_repository);\n \tprepare_packing_data(the_repository, &to_pack);\n \n \tif (progress)\n@@ -3486,12 +3489,23 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)\n \tif (include_tag && nr_result)\n \t\tfor_each_ref(add_ref_tag, NULL);\n \tstop_progress(&progress_state);\n+\ttrace2_region_leave(\"pack-objects\", \"enumerate-objects\",\n+\t\t\t    the_repository);\n \n \tif (non_empty && !nr_result)\n \t\treturn 0;\n-\tif (nr_result)\n+\tif (nr_result) {\n+\t\ttrace2_region_enter(\"pack-objects\", \"prepare-pack\",\n+\t\t\t\t    the_repository);\n \t\tprepare_pack(window, depth);\n+\t\ttrace2_region_leave(\"pack-objects\", \"prepare-pack\",\n+\t\t\t\t    the_repository);\n+\t}\n+\n+\ttrace2_region_enter(\"pack-objects\", \"write-pack-file\", the_repository);\n \twrite_pack_file();\n+\ttrace2_region_leave(\"pack-objects\", \"write-pack-file\", the_repository);\n+\n \tif (progress)\n \t\tfprintf_ln(stderr,\n \t\t\t   _(\"Total %\"PRIu32\" (delta %\"PRIu32\"),\"\n-- \ngitgitgadget\n\n"},{"id":"368611","messageId":"12de7e42decb26fa665b742532b89dbe3ab925bd.1549473350.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v6 03/15] trace2: collect Windows-specific process information","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-06T17:15:55Z","receivedAt":"2019-02-06T17:16:24Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd platform-specific interface to log information about the current\nprocess.\n\nOn Windows, this interface is used to indicate whether the git process\nis running under a debugger and list names of the process ancestors.\n\nInformation for other platforms is left for a future effort.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n common-main.c                            |   1 +\n compat/win32/trace2_win32_process_info.c | 101 +++++++++++++++++++++++\n config.mak.uname                         |   2 +\n trace2.h                                 |  14 ++++\n 4 files changed, 118 insertions(+)\n create mode 100644 compat/win32/trace2_win32_process_info.c\n\ndiff --git a/common-main.c b/common-main.c\nindex 6dbdc4adf2..d484aec209 100644\n--- a/common-main.c\n+++ b/common-main.c\n@@ -37,6 +37,7 @@ int main(int argc, const char **argv)\n \n \ttrace2_initialize();\n \ttrace2_cmd_start(argv);\n+\ttrace2_collect_process_info();\n \n \tgit_resolve_executable_dir(argv[0]);\n \ndiff --git a/compat/win32/trace2_win32_process_info.c b/compat/win32/trace2_win32_process_info.c\nnew file mode 100644\nindex 0000000000..253199f812\n--- /dev/null\n+++ b/compat/win32/trace2_win32_process_info.c\n@@ -0,0 +1,101 @@\n+#include \"../../cache.h\"\n+#include \"../../json-writer.h\"\n+#include <Psapi.h>\n+#include <tlHelp32.h>\n+\n+/*\n+ * Find the process data for the given PID in the given snapshot\n+ * and update the PROCESSENTRY32 data.\n+ */\n+static int find_pid(DWORD pid, HANDLE hSnapshot, PROCESSENTRY32 *pe32)\n+{\n+\tpe32->dwSize = sizeof(PROCESSENTRY32);\n+\n+\tif (Process32First(hSnapshot, pe32)) {\n+\t\tdo {\n+\t\t\tif (pe32->th32ProcessID == pid)\n+\t\t\t\treturn 1;\n+\t\t} while (Process32Next(hSnapshot, pe32));\n+\t}\n+\treturn 0;\n+}\n+\n+/*\n+ * Accumulate JSON array:\n+ *     [\n+ *         exe-name-parent,\n+ *         exe-name-grand-parent,\n+ *         ...\n+ *     ]\n+ *\n+ * Note: we only report the filename of the process executable; the\n+ *       only way to get its full pathname is to use OpenProcess()\n+ *       and GetModuleFileNameEx() or QueryfullProcessImageName()\n+ *       and that seems rather expensive (on top of the cost of\n+ *       getting the snapshot).\n+ */\n+static void get_processes(struct json_writer *jw, HANDLE hSnapshot)\n+{\n+\tPROCESSENTRY32 pe32;\n+\tDWORD pid;\n+\n+\tpid = GetCurrentProcessId();\n+\n+\t/* We only want parent processes, so skip self. */\n+\tif (!find_pid(pid, hSnapshot, &pe32))\n+\t\treturn;\n+\tpid = pe32.th32ParentProcessID;\n+\n+\twhile (find_pid(pid, hSnapshot, &pe32)) {\n+\t\tjw_array_string(jw, pe32.szExeFile);\n+\n+\t\tpid = pe32.th32ParentProcessID;\n+\t}\n+}\n+\n+/*\n+ * Emit JSON data for the current and parent processes.  Individual\n+ * trace2 targets can decide how to actually print it.\n+ */\n+static void get_ancestry(void)\n+{\n+\tHANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);\n+\n+\tif (hSnapshot != INVALID_HANDLE_VALUE) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\t\tjw_array_begin(&jw, 0);\n+\t\tget_processes(&jw, hSnapshot);\n+\t\tjw_end(&jw);\n+\n+\t\ttrace2_data_json(\"process\", the_repository, \"windows/ancestry\",\n+\t\t\t\t &jw);\n+\n+\t\tjw_release(&jw);\n+\t\tCloseHandle(hSnapshot);\n+\t}\n+}\n+\n+/*\n+ * Is a debugger attached to the current process?\n+ *\n+ * This will catch debug runs (where the debugger started the process).\n+ * This is the normal case.  Since this code is called during our startup,\n+ * it will not report instances where a debugger is attached dynamically\n+ * to a running git process, but that is relatively rare.\n+ */\n+static void get_is_being_debugged(void)\n+{\n+\tif (IsDebuggerPresent())\n+\t\ttrace2_data_intmax(\"process\", the_repository,\n+\t\t\t\t   \"windows/debugger_present\", 1);\n+}\n+\n+void trace2_collect_process_info(void)\n+{\n+\tif (!trace2_is_enabled())\n+\t\treturn;\n+\n+\tget_is_being_debugged();\n+\tget_ancestry();\n+}\ndiff --git a/config.mak.uname b/config.mak.uname\nindex 7b36a1dfe7..a3c03ce2ae 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -391,6 +391,7 @@ ifeq ($(uname_S),Windows)\n \tBASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE\n \tCOMPAT_OBJS = compat/msvc.o compat/winansi.o \\\n \t\tcompat/win32/pthread.o compat/win32/syslog.o \\\n+\t\tcompat/win32/trace2_win32_process_info.o \\\n \t\tcompat/win32/dirent.o\n \tCOMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\\\".exe\\\"\n \tBASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE\n@@ -545,6 +546,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))\n \tCOMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32\n \tCOMPAT_CFLAGS += -DSTRIP_EXTENSION=\\\".exe\\\"\n \tCOMPAT_OBJS += compat/mingw.o compat/winansi.o \\\n+\t\tcompat/win32/trace2_win32_process_info.o \\\n \t\tcompat/win32/path-utils.o \\\n \t\tcompat/win32/pthread.o compat/win32/syslog.o \\\n \t\tcompat/win32/dirent.o\ndiff --git a/trace2.h b/trace2.h\nindex fce9891f53..ae5020d0e6 100644\n--- a/trace2.h\n+++ b/trace2.h\n@@ -368,4 +368,18 @@ void trace2_printf(const char *fmt, ...);\n /* clang-format on */\n #endif\n \n+/*\n+ * Optional platform-specific code to dump information about the\n+ * current and any parent process(es).  This is intended to allow\n+ * post-processors to know who spawned this git instance and anything\n+ * else the platform may be able to tell us about the current process.\n+ */\n+#if defined(GIT_WINDOWS_NATIVE)\n+void trace2_collect_process_info(void);\n+#else\n+#define trace2_collect_process_info() \\\n+\tdo {                          \\\n+\t} while (0)\n+#endif\n+\n #endif /* TRACE2_H */\n-- \ngitgitgadget\n\n"},{"id":"369231","messageId":"87h8d7kex4.fsf@evledraar.gmail.com","threadId":"50293","inReplyTo":"00b25da38bd107b543a34833e5e363385c9eaa37.1549043937.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 14/15] trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-02-13T13:15:35Z","receivedAt":"2019-02-13T13:15:43Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Feb 01 2019, Jeff Hostetler via GitGitGadget wrote:\n\n> +test_expect_success 'event stream, error event' '\n> +\ttest_when_finished \"rm trace.event actual expect\" &&\n> +\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 003error \"hello world\" \"this is a test\" &&\n> +\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n\nThese & similar tests on some systems ...\n\n> [...]\n> diff --git a/t/t0212/parse_events.perl b/t/t0212/parse_events.perl\n> new file mode 100644\n> index 0000000000..a2776ba216\n> --- /dev/null\n> +++ b/t/t0212/parse_events.perl\n> @@ -0,0 +1,251 @@\n> [...]\n> +use strict;\n> +use warnings;\n> +use JSON::PP;\n> +use Data::Dumper;\n> +use Getopt::Long;\n\n...because JSON::PP is not a core perl module. See\ne.g. t/t9501-gitweb-standalone-http-status.sh for how we deal with this,\ni.e.:\n\n    perl -MJSON::PP -e 0 >/dev/null 2>&1 && test_set_prereq JSON_PP\n\nThen later e.g.:\n\n    test_expect_success JSON_PP 'event stream, error event' '[...]\n    \n\n\n"},{"id":"369308","messageId":"87a7iyk0r8.fsf@evledraar.gmail.com","threadId":"50293","inReplyTo":"pull.108.v6.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v6 00/15] Trace2 tracing facility","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-02-14T12:33:47Z","receivedAt":"2019-02-14T12:33:53Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Feb 06 2019, Jeff Hostetler via GitGitGadget wrote:\n\n> V6 addresses: [] The remaining hdr-check warning in trace2/tr2_tls.h\n>\n> There are no other outstanding comments that I'm aware of.\n\nNot a comment on this, just a follow-up question. I started looking into\nwhether this could be driven by config instead of getenv(). A lot easier\nto set up in some cases than injecting env variables, especialy if the\nlog target supported a strftime() string, is any of that something\nyou've looked into already (so I don't do dupe work...).\n\nThere's the chicken & egg problem with wanting to do traces way before\nwe get to reading config, so I expect that such a facility would need to\nwork by always trace record at the beginning until we get far enough to\nwrite the config, and then either stop and throw away the buffer, or\nwrite out the existing trace to the configured target, and continue.\n"},{"id":"369389","messageId":"87y36hnit9.fsf@evledraar.gmail.com","threadId":"50293","inReplyTo":"6bad326bbd27426845734d08428e6987a75d9ad9.1549473350.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v6 02/15] trace2: create new combined trace facility","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-02-15T16:00:02Z","receivedAt":"2019-02-15T16:00:09Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Feb 06 2019, Jeff Hostetler via GitGitGadget wrote:\n\n> +\tsa.sun_family = AF_UNIX;\n> +\tstrlcpy(sa.sun_path, path, sizeof(sa.sun_path));\n> +\tif ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1 ||\n> +\t    connect(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {\n> +\t\tif (tr2_dst_want_warning())\n> +\t\t\twarning(\"trace2: could not connect to socket '%s' for '%s' tracing: %s\",\n> +\t\t\t\tpath, dst->env_var_name, strerror(errno));\n> +\n> +\t\ttr2_dst_trace_disable(dst);\n> +\t\treturn 0;\n> +\t}\n\nJust curious, what do you use af_unix:* for? Is this provided by some\nWindows emulation ?\n\nOn Linux with systemd, this does not work for logging to systemd's\n/dev/log. You're opening the socket with SOCK_STREAM, but it needs\nSOCK_DGRAM. I.e.:\n\n    # fail\n    echo '{\"yay\": \"testing\"}' | nc -U /dev/log\n    # works\n    echo '{\"yay\": \"testing\"}' | nc -uU /dev/log\n\nSo that gives something that'll \"work\" for \"jounalctl -f\", but on my\nsystem ends up being munged to the invalid JSON:\n\n    '{\"yay\"[PID]: \"testing\"}'.\n\nI found, and this may be specific to the systemd/rsyslog setup on RedHat\nI'm working with, that what /dev/log is expecting is a payload in the\nsyslog format: https://tools.ietf.org/html/rfc5424\n\nSo related to my question in\nhttps://public-inbox.org/git/87a7iyk0r8.fsf@evledraar.gmail.com/ I\nwonder what we should do about this. It seems the bare minimal thing\nthat's needed it some way to open a socket with SOCK_DGRAM, maybe we\nshould just do that unconditionally if the connect() fails with\nEPROTOTYPE?\n\nBut it seems that for that to be useful for systemd we'd need to support\nthe syslog.h interface (so different from af_unix:*), or learn how to\nwrite a standard syslog packet to an af_unix:* SOCK_DGRAM socket.\n\nI guess we can start with just supporting that by hardcoding the ident &\nLOG_* values to something sensible, e.g.:\n\n  openlog(\"git\", LOG_PID | (tr2_dst_want_warning() ? LOG_CONS : 0), LOG_USER);\n  syslog(LOG_USER|LOG_DEBUG, message_or_json);\n  closelog();\n\nI can write the patches for this. As in the earlier message I'm just\nlooking for some context, whether this is stuff you have WIP already,\nand whether you have any other/related ideas. Thanks.\n"},{"id":"369395","messageId":"0aff8302-371e-cb5e-fe5e-f08e45456559@jeffhostetler.com","threadId":"50293","inReplyTo":"87a7iyk0r8.fsf@evledraar.gmail.com","subject":"Re: [PATCH v6 00/15] Trace2 tracing facility","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2019-02-15T17:25:26Z","receivedAt":"2019-02-15T17:25:30Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 2/14/2019 7:33 AM, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Wed, Feb 06 2019, Jeff Hostetler via GitGitGadget wrote:\n> \n>> V6 addresses: [] The remaining hdr-check warning in trace2/tr2_tls.h\n>>\n>> There are no other outstanding comments that I'm aware of.\n> \n> Not a comment on this, just a follow-up question. I started looking into\n> whether this could be driven by config instead of getenv(). A lot easier\n> to set up in some cases than injecting env variables, especialy if the\n> log target supported a strftime() string, is any of that something\n> you've looked into already (so I don't do dupe work...).\n> \n> There's the chicken & egg problem with wanting to do traces way before\n> we get to reading config, so I expect that such a facility would need to\n> work by always trace record at the beginning until we get far enough to\n> write the config, and then either stop and throw away the buffer, or\n> write out the existing trace to the configured target, and continue.\n> \n\nYes, I beat my head against the config settings for quite a while\nbefore settling on using an env var.\n\nI wanted to get the:\n() full process elapsed time,\n() the full original argv,\n() all of the command dispatch, run-dashed, and alias expansion,\n() and for my atexit() to run last.\nSo I hooked into main() before the config is loaded.\n\nIn most commands, the config is processed about the same time as\nparse_options() is called.  And we have to insert code in\ngit_default_config() to load my settings.  This happens after all\nof the .git dir discovery, \"-c\" and \"-C\" processing, alias expansion,\ncommand dispatch and etc.  And the argv received in the cmd_*()\nfunction has been modified.  So we lose some information.  (I tried\nthis for a while and didn't like the results.)\n\nI also tried using read_early_config() various places, but there\nwere problems here too.  Too early and the \"-c\" settings weren't\nparsed yet.  And there was an issue about when .git dir was discovered,\nso local config settings weren't ready yet.\n\nI also recall having a problem when doing an early iteration with\nside effects (or rather the early iteration caused something to be\nset that caused the real iteration (in cmd_*()) to short-cut), but\nI don't remember the details.\n\nSo we have a custom installer that also sets the environment variable\nafter git is installed and haven't had any problems.\n\n\nI hesitate to say we should always start allocating a bunch of data\nand spinning up the TLS data and etc. before we know if tracing is\nwanted.  Just seems expensive for most users.\n\n\nI could see having a \"~/.git_tr2_config\" or something similar in\nsome place like \"/etc\" that only contained the Trace2 settings.\nIt would be safe to read very early inside main() and we would not\nconsider it to be part of the real config.  For example, \"git config\"\nwould not know about it.  Then you could enforce a system-wide\nsetting without any of the env var issues.\n\n\nWRT the strftime() question, we could either add syntax to the\nenv var value (or the tr2 config setting) to have some tokens\nfor that.  I just stuck with absolute pathnames since I started\nby copying what was done for GIT_TRACE.\n\nJeff\n\n\n"},{"id":"369399","messageId":"e7be277a-169d-f759-dc14-4ea0e7768c89@jeffhostetler.com","threadId":"50293","inReplyTo":"87y36hnit9.fsf@evledraar.gmail.com","subject":"Re: [PATCH v6 02/15] trace2: create new combined trace facility","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2019-02-15T17:57:43Z","receivedAt":"2019-02-15T17:57:47Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 2/15/2019 11:00 AM, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Wed, Feb 06 2019, Jeff Hostetler via GitGitGadget wrote:\n> \n>> +\tsa.sun_family = AF_UNIX;\n>> +\tstrlcpy(sa.sun_path, path, sizeof(sa.sun_path));\n>> +\tif ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1 ||\n>> +\t    connect(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {\n>> +\t\tif (tr2_dst_want_warning())\n>> +\t\t\twarning(\"trace2: could not connect to socket '%s' for '%s' tracing: %s\",\n>> +\t\t\t\tpath, dst->env_var_name, strerror(errno));\n>> +\n>> +\t\ttr2_dst_trace_disable(dst);\n>> +\t\treturn 0;\n>> +\t}\n> \n> Just curious, what do you use af_unix:* for? Is this provided by some\n> Windows emulation ?\n\nOn the Mac, I want to send data to a server that only handles\nstream data.  I thought about making the prefix something like\n     \"af_unix:stream:<path>\"\nand then we could have the correpsonding dgram version too, but\nI didn't add that.\n\nI think I'll add this in the next re-roll.\n\n\n\n> On Linux with systemd, this does not work for logging to systemd's\n> /dev/log. You're opening the socket with SOCK_STREAM, but it needs\n> SOCK_DGRAM. I.e.:\n> \n>      # fail\n>      echo '{\"yay\": \"testing\"}' | nc -U /dev/log\n>      # works\n>      echo '{\"yay\": \"testing\"}' | nc -uU /dev/log\n> \n> So that gives something that'll \"work\" for \"jounalctl -f\", but on my\n> system ends up being munged to the invalid JSON:\n> \n>      '{\"yay\"[PID]: \"testing\"}'.\n> \n> I found, and this may be specific to the systemd/rsyslog setup on RedHat\n> I'm working with, that what /dev/log is expecting is a payload in the\n> syslog format: https://tools.ietf.org/html/rfc5424\n> \n> So related to my question in\n> https://public-inbox.org/git/87a7iyk0r8.fsf@evledraar.gmail.com/ I\n> wonder what we should do about this. It seems the bare minimal thing\n> that's needed it some way to open a socket with SOCK_DGRAM, maybe we\n> should just do that unconditionally if the connect() fails with\n> EPROTOTYPE?\n> \n> But it seems that for that to be useful for systemd we'd need to support\n> the syslog.h interface (so different from af_unix:*), or learn how to\n> write a standard syslog packet to an af_unix:* SOCK_DGRAM socket.\n> \n> I guess we can start with just supporting that by hardcoding the ident &\n> LOG_* values to something sensible, e.g.:\n> \n>    openlog(\"git\", LOG_PID | (tr2_dst_want_warning() ? LOG_CONS : 0), LOG_USER);\n>    syslog(LOG_USER|LOG_DEBUG, message_or_json);\n>    closelog();\n> \n> I can write the patches for this. As in the earlier message I'm just\n> looking for some context, whether this is stuff you have WIP already,\n> and whether you have any other/related ideas. Thanks.\n> \n\nWhen I created the \"af_unix:\" prefix for Unix domain sockets, I thought\nwe could later do other types of sockets if we wanted to.  The common\ntheme being they just need an \"fd\".\n\nFor syslog(), rather than changing the af_unix socket code, you could\ncreate a new Trace2 target.  For example, copy one of the existing\ntargets:\n    trace2/tr2_tgt_normal.c that defines GIT_TR2\n    trace2/tr2_tgt_event.c that defines GIT_TR2_EVENT\n    trace2/tr2_tgt_perf.c that defines GIT_TR2_PERF\nand create:\n    trace2/tr2_tgt_syslog.c that defines GIT_TR2_SYSLOG.\n\nThen in tr2_tgt_syslog.c you have complete control of the formatting\nof the data you send to syslog().  You can omit fields as necessary.\nOr you can printf format it rather than JSON encode it.\n\nThen in trace2/tr2_dst.c add a parser for \"syslog:<whatever>\".\nAll it has to do is set a bit so that tr2_dst_trace_want()\nreturns true if you were able to openlog().\n\nHope that helps,\nJeff\n\n\n"},{"id":"369983","messageId":"pull.108.v7.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v6.git.gitgitgadget@gmail.com","subject":"[PATCH v7 00/15] Trace2 tracing facility","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-22T22:24:59Z","receivedAt":"2019-02-22T22:25:04Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"V7 addresses: [] squashes in the fixup for windows process ancestry\ncalculation. [] adds optional socket_type parameter to the unix domain\nsocket syntax. [] allow unix domain socket syntax to try both stream and\ndgram if socket_type not specified. [] add Perl prereq checks for JSON:PP in\ntrace2 tests. [] minor clang-format cleanup.\n\nThanks Jeff\n\nCc: gitster@pobox.comCc: peff@peff.netCc: avarab@gmail.com\n\nDerrick Stolee (1):\n  trace2:data: pack-objects: add trace2 regions\n\nJeff Hostetler (14):\n  trace2: Documentation/technical/api-trace2.txt\n  trace2: create new combined trace facility\n  trace2: collect Windows-specific process information\n  trace2:data: add trace2 regions to wt-status\n  trace2:data: add editor/pager child classification\n  trace2:data: add trace2 sub-process classification\n  trace2:data: add trace2 transport child classification\n  trace2:data: add trace2 hook classification\n  trace2:data: add trace2 instrumentation to index read/write\n  trace2:data: add subverb to checkout command\n  trace2:data: add subverb to reset command\n  trace2:data: add subverb for rebase\n  trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh\n  trace2: add for_each macros to clang-format\n\n .clang-format                            |    2 +-\n Documentation/technical/api-trace2.txt   | 1350 ++++++++++++++++++++++\n Makefile                                 |   15 +-\n builtin/am.c                             |    1 +\n builtin/checkout.c                       |    7 +\n builtin/pack-objects.c                   |   16 +-\n builtin/rebase.c                         |   17 +\n builtin/receive-pack.c                   |    4 +\n builtin/reset.c                          |    6 +\n builtin/submodule--helper.c              |    9 +-\n builtin/worktree.c                       |    1 +\n cache.h                                  |    1 +\n common-main.c                            |   13 +-\n compat/mingw.c                           |   11 +-\n compat/mingw.h                           |    3 +-\n compat/win32/trace2_win32_process_info.c |  147 +++\n config.c                                 |    2 +\n config.mak.uname                         |    2 +\n connect.c                                |    3 +\n editor.c                                 |    1 +\n exec-cmd.c                               |    2 +\n git-compat-util.h                        |    7 +\n git.c                                    |   65 ++\n pager.c                                  |    1 +\n read-cache.c                             |   51 +-\n remote-curl.c                            |    7 +\n repository.c                             |    2 +\n repository.h                             |    3 +\n run-command.c                            |   59 +-\n run-command.h                            |   13 +-\n sequencer.c                              |    2 +\n sh-i18n--envsubst.c                      |    3 +\n sub-process.c                            |    1 +\n submodule.c                              |   11 +-\n t/helper/test-parse-options.c            |    3 +\n t/helper/test-tool.c                     |    4 +\n t/helper/test-tool.h                     |    1 +\n t/helper/test-trace2.c                   |  273 +++++\n t/t0001-init.sh                          |    1 +\n t/t0210-trace2-normal.sh                 |  135 +++\n t/t0210/scrub_normal.perl                |   48 +\n t/t0211-trace2-perf.sh                   |  153 +++\n t/t0211/scrub_perf.perl                  |   76 ++\n t/t0212-trace2-event.sh                  |  236 ++++\n t/t0212/parse_events.perl                |  251 ++++\n trace2.c                                 |  761 ++++++++++++\n trace2.h                                 |  385 ++++++\n trace2/tr2_cfg.c                         |   90 ++\n trace2/tr2_cfg.h                         |   19 +\n trace2/tr2_cmd_name.c                    |   30 +\n trace2/tr2_cmd_name.h                    |   24 +\n trace2/tr2_dst.c                         |  252 ++++\n trace2/tr2_dst.h                         |   36 +\n trace2/tr2_sid.c                         |   67 ++\n trace2/tr2_sid.h                         |   18 +\n trace2/tr2_tbuf.c                        |   32 +\n trace2/tr2_tbuf.h                        |   23 +\n trace2/tr2_tgt.h                         |  133 +++\n trace2/tr2_tgt_event.c                   |  588 ++++++++++\n trace2/tr2_tgt_normal.c                  |  323 ++++++\n trace2/tr2_tgt_perf.c                    |  534 +++++++++\n trace2/tr2_tls.c                         |  164 +++\n trace2/tr2_tls.h                         |   97 ++\n transport-helper.c                       |    2 +\n transport.c                              |    1 +\n usage.c                                  |   31 +\n wt-status.c                              |   24 +-\n 67 files changed, 6630 insertions(+), 23 deletions(-)\n create mode 100644 Documentation/technical/api-trace2.txt\n create mode 100644 compat/win32/trace2_win32_process_info.c\n create mode 100644 t/helper/test-trace2.c\n create mode 100755 t/t0210-trace2-normal.sh\n create mode 100644 t/t0210/scrub_normal.perl\n create mode 100755 t/t0211-trace2-perf.sh\n create mode 100644 t/t0211/scrub_perf.perl\n create mode 100755 t/t0212-trace2-event.sh\n create mode 100644 t/t0212/parse_events.perl\n create mode 100644 trace2.c\n create mode 100644 trace2.h\n create mode 100644 trace2/tr2_cfg.c\n create mode 100644 trace2/tr2_cfg.h\n create mode 100644 trace2/tr2_cmd_name.c\n create mode 100644 trace2/tr2_cmd_name.h\n create mode 100644 trace2/tr2_dst.c\n create mode 100644 trace2/tr2_dst.h\n create mode 100644 trace2/tr2_sid.c\n create mode 100644 trace2/tr2_sid.h\n create mode 100644 trace2/tr2_tbuf.c\n create mode 100644 trace2/tr2_tbuf.h\n create mode 100644 trace2/tr2_tgt.h\n create mode 100644 trace2/tr2_tgt_event.c\n create mode 100644 trace2/tr2_tgt_normal.c\n create mode 100644 trace2/tr2_tgt_perf.c\n create mode 100644 trace2/tr2_tls.c\n create mode 100644 trace2/tr2_tls.h\n\n\nbase-commit: b5101f929789889c2e536d915698f58d5c5c6b7a\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-108%2Fjeffhostetler%2Fcore-trace2-2019-v0-v7\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-108/jeffhostetler/core-trace2-2019-v0-v7\nPull-Request: https://github.com/gitgitgadget/git/pull/108\n\nRange-diff vs v6:\n\n  1:  4c006f4995 !  1:  8ac6acf88d trace2: Documentation/technical/api-trace2.txt\n     @@ -122,10 +122,13 @@\n      +\n      +\tEnables the target, opens and writes to the file in append mode.\n      +\n     -+`af_unix:<absolute-pathname>`::\n     ++`af_unix:[<socket_type>:]<absolute-pathname>`::\n      +\n      +\tEnables the target, opens and writes to a Unix Domain Socket\n      +\t(on platforms that support them).\n     +++\n     ++Socket type can be either `stream` or `dgram`.  If the socket type is\n     ++omitted, Git will try both.\n      +\n      +== Trace2 API\n      +\n  2:  6bad326bbd !  2:  90e4e6af93 trace2: create new combined trace facility\n     @@ -947,8 +947,7 @@\n      +\n      +\tfor_each_wanted_builtin (j, tgt_j)\n      +\t\tif (tgt_j->pfn_command_name_fl)\n     -+\t\t\ttgt_j->pfn_command_name_fl(file, line, name,\n     -+\t\t\t\t\t\t   hierarchy);\n     ++\t\t\ttgt_j->pfn_command_name_fl(file, line, name, hierarchy);\n      +}\n      +\n      +void trace2_cmd_mode_fl(const char *file, int line, const char *mode)\n     @@ -2080,37 +2079,103 @@\n      +\n      +#ifndef NO_UNIX_SOCKETS\n      +#define PREFIX_AF_UNIX \"af_unix:\"\n     -+#define PREFIX_AF_UNIX_LEN (8)\n     ++#define PREFIX_AF_UNIX_STREAM \"af_unix:stream:\"\n     ++#define PREFIX_AF_UNIX_DGRAM \"af_unix:dgram:\"\n     ++\n     ++static int tr2_dst_try_uds_connect(const char *path, int sock_type, int *out_fd)\n     ++{\n     ++\tint fd;\n     ++\tstruct sockaddr_un sa;\n     ++\n     ++\tfd = socket(AF_UNIX, sock_type, 0);\n     ++\tif (fd == -1)\n     ++\t\treturn errno;\n     ++\n     ++\tsa.sun_family = AF_UNIX;\n     ++\tstrlcpy(sa.sun_path, path, sizeof(sa.sun_path));\n     ++\n     ++\tif (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {\n     ++\t\tint e = errno;\n     ++\t\tclose(fd);\n     ++\t\treturn e;\n     ++\t}\n     ++\n     ++\t*out_fd = fd;\n     ++\treturn 0;\n     ++}\n     ++\n     ++#define TR2_DST_UDS_TRY_STREAM (1 << 0)\n     ++#define TR2_DST_UDS_TRY_DGRAM  (1 << 1)\n      +\n      +static int tr2_dst_try_unix_domain_socket(struct tr2_dst *dst,\n      +\t\t\t\t\t  const char *tgt_value)\n      +{\n     ++\tunsigned int uds_try = 0;\n      +\tint fd;\n     -+\tstruct sockaddr_un sa;\n     -+\tconst char *path = tgt_value + PREFIX_AF_UNIX_LEN;\n     -+\tint path_len = strlen(path);\n     ++\tint e;\n     ++\tconst char *path = NULL;\n     ++\n     ++\t/*\n     ++\t * Allow \"af_unix:[<type>:]<absolute_path>\"\n     ++\t *\n     ++\t * Trace2 always writes complete individual messages (without\n     ++\t * chunking), so we can talk to either DGRAM or STREAM type sockets.\n     ++\t *\n     ++\t * Allow the user to explicitly request the socket type.\n     ++\t *\n     ++\t * If they omit the socket type, try one and then the other.\n     ++\t */\n      +\n     -+\tif (!is_absolute_path(path) || path_len >= sizeof(sa.sun_path)) {\n     ++\tif (skip_prefix(tgt_value, PREFIX_AF_UNIX_STREAM, &path))\n     ++\t\tuds_try |= TR2_DST_UDS_TRY_STREAM;\n     ++\n     ++\telse if (skip_prefix(tgt_value, PREFIX_AF_UNIX_DGRAM, &path))\n     ++\t\tuds_try |= TR2_DST_UDS_TRY_DGRAM;\n     ++\n     ++\telse if (skip_prefix(tgt_value, PREFIX_AF_UNIX, &path))\n     ++\t\tuds_try |= TR2_DST_UDS_TRY_STREAM | TR2_DST_UDS_TRY_DGRAM;\n     ++\n     ++\tif (!path || !*path) {\n      +\t\tif (tr2_dst_want_warning())\n     -+\t\t\twarning(\"trace2: invalid AF_UNIX path '%s' for '%s' tracing\",\n     -+\t\t\t\tpath, dst->env_var_name);\n     ++\t\t\twarning(\"trace2: invalid AF_UNIX value '%s' for '%s' tracing\",\n     ++\t\t\t\ttgt_value, dst->env_var_name);\n      +\n      +\t\ttr2_dst_trace_disable(dst);\n      +\t\treturn 0;\n      +\t}\n      +\n     -+\tsa.sun_family = AF_UNIX;\n     -+\tstrlcpy(sa.sun_path, path, sizeof(sa.sun_path));\n     -+\tif ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1 ||\n     -+\t    connect(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {\n     ++\tif (!is_absolute_path(path) ||\n     ++\t    strlen(path) >= sizeof(((struct sockaddr_un *)0)->sun_path)) {\n      +\t\tif (tr2_dst_want_warning())\n     -+\t\t\twarning(\"trace2: could not connect to socket '%s' for '%s' tracing: %s\",\n     -+\t\t\t\tpath, dst->env_var_name, strerror(errno));\n     ++\t\t\twarning(\"trace2: invalid AF_UNIX path '%s' for '%s' tracing\",\n     ++\t\t\t\tpath, dst->env_var_name);\n      +\n      +\t\ttr2_dst_trace_disable(dst);\n      +\t\treturn 0;\n      +\t}\n      +\n     ++\tif (uds_try & TR2_DST_UDS_TRY_STREAM) {\n     ++\t\te = tr2_dst_try_uds_connect(path, SOCK_STREAM, &fd);\n     ++\t\tif (!e)\n     ++\t\t\tgoto connected;\n     ++\t\tif (e != EPROTOTYPE)\n     ++\t\t\tgoto error;\n     ++\t}\n     ++\tif (uds_try & TR2_DST_UDS_TRY_DGRAM) {\n     ++\t\te = tr2_dst_try_uds_connect(path, SOCK_DGRAM, &fd);\n     ++\t\tif (!e)\n     ++\t\t\tgoto connected;\n     ++\t}\n     ++\n     ++error:\n     ++\tif (tr2_dst_want_warning())\n     ++\t\twarning(\"trace2: could not connect to socket '%s' for '%s' tracing: %s\",\n     ++\t\t\tpath, dst->env_var_name, strerror(e));\n     ++\n     ++\ttr2_dst_trace_disable(dst);\n     ++\treturn 0;\n     ++\n     ++connected:\n      +\tdst->fd = fd;\n      +\tdst->need_close = 1;\n      +\tdst->initialized = 1;\n     @@ -2124,19 +2189,8 @@\n      +{\n      +\tstruct strbuf buf = STRBUF_INIT;\n      +\n     -+\tstrbuf_addf(&buf, \"trace2: unknown trace value for '%s': '%s'\",\n     ++\tstrbuf_addf(&buf, \"trace2: unknown value for '%s': '%s'\",\n      +\t\t    dst->env_var_name, tgt_value);\n     -+\tstrbuf_addstr(\n     -+\t\t&buf,\n     -+\t\t\"\\n         If you want to trace into a file, then please set it\"\n     -+\t\t\"\\n         to an absolute pathname.\");\n     -+#ifndef NO_UNIX_SOCKETS\n     -+\tstrbuf_addstr(\n     -+\t\t&buf,\n     -+\t\t\"\\n         If you want to trace to a unix domain socket, prefix\"\n     -+\t\t\"\\n         the absolute pathname with \\\"af_unix:\\\".\");\n     -+#endif\n     -+\n      +\twarning(\"%s\", buf.buf);\n      +\n      +\tstrbuf_release(&buf);\n     @@ -2174,7 +2228,7 @@\n      +\t\treturn tr2_dst_try_path(dst, tgt_value);\n      +\n      +#ifndef NO_UNIX_SOCKETS\n     -+\tif (!strncmp(tgt_value, PREFIX_AF_UNIX, PREFIX_AF_UNIX_LEN))\n     ++\tif (starts_with(tgt_value, PREFIX_AF_UNIX))\n      +\t\treturn tr2_dst_try_unix_domain_socket(dst, tgt_value);\n      +#endif\n      +\n     @@ -2817,8 +2871,7 @@\n      +\tjw_release(&jw);\n      +}\n      +\n     -+static void fn_command_name_fl(const char *file, int line,\n     -+\t\t\t       const char *name,\n     ++static void fn_command_name_fl(const char *file, int line, const char *name,\n      +\t\t\t       const char *hierarchy)\n      +{\n      +\tconst char *event_name = \"cmd_name\";\n     @@ -3327,8 +3380,7 @@\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n     -+static void fn_command_name_fl(const char *file, int line,\n     -+\t\t\t       const char *name,\n     ++static void fn_command_name_fl(const char *file, int line, const char *name,\n      +\t\t\t       const char *hierarchy)\n      +{\n      +\tstruct strbuf buf_payload = STRBUF_INIT;\n     @@ -3747,8 +3799,7 @@\n      +\tstrbuf_release(&buf_payload);\n      +}\n      +\n     -+static void fn_command_name_fl(const char *file, int line,\n     -+\t\t\t       const char *name,\n     ++static void fn_command_name_fl(const char *file, int line, const char *name,\n      +\t\t\t       const char *hierarchy)\n      +{\n      +\tconst char *event_name = \"cmd_name\";\n  3:  12de7e42de !  3:  50f689dc95 trace2: collect Windows-specific process information\n     @@ -35,6 +35,12 @@\n      +#include <tlHelp32.h>\n      +\n      +/*\n     ++ * An arbitrarily chosen value to limit the size of the ancestor\n     ++ * array built in git_processes().\n     ++ */\n     ++#define NR_PIDS_LIMIT 10\n     ++\n     ++/*\n      + * Find the process data for the given PID in the given snapshot\n      + * and update the PROCESSENTRY32 data.\n      + */\n     @@ -52,7 +58,7 @@\n      +}\n      +\n      +/*\n     -+ * Accumulate JSON array:\n     ++ * Accumulate JSON array of our parent processes:\n      + *     [\n      + *         exe-name-parent,\n      + *         exe-name-grand-parent,\n     @@ -64,21 +70,61 @@\n      + *       and GetModuleFileNameEx() or QueryfullProcessImageName()\n      + *       and that seems rather expensive (on top of the cost of\n      + *       getting the snapshot).\n     ++ *\n     ++ * Note: we compute the set of parent processes by walking the PPID\n     ++ *       link in each visited PROCESSENTRY32 record.  This search\n     ++ *       stops when an ancestor process is not found in the snapshot\n     ++ *       (because it exited before the current or intermediate parent\n     ++ *       process exited).\n     ++ *\n     ++ *       This search may compute an incorrect result if the PPID link\n     ++ *       refers to the PID of an exited parent and that PID has been\n     ++ *       recycled and given to a new unrelated process.\n     ++ *\n     ++ *       Worse, it is possible for a child or descendant of the\n     ++ *       current process to be given the recycled PID and cause a\n     ++ *       PPID-cycle.  This would cause an infinite loop building our\n     ++ *       parent process array.\n     ++ *\n     ++ * Note: for completeness, the \"System Idle\" process has PID=0 and\n     ++ *       PPID=0 and could cause another PPID-cycle.  We don't expect\n     ++ *       Git to be a descendant of the idle process, but because of\n     ++ *       PID recycling, it might be possible to get a PPID link value\n     ++ *       of 0.  This too would cause an infinite loop.\n     ++ *\n     ++ * Therefore, we keep an array of the visited PPIDs to guard against\n     ++ * cycles.\n     ++ *\n     ++ * We use a fixed-size array rather than ALLOC_GROW to keep things\n     ++ * simple and avoid the alloc/realloc overhead.  It is OK if we\n     ++ * truncate the search and return a partial answer.\n      + */\n      +static void get_processes(struct json_writer *jw, HANDLE hSnapshot)\n      +{\n      +\tPROCESSENTRY32 pe32;\n      +\tDWORD pid;\n     ++\tDWORD pid_list[NR_PIDS_LIMIT];\n     ++\tint k, nr_pids = 0;\n      +\n      +\tpid = GetCurrentProcessId();\n     -+\n     -+\t/* We only want parent processes, so skip self. */\n     -+\tif (!find_pid(pid, hSnapshot, &pe32))\n     -+\t\treturn;\n     -+\tpid = pe32.th32ParentProcessID;\n     -+\n      +\twhile (find_pid(pid, hSnapshot, &pe32)) {\n     -+\t\tjw_array_string(jw, pe32.szExeFile);\n     ++\t\t/* Only report parents. Omit self from the JSON output. */\n     ++\t\tif (nr_pids)\n     ++\t\t\tjw_array_string(jw, pe32.szExeFile);\n     ++\n     ++\t\t/* Check for cycle in snapshot. (Yes, it happened.) */\n     ++\t\tfor (k = 0; k < nr_pids; k++)\n     ++\t\t\tif (pid == pid_list[k]) {\n     ++\t\t\t\tjw_array_string(jw, \"(cycle)\");\n     ++\t\t\t\treturn;\n     ++\t\t\t}\n     ++\n     ++\t\tif (nr_pids == NR_PIDS_LIMIT) {\n     ++\t\t\tjw_array_string(jw, \"(truncated)\");\n     ++\t\t\treturn;\n     ++\t\t}\n     ++\n     ++\t\tpid_list[nr_pids++] = pid;\n      +\n      +\t\tpid = pe32.th32ParentProcessID;\n      +\t}\n  4:  5835edbd01 =  4:  40411100c3 trace2:data: add trace2 regions to wt-status\n  5:  99d13ef478 =  5:  18ff59d6fe trace2:data: add editor/pager child classification\n  6:  d7ce85b702 =  6:  ec7d7cd4c7 trace2:data: add trace2 sub-process classification\n  7:  b70c289903 =  7:  485a4f2a68 trace2:data: add trace2 transport child classification\n  8:  2a0da88579 =  8:  18659d61da trace2:data: add trace2 hook classification\n  9:  bca6a7f0d6 =  9:  08be5aadbe trace2:data: add trace2 instrumentation to index read/write\n 10:  68269ee060 = 10:  4e679241fc trace2:data: pack-objects: add trace2 regions\n 11:  0191283fff = 11:  2a5aa3a8dc trace2:data: add subverb to checkout command\n 12:  24d8d8d768 = 12:  ebaa9b1d80 trace2:data: add subverb to reset command\n 13:  90cec071cf = 13:  3e6fee40b0 trace2:data: add subverb for rebase\n 14:  b9f0c6fd66 ! 14:  398edb7738 trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh\n     @@ -767,6 +767,8 @@\n      +test_description='test trace2 facility'\n      +. ./test-lib.sh\n      +\n     ++perl -MJSON::PP -e 0 >/dev/null 2>&1 && test_set_prereq JSON_PP\n     ++\n      +# Add t/helper directory to PATH so that we can use a relative\n      +# path to run nested instances of test-tool.exe (see 004child).\n      +# This helps with HEREDOC comparisons later.\n     @@ -802,7 +804,7 @@\n      +#\n      +# To the above, add multiple 'error <msg>' events\n      +\n     -+test_expect_success 'event stream, error event' '\n     ++test_expect_success JSON_PP 'event stream, error event' '\n      +\ttest_when_finished \"rm trace.event actual expect\" &&\n      +\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 003error \"hello world\" \"this is a test\" &&\n      +\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n     @@ -839,7 +841,7 @@\n      +#    P2: |--- TT trace2 004child\n      +#    P3:      |--- TT trace2 001return 0\n      +\n     -+test_expect_success 'event stream, return code 0' '\n     ++test_expect_success JSON_PP 'event stream, return code 0' '\n      +\ttest_when_finished \"rm trace.event actual expect\" &&\n      +\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 004child test-tool trace2 004child test-tool trace2 001return 0 &&\n      +\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n     @@ -926,7 +928,7 @@\n      +\n      +# Test listing of all \"interesting\" config settings.\n      +\n     -+test_expect_success 'event stream, list config' '\n     ++test_expect_success JSON_PP 'event stream, list config' '\n      +\ttest_when_finished \"rm trace.event actual expect\" &&\n      +\tgit config --local t0212.abc 1 &&\n      +\tgit config --local t0212.def \"hello world\" &&\n     @@ -961,7 +963,7 @@\n      +\ttest_cmp expect actual\n      +'\n      +\n     -+test_expect_success 'basic trace2_data' '\n     ++test_expect_success JSON_PP 'basic trace2_data' '\n      +\ttest_when_finished \"rm trace.event actual expect\" &&\n      +\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 006data test_category k1 v1 test_category k2 v2 &&\n      +\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n 15:  93a25d09a1 = 15:  662201f49e trace2: add for_each macros to clang-format\n\n-- \ngitgitgadget\n"},{"id":"369984","messageId":"50f689dc956ea5ad7100475b6f91c918567647f4.1550874298.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v7.git.gitgitgadget@gmail.com","subject":"[PATCH v7 03/15] trace2: collect Windows-specific process information","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-22T22:25:02Z","receivedAt":"2019-02-22T22:25:07Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd platform-specific interface to log information about the current\nprocess.\n\nOn Windows, this interface is used to indicate whether the git process\nis running under a debugger and list names of the process ancestors.\n\nInformation for other platforms is left for a future effort.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n common-main.c                            |   1 +\n compat/win32/trace2_win32_process_info.c | 147 +++++++++++++++++++++++\n config.mak.uname                         |   2 +\n trace2.h                                 |  14 +++\n 4 files changed, 164 insertions(+)\n create mode 100644 compat/win32/trace2_win32_process_info.c\n\ndiff --git a/common-main.c b/common-main.c\nindex 6dbdc4adf2..d484aec209 100644\n--- a/common-main.c\n+++ b/common-main.c\n@@ -37,6 +37,7 @@ int main(int argc, const char **argv)\n \n \ttrace2_initialize();\n \ttrace2_cmd_start(argv);\n+\ttrace2_collect_process_info();\n \n \tgit_resolve_executable_dir(argv[0]);\n \ndiff --git a/compat/win32/trace2_win32_process_info.c b/compat/win32/trace2_win32_process_info.c\nnew file mode 100644\nindex 0000000000..52bd62034b\n--- /dev/null\n+++ b/compat/win32/trace2_win32_process_info.c\n@@ -0,0 +1,147 @@\n+#include \"../../cache.h\"\n+#include \"../../json-writer.h\"\n+#include <Psapi.h>\n+#include <tlHelp32.h>\n+\n+/*\n+ * An arbitrarily chosen value to limit the size of the ancestor\n+ * array built in git_processes().\n+ */\n+#define NR_PIDS_LIMIT 10\n+\n+/*\n+ * Find the process data for the given PID in the given snapshot\n+ * and update the PROCESSENTRY32 data.\n+ */\n+static int find_pid(DWORD pid, HANDLE hSnapshot, PROCESSENTRY32 *pe32)\n+{\n+\tpe32->dwSize = sizeof(PROCESSENTRY32);\n+\n+\tif (Process32First(hSnapshot, pe32)) {\n+\t\tdo {\n+\t\t\tif (pe32->th32ProcessID == pid)\n+\t\t\t\treturn 1;\n+\t\t} while (Process32Next(hSnapshot, pe32));\n+\t}\n+\treturn 0;\n+}\n+\n+/*\n+ * Accumulate JSON array of our parent processes:\n+ *     [\n+ *         exe-name-parent,\n+ *         exe-name-grand-parent,\n+ *         ...\n+ *     ]\n+ *\n+ * Note: we only report the filename of the process executable; the\n+ *       only way to get its full pathname is to use OpenProcess()\n+ *       and GetModuleFileNameEx() or QueryfullProcessImageName()\n+ *       and that seems rather expensive (on top of the cost of\n+ *       getting the snapshot).\n+ *\n+ * Note: we compute the set of parent processes by walking the PPID\n+ *       link in each visited PROCESSENTRY32 record.  This search\n+ *       stops when an ancestor process is not found in the snapshot\n+ *       (because it exited before the current or intermediate parent\n+ *       process exited).\n+ *\n+ *       This search may compute an incorrect result if the PPID link\n+ *       refers to the PID of an exited parent and that PID has been\n+ *       recycled and given to a new unrelated process.\n+ *\n+ *       Worse, it is possible for a child or descendant of the\n+ *       current process to be given the recycled PID and cause a\n+ *       PPID-cycle.  This would cause an infinite loop building our\n+ *       parent process array.\n+ *\n+ * Note: for completeness, the \"System Idle\" process has PID=0 and\n+ *       PPID=0 and could cause another PPID-cycle.  We don't expect\n+ *       Git to be a descendant of the idle process, but because of\n+ *       PID recycling, it might be possible to get a PPID link value\n+ *       of 0.  This too would cause an infinite loop.\n+ *\n+ * Therefore, we keep an array of the visited PPIDs to guard against\n+ * cycles.\n+ *\n+ * We use a fixed-size array rather than ALLOC_GROW to keep things\n+ * simple and avoid the alloc/realloc overhead.  It is OK if we\n+ * truncate the search and return a partial answer.\n+ */\n+static void get_processes(struct json_writer *jw, HANDLE hSnapshot)\n+{\n+\tPROCESSENTRY32 pe32;\n+\tDWORD pid;\n+\tDWORD pid_list[NR_PIDS_LIMIT];\n+\tint k, nr_pids = 0;\n+\n+\tpid = GetCurrentProcessId();\n+\twhile (find_pid(pid, hSnapshot, &pe32)) {\n+\t\t/* Only report parents. Omit self from the JSON output. */\n+\t\tif (nr_pids)\n+\t\t\tjw_array_string(jw, pe32.szExeFile);\n+\n+\t\t/* Check for cycle in snapshot. (Yes, it happened.) */\n+\t\tfor (k = 0; k < nr_pids; k++)\n+\t\t\tif (pid == pid_list[k]) {\n+\t\t\t\tjw_array_string(jw, \"(cycle)\");\n+\t\t\t\treturn;\n+\t\t\t}\n+\n+\t\tif (nr_pids == NR_PIDS_LIMIT) {\n+\t\t\tjw_array_string(jw, \"(truncated)\");\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tpid_list[nr_pids++] = pid;\n+\n+\t\tpid = pe32.th32ParentProcessID;\n+\t}\n+}\n+\n+/*\n+ * Emit JSON data for the current and parent processes.  Individual\n+ * trace2 targets can decide how to actually print it.\n+ */\n+static void get_ancestry(void)\n+{\n+\tHANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);\n+\n+\tif (hSnapshot != INVALID_HANDLE_VALUE) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\t\tjw_array_begin(&jw, 0);\n+\t\tget_processes(&jw, hSnapshot);\n+\t\tjw_end(&jw);\n+\n+\t\ttrace2_data_json(\"process\", the_repository, \"windows/ancestry\",\n+\t\t\t\t &jw);\n+\n+\t\tjw_release(&jw);\n+\t\tCloseHandle(hSnapshot);\n+\t}\n+}\n+\n+/*\n+ * Is a debugger attached to the current process?\n+ *\n+ * This will catch debug runs (where the debugger started the process).\n+ * This is the normal case.  Since this code is called during our startup,\n+ * it will not report instances where a debugger is attached dynamically\n+ * to a running git process, but that is relatively rare.\n+ */\n+static void get_is_being_debugged(void)\n+{\n+\tif (IsDebuggerPresent())\n+\t\ttrace2_data_intmax(\"process\", the_repository,\n+\t\t\t\t   \"windows/debugger_present\", 1);\n+}\n+\n+void trace2_collect_process_info(void)\n+{\n+\tif (!trace2_is_enabled())\n+\t\treturn;\n+\n+\tget_is_being_debugged();\n+\tget_ancestry();\n+}\ndiff --git a/config.mak.uname b/config.mak.uname\nindex 7b36a1dfe7..a3c03ce2ae 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -391,6 +391,7 @@ ifeq ($(uname_S),Windows)\n \tBASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE\n \tCOMPAT_OBJS = compat/msvc.o compat/winansi.o \\\n \t\tcompat/win32/pthread.o compat/win32/syslog.o \\\n+\t\tcompat/win32/trace2_win32_process_info.o \\\n \t\tcompat/win32/dirent.o\n \tCOMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\\\".exe\\\"\n \tBASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE\n@@ -545,6 +546,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))\n \tCOMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32\n \tCOMPAT_CFLAGS += -DSTRIP_EXTENSION=\\\".exe\\\"\n \tCOMPAT_OBJS += compat/mingw.o compat/winansi.o \\\n+\t\tcompat/win32/trace2_win32_process_info.o \\\n \t\tcompat/win32/path-utils.o \\\n \t\tcompat/win32/pthread.o compat/win32/syslog.o \\\n \t\tcompat/win32/dirent.o\ndiff --git a/trace2.h b/trace2.h\nindex fce9891f53..ae5020d0e6 100644\n--- a/trace2.h\n+++ b/trace2.h\n@@ -368,4 +368,18 @@ void trace2_printf(const char *fmt, ...);\n /* clang-format on */\n #endif\n \n+/*\n+ * Optional platform-specific code to dump information about the\n+ * current and any parent process(es).  This is intended to allow\n+ * post-processors to know who spawned this git instance and anything\n+ * else the platform may be able to tell us about the current process.\n+ */\n+#if defined(GIT_WINDOWS_NATIVE)\n+void trace2_collect_process_info(void);\n+#else\n+#define trace2_collect_process_info() \\\n+\tdo {                          \\\n+\t} while (0)\n+#endif\n+\n #endif /* TRACE2_H */\n-- \ngitgitgadget\n\n"},{"id":"369985","messageId":"8ac6acf88d042dbe2abfb5921848bfae942a7045.1550874298.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v7.git.gitgitgadget@gmail.com","subject":"[PATCH v7 01/15] trace2: Documentation/technical/api-trace2.txt","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-22T22:25:00Z","receivedAt":"2019-02-22T22:25:08Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreated design document for Trace2 feature.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Documentation/technical/api-trace2.txt | 1350 ++++++++++++++++++++++++\n 1 file changed, 1350 insertions(+)\n create mode 100644 Documentation/technical/api-trace2.txt\n\ndiff --git a/Documentation/technical/api-trace2.txt b/Documentation/technical/api-trace2.txt\nnew file mode 100644\nindex 0000000000..fa0e421b8f\n--- /dev/null\n+++ b/Documentation/technical/api-trace2.txt\n@@ -0,0 +1,1350 @@\n+= Trace2 API\n+\n+The Trace2 API can be used to print debug, performance, and telemetry\n+information to stderr or a file.  The Trace2 feature is inactive unless\n+explicitly enabled by enabling one or more Trace2 Targets.\n+\n+The Trace2 API is intended to replace the existing (Trace1)\n+printf-style tracing provided by the existing `GIT_TRACE` and\n+`GIT_TRACE_PERFORMANCE` facilities.  During initial implementation,\n+Trace2 and Trace1 may operate in parallel.\n+\n+The Trace2 API defines a set of high-level messages with known fields,\n+such as (`start`: `argv`) and (`exit`: {`exit-code`, `elapsed-time`}).\n+\n+Trace2 instrumentation throughout the Git code base sends Trace2\n+messages to the enabled Trace2 Targets.  Targets transform these\n+messages content into purpose-specific formats and write events to\n+their data streams.  In this manner, the Trace2 API can drive\n+many different types of analysis.\n+\n+Targets are defined using a VTable allowing easy extension to other\n+formats in the future.  This might be used to define a binary format,\n+for example.\n+\n+== Trace2 Targets\n+\n+Trace2 defines the following set of Trace2 Targets.\n+Format details are given in a later section.\n+\n+`GIT_TR2` (NORMAL)::\n+\n+\ta simple printf format like GIT_TRACE.\n++\n+------------\n+$ export GIT_TR2=~/log.normal\n+$ git version\n+git version 2.20.1.155.g426c96fcdb\n+------------\n++\n+------------\n+$ cat ~/log.normal\n+12:28:42.620009 common-main.c:38                  version 2.20.1.155.g426c96fcdb\n+12:28:42.620989 common-main.c:39                  start git version\n+12:28:42.621101 git.c:432                         cmd_name version (version)\n+12:28:42.621215 git.c:662                         exit elapsed:0.001227 code:0\n+12:28:42.621250 trace2/tr2_tgt_normal.c:124       atexit elapsed:0.001265 code:0\n+------------\n+\n+`GIT_TR2_PERF` (PERF)::\n+\n+\ta column-based format to replace GIT_TRACE_PERFORMANCE suitable for\n+\tdevelopment and testing, possibly to complement tools like gprof.\n++\n+------------\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git version\n+git version 2.20.1.155.g426c96fcdb\n+------------\n++\n+------------\n+$ cat ~/log.perf\n+12:28:42.620675 common-main.c:38                  | d0 | main                     | version      |     |           |           |            | 2.20.1.155.g426c96fcdb\n+12:28:42.621001 common-main.c:39                  | d0 | main                     | start        |     |           |           |            | git version\n+12:28:42.621111 git.c:432                         | d0 | main                     | cmd_name     |     |           |           |            | version (version)\n+12:28:42.621225 git.c:662                         | d0 | main                     | exit         |     |  0.001227 |           |            | code:0\n+12:28:42.621259 trace2/tr2_tgt_perf.c:211         | d0 | main                     | atexit       |     |  0.001265 |           |            | code:0\n+------------\n+\n+`GIT_TR2_EVENT` (EVENT)::\n+\n+\ta JSON-based format of event data suitable for telemetry analysis.\n++\n+------------\n+$ export GIT_TR2_EVENT=~/log.event\n+$ git version\n+git version 2.20.1.155.g426c96fcdb\n+------------\n++\n+------------\n+$ cat ~/log.event\n+{\"event\":\"version\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.620713\",\"file\":\"common-main.c\",\"line\":38,\"evt\":\"1\",\"exe\":\"2.20.1.155.g426c96fcdb\"}\n+{\"event\":\"start\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621027\",\"file\":\"common-main.c\",\"line\":39,\"argv\":[\"git\",\"version\"]}\n+{\"event\":\"cmd_name\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621122\",\"file\":\"git.c\",\"line\":432,\"name\":\"version\",\"hierarchy\":\"version\"}\n+{\"event\":\"exit\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621236\",\"file\":\"git.c\",\"line\":662,\"t_abs\":0.001227,\"code\":0}\n+{\"event\":\"atexit\",\"sid\":\"1547659722619736-11614\",\"thread\":\"main\",\"time\":\"2019-01-16 17:28:42.621268\",\"file\":\"trace2/tr2_tgt_event.c\",\"line\":163,\"t_abs\":0.001265,\"code\":0}\n+------------\n+\n+== Enabling a Target\n+\n+A Trace2 Target is enabled when the corresponding environment variable\n+(`GIT_TR2`, `GIT_TR2_PERF`, or `GIT_TR2_EVENT`) is set.  The following\n+values are recognized.\n+\n+`0`::\n+`false`::\n+\n+\tDisables the target.\n+\n+`1`::\n+`true`::\n+\n+\tEnables the target and writes stream to `STDERR`.\n+\n+`[2-9]`::\n+\n+\tEnables the target and writes to the already opened file descriptor.\n+\n+`<absolute-pathname>`::\n+\n+\tEnables the target, opens and writes to the file in append mode.\n+\n+`af_unix:[<socket_type>:]<absolute-pathname>`::\n+\n+\tEnables the target, opens and writes to a Unix Domain Socket\n+\t(on platforms that support them).\n++\n+Socket type can be either `stream` or `dgram`.  If the socket type is\n+omitted, Git will try both.\n+\n+== Trace2 API\n+\n+All public Trace2 functions and macros are defined in `trace2.h` and\n+`trace2.c`.  All public symbols are prefixed with `trace2_`.\n+\n+There are no public Trace2 data structures.\n+\n+The Trace2 code also defines a set of private functions and data types\n+in the `trace2/` directory.  These symbols are prefixed with `tr2_`\n+and should only be used by functions in `trace2.c`.\n+\n+== Conventions for Public Functions and Macros\n+\n+The functions defined by the Trace2 API are declared and documented\n+in `trace2.h`.  It defines the API functions and wrapper macros for\n+Trace2.\n+\n+Some functions have a `_fl()` suffix to indicate that they take `file`\n+and `line-number` arguments.\n+\n+Some functions have a `_va_fl()` suffix to indicate that they also\n+take a `va_list` argument.\n+\n+Some functions have a `_printf_fl()` suffix to indicate that they also\n+take a varargs argument.\n+\n+There are CPP wrapper macros and ifdefs to hide most of these details.\n+See `trace2.h` for more details.  The following discussion will only\n+describe the simplified forms.\n+\n+== Public API\n+\n+All Trace2 API functions send a messsage to all of the active\n+Trace2 Targets.  This section describes the set of available\n+messages.\n+\n+It helps to divide these functions into groups for discussion\n+purposes.\n+\n+=== Basic Command Messages\n+\n+These are concerned with the lifetime of the overall git process.\n+\n+`void trace2_initialize()`::\n+\n+\tDetermines if any Trace2 Targets should be enabled and\n+\tinitializes the Trace2 facility.  This includes starting the\n+\telapsed time clocks and thread local storage (TLS).\n++\n+This function emits a \"version\" message containing the version of git\n+and the Trace2 protocol.\n++\n+This function should be called from `main()` as early as possible in\n+the life of the process.\n+\n+`int trace2_is_enabled()`::\n+\n+\tReturns 1 if Trace2 is enabled (at least one target is\n+\tactive).\n+\n+`void trace2_cmd_start(int argc, const char **argv)`::\n+\n+\tEmits a \"start\" message containing the process command line\n+\targuments.\n+\n+`int trace2_cmd_exit(int exit_code)`::\n+\n+\tEmits an \"exit\" message containing the process exit-code and\n+\telapsed time.\n++\n+Returns the exit-code.\n+\n+`void trace2_cmd_error(const char *fmt, va_list ap)`::\n+\n+\tEmits an \"error\" message containing a formatted error message.\n+\n+`void trace2_cmd_path(const char *pathname)`::\n+\n+\tEmits a \"cmd_path\" message with the full pathname of the\n+\tcurrent process.\n+\n+=== Command Detail Messages\n+\n+These are concerned with describing the specific Git command\n+after the command line, config, and environment are inspected.\n+\n+`void trace2_cmd_name(const char *name)`::\n+\n+\tEmits a \"cmd_name\" message with the canonical name of the\n+\tcommand, for example \"status\" or \"checkout\".\n+\n+`void trace2_cmd_mode(const char *mode)`::\n+\n+\tEmits a \"cmd_mode\" message with a qualifier name to further\n+\tdescribe the current git command.\n++\n+This message is intended to be used with git commands having multiple\n+major modes.  For example, a \"checkout\" command can checkout a new\n+branch or it can checkout a single file, so the checkout code could\n+emit a cmd_mode message of \"branch\" or \"file\".\n+\n+`void trace2_cmd_alias(const char *alias, const char **argv_expansion)`::\n+\n+\tEmits an \"alias\" message containing the alias used and the\n+\targument expansion.\n+\n+`void trace2_def_param(const char *parameter, const char *value)`::\n+\n+\tEmits a \"def_param\" message containing a key/value pair.\n++\n+This message is intended to report some global aspect of the current\n+command, such as a configuration setting or command line switch that\n+significantly affects program performance or behavior, such as\n+`core.abbrev`, `status.showUntrackedFiles`, or `--no-ahead-behind`.\n+\n+`void trace2_cmd_list_config()`::\n+\n+\tEmits a \"def_param\" messages for \"important\" configuration\n+\tsettings.\n++\n+The environment variable `GIT_TR2_CONFIG_PARAMS` can be set to a\n+list of patterns of important configuration settings, for example:\n+`core.*,remote.*.url`.  This function will iterate over all config\n+settings and emit a \"def_param\" message for each match.\n+\n+`void trace2_cmd_set_config(const char *key, const char *value)`::\n+\n+\tEmits a \"def_param\" message for a specific configuration\n+\tsetting IFF it matches the `GIT_TR2_CONFIG_PARAMS` pattern.\n++\n+This is used to hook into `git_config_set()` and catch any\n+configuration changes and update a value previously reported by\n+`trace2_cmd_list_config()`.\n+\n+`void trace2_def_repo(struct repository *repo)`::\n+\n+\tRegisters a repository with the Trace2 layer.  Assigns a\n+\tunique \"repo-id\" to `repo->trace2_repo_id`.\n++\n+Emits a \"worktree\" messages containing the repo-id and the worktree\n+pathname.\n++\n+Region and data messages (described later) may refer to this repo-id.\n++\n+The main/top-level repository will have repo-id value 1 (aka \"r1\").\n++\n+The repo-id field is in anticipation of future in-proc submodule\n+repositories.\n+\n+=== Child Process Messages\n+\n+These are concerned with the various spawned child processes,\n+including shell scripts, git commands, editors, pagers, and hooks.\n+\n+`void trace2_child_start(struct child_process *cmd)`::\n+\n+\tEmits a \"child_start\" message containing the \"child-id\",\n+\t\"child-argv\", and \"child-classification\".\n++\n+Before calling this, set `cmd->trace2_child_class` to a name\n+describing the type of child process, for example \"editor\".\n++\n+This function assigns a unique \"child-id\" to `cmd->trace2_child_id`.\n+This field is used later during the \"child_exit\" message to associate\n+it with the \"child_start\" message.\n++\n+This function should be called before spawning the child process.\n+\n+`void trace2_child_exit(struct child_proess *cmd, int child_exit_code)`::\n+\n+\tEmits a \"child_exit\" message containing the \"child-id\",\n+\tthe child's elapsed time and exit-code.\n++\n+The reported elapsed time includes the process creation overhead and\n+time spend waiting for it to exit, so it may be slightly longer than\n+the time reported by the child itself.\n++\n+This function should be called after reaping the child process.\n+\n+`int trace2_exec(const char *exe, const char **argv)`::\n+\n+\tEmits a \"exec\" message containing the \"exec-id\" and the\n+\targv of the new process.\n++\n+This function should be called before calling one of the `exec()`\n+variants, such as `execvp()`.\n++\n+This function returns a unique \"exec-id\".  This value is used later\n+if the exec() fails and a \"exec-result\" message is necessary.\n+\n+`void trace2_exec_result(int exec_id, int error_code)`::\n+\n+\tEmits a \"exec_result\" message containing the \"exec-id\"\n+\tand the error code.\n++\n+On Unix-based systems, `exec()` does not return if successful.\n+This message is used to indicate that the `exec()` failed and\n+that the current program is continuing.\n+\n+=== Git Thread Messages\n+\n+These messages are concerned with Git thread usage.\n+\n+`void trace2_thread_start(const char *thread_name)`::\n+\n+\tEmits a \"thread_start\" message.\n++\n+The `thread_name` field should be a descriptive name, such as the\n+unique name of the thread-proc.  A unique \"thread-id\" will be added\n+to the name to uniquely identify thread instances.\n++\n+Region and data messages (described later) may refer to this thread\n+name.\n++\n+This function must be called by the thread-proc of the new thread\n+(so that TLS data is properly initialized) and not by the caller\n+of `pthread_create()`.\n+\n+`void trace2_thread_exit()`::\n+\n+\tEmits a \"thread_exit\" message containing the thread name\n+\tand the thread elapsed time.\n++\n+This function must be called by the thread-proc before it returns\n+(so that the coorect TLS data is used and cleaned up.  It should\n+not be called by the caller of `pthread_join()`.\n+\n+=== Region and Data Messages\n+\n+These are concerned with recording performance data\n+over regions or spans of code.\n+\n+`void trace2_region_enter(const char *category, const char *label, const struct repository *repo)`::\n+   \n+`void trace2_region_enter_printf(const char *category, const char *label, const struct repository *repo, const char *fmt, ...)`::\n+\n+`void trace2_region_enter_printf_va(const char *category, const char *label, const struct repository *repo, const char *fmt, va_list ap)`::\n+\n+\tEmits a thread-relative \"region_enter\" message with optional\n+\tprintf string.\n++\n+This function pushes a new region nesting stack level on the current\n+thread and starts a clock for the new stack frame.\n++\n+The `category` field is an arbitrary category name used to classify\n+regions by feature area, such as \"status\" or \"index\".  At this time\n+it is only just printed along with the rest of the message.  It may\n+be used in the future to filter messages.\n++\n+The `label` field is an arbitrary label used to describe the activity\n+being started, such as \"read_recursive\" or \"do_read_index\".\n++\n+The `repo` field, if set, will be used to get the \"repo-id\", so that\n+recursive oerations can be attributed to the correct repository.\n+\n+`void trace2_region_leave(const char *category, const char *label, const struct repository *repo)`::\n+\n+`void trace2_region_leave_printf(const char *category, const char *label, const struct repository *repo, const char *fmt, ...)`::\n+\n+`void trace2_region_leave_printf_va(const char *category, const char *label, const struct repository *repo, const char *fmt, va_list ap)`::\n+\n+\tEmits a thread-relative \"region_leave\" message with optional\n+\tprintf string.\n++\n+This function pops the region nesting stack on the current thread\n+and reports the elapsed time of the stack frame.\n++\n+The `category`, `label`, and `repo` fields are the same as above.\n+The `category` and `label` do not need to match the correpsonding\n+\"region_enter\" message, but it makes the data stream easier to\n+understand.\n+\n+`void trace2_data_string(const char *category, const struct repository *repo, const char *key, const char * value)`::\n+\n+`void trace2_data_intmax(const char *category, const struct repository *repo, const char *key, intmax value)`::\n+\n+`void trace2_data_json(const char *category, const struct repository *repo, const char *key, const struct json_writer *jw)`::\n+\n+\tEmits a region- and thread-relative \"data\" or \"data_json\" message.\n++\n+This is a key/value pair message containing information about the\n+current thread, region stack, and repository.  This could be used\n+to print the number of files in a directory during a multi-threaded\n+recursive tree walk.\n+\n+`void trace2_printf(const char *fmt, ...)`::\n+\n+`void trace2_printf_va(const char *fmt, va_list ap)`::\n+\n+\tEmits a region- and thread-relative \"printf\" message.\n+\n+== Trace2 Target Formats\n+\n+=== NORMAL Format\n+\n+NORMAL format is enabled when the `GIT_TR2` environment variable is\n+set.\n+\n+Events are written as lines of the form:\n+\n+------------\n+[<time> SP <filename>:<line> SP+] <event-name> [[SP] <event-message>] LF\n+------------\n+\n+`<event-name>`::\n+\n+\tis the event name.\n+\n+`<event-message>`::\n+\tis a free-form printf message intended for human consumption.\n++\n+Note that this may contain embedded LF or CRLF characters that are\n+not escaped, so the event may spill across multiple lines.\n+\n+If `GIT_TR2_BRIEF` is true, the `time`, `filename`, and `line` fields\n+are omitted.\n+\n+This target is intended to be more of a summary (like GIT_TRACE) and\n+less detailed than the other targets.  It ignores thread, region, and\n+data messages, for example.\n+\n+=== PERF Format\n+\n+PERF format is enabled when the `GIT_TR2_PERF` environment variable\n+is set.\n+\n+Events are written as lines of the form:\n+\n+------------\n+[<time> SP <filename>:<line> SP+\n+    BAR SP] d<depth> SP\n+    BAR SP <thread-name> SP+\n+    BAR SP <event-name> SP+\n+    BAR SP [r<repo-id>] SP+\n+    BAR SP [<t_abs>] SP+\n+    BAR SP [<t_rel>] SP+\n+    BAR SP [<category>] SP+\n+    BAR SP DOTS* <perf-event-message>\n+    LF\n+------------\n+\n+`<depth>`::\n+\tis the git process depth.  This is the number of parent\n+\tgit processes.  A top-level git command has depth value \"d0\".\n+\tA child of it has depth value \"d1\".  A second level child\n+\thas depth value \"d2\" and so on.\n+\n+`<thread-name>`::\n+\tis a unique name for the thread.  The primary thread\n+\tis called \"main\".  Other thread names are of the form \"th%d:%s\"\n+\tand include a unique number and the name of the thread-proc.\n+\n+`<event-name>`::\n+\tis the event name.\n+\n+`<repo-id>`::\n+\twhen present, is a number indicating the repository\n+\tin use.  A `def_repo` event is emitted when a repository is\n+\topened.  This defines the repo-id and associated worktree.\n+\tSubsequent repo-specific events will reference this repo-id.\n++\n+Currently, this is always \"r1\" for the main repository.\n+This field is in anticipation of in-proc submodules in the future.\n+\n+`<t_abs>`::\n+\twhen present, is the absolute time in seconds since the\n+\tprogram started.\n+\n+`<t_rel>`::\n+\twhen present, is time in seconds relative to the start of\n+\tthe current region.  For a thread-exit event, it is the elapsed\n+\ttime of the thread.\n+\n+`<category>`::\n+\tis present on region and data events and is used to\n+\tindicate a broad category, such as \"index\" or \"status\".\n+\n+`<perf-event-message>`::\n+\tis a free-form printf message intended for human consumption.\n+\n+------------\n+15:33:33.532712 wt-status.c:2310                  | d0 | main                     | region_enter | r1  |  0.126064 |           | status     | label:print\n+15:33:33.532712 wt-status.c:2331                  | d0 | main                     | region_leave | r1  |  0.127568 |  0.001504 | status     | label:print\n+------------\n+\n+If `GIT_TR2_PERF_BRIEF` is true, the `time`, `file`, and `line`\n+fields are omitted.\n+\n+------------\n+d0 | main                     | region_leave | r1  |  0.011717 |  0.009122 | index      | label:preload\n+------------\n+\n+The PERF target is intended for interactive performance analysis\n+during development and is quite noisy.\n+\n+=== EVENT Format\n+\n+EVENT format is enabled when the `GIT_TR2_EVENT` environment\n+variable is set.\n+\n+Each event is a JSON-object containing multiple key/value pairs\n+written as a single line and followed by a LF.\n+\n+------------\n+'{' <key> ':' <value> [',' <key> ':' <value>]* '}' LF\n+------------\n+\n+Some key/value pairs are common to all events and some are\n+event-specific.\n+\n+==== Common Key/Value Pairs\n+\n+The following key/value pairs are common to all events:\n+\n+------------\n+{\n+\t\"event\":\"version\",\n+\t\"sid\":\"1547659722619736-11614\",\n+\t\"thread\":\"main\",\n+\t\"time\":\"2019-01-16 17:28:42.620713\",\n+\t\"file\":\"common-main.c\",\n+\t\"line\":38,\n+\t...\n+}\n+------------\n+\n+`\"event\":<event>`::\n+\tis the event name.\n+\n+`\"sid\":<sid>`::\n+\tis the session-id.  This is a unique string to identify the\n+\tprocess instance to allow all events emitted by a process to\n+\tbe identified.  A session-id is used instead of a PID because\n+\tPIDs are recycled by the OS.  For child git processes, the\n+\tsession-id is prepended with the session-id of the parent git\n+\tprocess to allow parent-child relationships to be identified\n+\tduring post-processing.\n+\n+`\"thread\":<thread>`::\n+\tis the thread name.\n+\n+`\"time\":<time>`::\n+\tis the UTC time of the event.\n+\n+`\"file\":<filename>`::\n+\tis source file generating the event.\n+\n+`\"line\":<line-number>`::\n+\tis the integer source line number generating the event.\n+\n+`\"repo\":<repo-id>`::\n+\twhen present, is the integer repo-id as described previously.\n+\n+If `GIT_TR2_EVENT_BRIEF` is true, the `file` and `line` fields are omitted\n+from all events and the `time` field is only present on the \"start\" and\n+\"atexit\" events.\n+\n+==== Event-Specific Key/Value Pairs\n+\n+`\"version\"`::\n+\tThis event gives the version of the executable and the EVENT format.\n++\n+------------\n+{\n+\t\"event\":\"version\",\n+\t...\n+\t\"evt\":\"1\",\t\t       # EVENT format version\n+\t\"exe\":\"2.20.1.155.g426c96fcdb\" # git version\n+}\n+------------\n+\n+`\"start\"`::\n+\tThis event contains the complete argv received by main().\n++\n+------------\n+{\n+\t\"event\":\"start\",\n+\t...\n+\t\"argv\":[\"git\",\"version\"]\n+}\n+------------\n+\n+`\"exit\"`::\n+\tThis event is emitted when git calls `exit()`.\n++\n+------------\n+{\n+\t\"event\":\"exit\",\n+\t...\n+\t\"t_abs\":0.001227, # elapsed time in seconds\n+\t\"code\":0\t  # exit code\n+}\n+------------\n+\n+`\"atexit\"`::\n+\tThis event is emitted by the Trace2 `atexit` routine during\n+\tfinal shutdown.  It should be the last event emitted by the\n+\tprocess.\n++\n+(The elapsed time reported here is greater than the time reported in\n+the \"exit\" event because it runs after all other atexit tasks have\n+completed.)\n++\n+------------\n+{\n+\t\"event\":\"atexit\",\n+\t...\n+\t\"t_abs\":0.001227, # elapsed time in seconds\n+\t\"code\":0          # exit code\n+}\n+------------\n+\n+`\"signal\"`::\n+\tThis event is emitted when the program is terminated by a user\n+\tsignal.  Depending on the platform, the signal event may\n+\tprevent the \"atexit\" event from being generated.\n++\n+------------\n+{\n+\t\"event\":\"signal\",\n+\t...\n+\t\"t_abs\":0.001227,  # elapsed time in seconds\n+\t\"signal\":13        # SIGTERM, SIGINT, etc.\n+}\n+------------\n+\n+`\"error\"`::\n+\tThis event is emitted when one of the `error()`, `die()`,\n+\tor `usage()` functions are called.\n++\n+------------\n+{\n+\t\"event\":\"error\",\n+\t...\n+\t\"msg\":\"invalid option: --cahced\", # formatted error message\n+\t\"fmt\":\"invalid option: %s\"\t  # error format string\n+}\n+------------\n++\n+The error event may be emitted more than once.  The format string\n+allows post-processors to group errors by type without worrying\n+about specific error arguments.\n+\n+`\"cmd_path\"`::\n+\tThis event contains the discovered full path of the git\n+\texecutable (on platforms that are configured to resolve it).\n++\n+------------\n+{\n+\t\"event\":\"cmd_path\",\n+\t...\n+\t\"path\":\"C:/work/gfw/git.exe\"\n+}\n+------------\n+\n+`\"cmd_name\"`::\n+\tThis event contains the command name for this git process\n+\tand the hierarchy of commands from parent git processes.\n++\n+------------\n+{\n+\t\"event\":\"cmd_name\",\n+\t...\n+\t\"name\":\"pack-objects\",\n+\t\"hierarchy\":\"push/pack-objects\"\n+}\n+------------\n++\n+Normally, the \"name\" field contains the canonical name of the\n+command.  When a canonical name is not available, one of\n+these special values are used:\n++\n+------------\n+\"_query_\"            # \"git --html-path\"\n+\"_run_dashed_\"       # when \"git foo\" tries to run \"git-foo\"\n+\"_run_shell_alias_\"  # alias expansion to a shell command\n+\"_run_git_alias_\"    # alias expansion to a git command\n+\"_usage_\"            # usage error\n+------------\n+\n+`\"cmd_mode\"`::\n+\tThis event, when present, describes the command variant This\n+\tevent may be emitted more than once.\n++\n+------------\n+{\n+\t\"event\":\"cmd_mode\",\n+\t...\n+\t\"name\":\"branch\"\n+}\n+------------\n++\n+The \"name\" field is an arbitrary string to describe the command mode.\n+For example, checkout can checkout a branch or an individual file.\n+And these variations typically have different performance\n+characteristics that are not comparable.\n+\n+`\"alias\"`::\n+\tThis event is present when an alias is expanded.\n++\n+------------\n+{\n+\t\"event\":\"alias\",\n+\t...\n+\t\"alias\":\"l\",\t\t # registered alias\n+\t\"argv\":[\"log\",\"--graph\"] # alias expansion\n+}\n+------------\n+\n+`\"child_start\"`::\n+\tThis event describes a child process that is about to be\n+\tspawned.\n++\n+------------\n+{\n+\t\"event\":\"child_start\",\n+\t...\n+\t\"child_id\":2,\n+\t\"child_class\":\"?\",\n+\t\"use_shell\":false,\n+\t\"argv\":[\"git\",\"rev-list\",\"--objects\",\"--stdin\",\"--not\",\"--all\",\"--quiet\"]\n+\n+\t\"hook_name\":\"<hook_name>\"  # present when child_class is \"hook\"\n+\t\"cd\":\"<path>\"\t\t   # present when cd is required\n+}\n+------------\n++\n+The \"child_id\" field can be used to match this child_start with the\n+corresponding child_exit event.\n++\n+The \"child_class\" field is a rough classification, such as \"editor\",\n+\"pager\", \"transport/*\", and \"hook\".  Unclassified children are classified\n+with \"?\".\n+\n+`\"child_exit\"`::\n+\tThis event is generated after the current process has returned\n+\tfrom the waitpid() and collected the exit information from the\n+\tchild.\n++\n+------------\n+{\n+\t\"event\":\"child_exit\",\n+\t...\n+\t\"child_id\":2,\n+\t\"pid\":14708,\t # child PID\n+\t\"code\":0,\t # child exit-code\n+\t\"t_rel\":0.110605 # observed run-time of child process\n+}\n+------------\n++\n+Note that the session-id of the child process is not available to\n+the current/spawning process, so the child's PID is reported here as\n+a hint for post-processing.  (But it is only a hint because the child\n+proces may be a shell script which doesn't have a session-id.)\n++\n+Note that the `t_rel` field contains the observed run time in seconds\n+for the child process (starting before the fork/exec/spawn and\n+stopping after the waitpid() and includes OS process creation overhead).\n+So this time will be slightly larger than the atexit time reported by\n+the child process itself.\n+\n+`\"exec\"`::\n+\tThis event is generated before git attempts to `exec()`\n+\tanother command rather than starting a child process.\n++\n+------------\n+{\n+\t\"event\":\"exec\",\n+\t...\n+\t\"exec_id\":0,\n+\t\"exe\":\"git\",\n+\t\"argv\":[\"foo\", \"bar\"]\n+}\n+------------\n++\n+The \"exec_id\" field is a command-unique id and is only useful if the\n+`exec()` fails and a corresponding exec_result event is generated.\n+\n+`\"exec_result\"`::\n+\tThis event is generated if the `exec()` fails and control\n+\treturns to the current git command.\n++\n+------------\n+{\n+\t\"event\":\"exec_result\",\n+\t...\n+\t\"exec_id\":0,\n+\t\"code\":1      # error code (errno) from exec()\n+}\n+------------\n+\n+`\"thread_start\"`::\n+\tThis event is generated when a thread is started.  It is\n+\tgenerated from *within* the new thread's thread-proc (for TLS\n+\treasons).\n++\n+------------\n+{\n+\t\"event\":\"thread_start\",\n+\t...\n+\t\"thread\":\"th02:preload_thread\" # thread name\n+}\n+------------\n+\n+`\"thread_exit\"`::\n+\tThis event is generated when a thread exits.  It is generated\n+\tfrom *within* the thread's thread-proc (for TLS reasons).\n++\n+------------\n+{\n+\t\"event\":\"thread_exit\",\n+\t...\n+\t\"thread\":\"th02:preload_thread\", # thread name\n+\t\"t_rel\":0.007328                # thread elapsed time\n+}\n+------------\n+\n+`\"def_param\"`::\n+\tThis event is generated to log a global parameter.\n++\n+------------\n+{\n+\t\"event\":\"def_param\",\n+\t...\n+\t\"param\":\"core.abbrev\",\n+\t\"value\":\"7\"\n+}\n+------------\n+\n+`\"def_repo\"`::\n+\tThis event defines a repo-id and associates it with the root\n+\tof the worktree.\n++\n+------------\n+{\n+\t\"event\":\"def_repo\",\n+\t...\n+\t\"repo\":1,\n+\t\"worktree\":\"/Users/jeffhost/work/gfw\"\n+}\n+------------\n++\n+As stated earlier, the repo-id is currently always 1, so there will\n+only be one def_repo event.  Later, if in-proc submodules are\n+supported, a def_repo event should be emitted for each submodule\n+visited.\n+\n+`\"region_enter\"`::\n+\tThis event is generated when entering a region.\n++\n+------------\n+{\n+\t\"event\":\"region_enter\",\n+\t...\n+\t\"repo\":1,                # optional\n+\t\"nesting\":1,             # current region stack depth\n+\t\"category\":\"index\",      # optional\n+\t\"label\":\"do_read_index\", # optional\n+\t\"msg\":\".git/index\"       # optional\n+}\n+------------\n++\n+The `category` field may be used in a future enhancement to\n+do category-based filtering.\n++\n+The `GIT_TR2_EVENT_NESTING` environment variable can be used to\n+filter deeply nested regions and data events.  It defaults to \"2\".\n+\n+`\"region_leave\"`::\n+\tThis event is generated when leaving a region.\n++\n+------------\n+{\n+\t\"event\":\"region_leave\",\n+\t...\n+\t\"repo\":1,                # optional\n+\t\"t_rel\":0.002876,        # time spent in region in seconds\n+\t\"nesting\":1,             # region stack depth\n+\t\"category\":\"index\",      # optional\n+\t\"label\":\"do_read_index\", # optional\n+\t\"msg\":\".git/index\"       # optional\n+}\n+------------\n+\n+`\"data\"`::\n+\tThis event is generated to log a thread- and region-local\n+\tkey/value pair.\n++\n+------------\n+{\n+\t\"event\":\"data\",\n+\t...\n+\t\"repo\":1,              # optional\n+\t\"t_abs\":0.024107,      # absolute elapsed time\n+\t\"t_rel\":0.001031,      # elapsed time in region/thread\n+\t\"nesting\":2,           # region stack depth\n+\t\"category\":\"index\",\n+\t\"key\":\"read/cache_nr\",\n+\t\"value\":\"3552\"\n+}\n+------------\n++\n+The \"value\" field may be an integer or a string.\n+\n+`\"data-json\"`::\n+\tThis event is generated to log a pre-formatted JSON string\n+\tcontaining structured data.\n++\n+------------\n+{\n+\t\"event\":\"data_json\",\n+\t...\n+\t\"repo\":1,              # optional\n+\t\"t_abs\":0.015905,\n+\t\"t_rel\":0.015905,\n+\t\"nesting\":1,\n+\t\"category\":\"process\",\n+\t\"key\":\"windows/ancestry\",\n+\t\"value\":[\"bash.exe\",\"bash.exe\"]\n+}\n+------------\n+\n+== Example Trace2 API Usage\n+\n+Here is a hypothetical usage of the Trace2 API showing the intended\n+usage (without worrying about the actual Git details).\n+\n+Initialization::\n+\n+\tInitialization happens in `main()`.  Behind the scenes, an\n+\t`atexit` and `signal` handler are registered.\n++\n+----------------\n+int main(int argc, const char **argv)\n+{\n+\tint exit_code;\n+\n+\ttrace2_initialize();\n+\ttrace2_cmd_start(argv);\n+\n+\texit_code = cmd_main(argc, argv);\n+\n+\ttrace2_cmd_exit(exit_code);\n+\n+\treturn exit_code;\n+}\n+----------------\n+\n+Command Details::\n+\n+\tAfter the basics are established, additional command\n+\tinformation can be sent to Trace2 as it is discovered.\n++\n+----------------\n+int cmd_checkout(int argc, const char **argv)\n+{\n+\ttrace2_cmd_name(\"checkout\");\n+\ttrace2_cmd_mode(\"branch\");\n+\ttrace2_def_repo(the_repository);\n+\n+\t// emit \"def_param\" messages for \"interesting\" config settings.\n+\ttrace2_cmd_list_config();\n+\n+\tif (do_something())\n+\t    trace2_cmd_error(\"Path '%s': cannot do something\", path);\n+\n+\treturn 0;\n+}\n+----------------\n+\n+Child Processes::\n+\n+\tWrap code spawning child processes.\n++\n+----------------\n+void run_child(...)\n+{\n+\tint child_exit_code;\n+\tstruct child_process cmd = CHILD_PROCESS_INIT;\n+\t...\n+\tcmd.trace2_child_class = \"editor\";\n+\n+\ttrace2_child_start(&cmd);\n+\tchild_exit_code = spawn_child_and_wait_for_it();\n+\ttrace2_child_exit(&cmd, child_exit_code);\n+}\n+----------------\n++\n+For example, the following fetch command spawned ssh, index-pack,\n+rev-list, and gc.  This example also shows that fetch took\n+5.199 seconds and of that 4.932 was in ssh.\n++\n+----------------\n+$ export GIT_TR2_BRIEF=1\n+$ export GIT_TR2=~/log.normal\n+$ git fetch origin\n+...\n+----------------\n++\n+----------------\n+$ cat ~/log.normal\n+version 2.20.1.vfs.1.1.47.g534dbe1ad1\n+start git fetch origin\n+worktree /Users/jeffhost/work/gfw\n+cmd_name fetch (fetch)\n+child_start[0] ssh git@github.com ...\n+child_start[1] git index-pack ...\n+... (Trace2 events from child processes omitted)\n+child_exit[1] pid:14707 code:0 elapsed:0.076353\n+child_exit[0] pid:14706 code:0 elapsed:4.931869\n+child_start[2] git rev-list ...\n+... (Trace2 events from child process omitted)\n+child_exit[2] pid:14708 code:0 elapsed:0.110605\n+child_start[3] git gc --auto\n+... (Trace2 events from child process omitted)\n+child_exit[3] pid:14709 code:0 elapsed:0.006240\n+exit elapsed:5.198503 code:0\n+atexit elapsed:5.198541 code:0\n+----------------\n++\n+When a git process is a (direct or indirect) child of another\n+git process, it inherits Trace2 context information.  This\n+allows the child to print the command hierarchy.  This example\n+shows gc as child[3] of fetch.  When the gc process reports\n+its name as \"gc\", it also reports the hierarchy as \"fetch/gc\".\n+(In this example, trace2 messages from the child process is\n+indented for clarity.)\n++\n+----------------\n+$ export GIT_TR2_BRIEF=1\n+$ export GIT_TR2=~/log.normal\n+$ git fetch origin\n+...\n+----------------\n++\n+----------------\n+$ cat ~/log.normal\n+version 2.20.1.160.g5676107ecd.dirty\n+start git fetch official\n+worktree /Users/jeffhost/work/gfw\n+cmd_name fetch (fetch)\n+...\n+child_start[3] git gc --auto\n+    version 2.20.1.160.g5676107ecd.dirty\n+    start /Users/jeffhost/work/gfw/git gc --auto\n+    worktree /Users/jeffhost/work/gfw\n+    cmd_name gc (fetch/gc)\n+    exit elapsed:0.001959 code:0\n+    atexit elapsed:0.001997 code:0\n+child_exit[3] pid:20303 code:0 elapsed:0.007564\n+exit elapsed:3.868938 code:0\n+atexit elapsed:3.868970 code:0\n+----------------\n+\n+Regions::\n+\n+\tRegions can be use to time an interesting section of code.\n++\n+----------------\n+void wt_status_collect(struct wt_status *s)\n+{\n+\ttrace2_region_enter(\"status\", \"worktrees\", s->repo);\n+\twt_status_collect_changes_worktree(s);\n+\ttrace2_region_leave(\"status\", \"worktrees\", s->repo);\n+\n+\ttrace2_region_enter(\"status\", \"index\", s->repo);\n+\twt_status_collect_changes_index(s);\n+\ttrace2_region_leave(\"status\", \"index\", s->repo);\n+\n+\ttrace2_region_enter(\"status\", \"untracked\", s->repo);\n+\twt_status_collect_untracked(s);\n+\ttrace2_region_leave(\"status\", \"untracked\", s->repo);\n+}\n+\n+void wt_status_print(struct wt_status *s)\n+{\n+\ttrace2_region_enter(\"status\", \"print\", s->repo);\n+\tswitch (s->status_format) {\n+\t    ...\n+\t}\n+\ttrace2_region_leave(\"status\", \"print\", s->repo);\n+}\n+----------------\n++\n+In this example, scanning for untracked files ran from +0.012568 to\n++0.027149 (since the process started) and took 0.014581 seconds.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+\n+$ cat ~/log.perf\n+d0 | main                     | version      |     |           |           |            | 2.20.1.160.g5676107ecd.dirty\n+d0 | main                     | start        |     |           |           |            | git status\n+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n+d0 | main                     | cmd_name     |     |           |           |            | status (status)\n+...\n+d0 | main                     | region_enter | r1  |  0.010988 |           | status     | label:worktrees\n+d0 | main                     | region_leave | r1  |  0.011236 |  0.000248 | status     | label:worktrees\n+d0 | main                     | region_enter | r1  |  0.011260 |           | status     | label:index\n+d0 | main                     | region_leave | r1  |  0.012542 |  0.001282 | status     | label:index\n+d0 | main                     | region_enter | r1  |  0.012568 |           | status     | label:untracked\n+d0 | main                     | region_leave | r1  |  0.027149 |  0.014581 | status     | label:untracked\n+d0 | main                     | region_enter | r1  |  0.027411 |           | status     | label:print\n+d0 | main                     | region_leave | r1  |  0.028741 |  0.001330 | status     | label:print\n+d0 | main                     | exit         |     |  0.028778 |           |            | code:0\n+d0 | main                     | atexit       |     |  0.028809 |           |            | code:0\n+----------------\n++\n+Regions may be nested.  This causes messages to be indented in the\n+PERF target, for example.\n+Elapsed times are relative to the start of the correpsonding nesting\n+level as expected.  For example, if we add region message to:\n++\n+----------------\n+static enum path_treatment read_directory_recursive(struct dir_struct *dir,\n+\tstruct index_state *istate, const char *base, int baselen,\n+\tstruct untracked_cache_dir *untracked, int check_only,\n+\tint stop_at_first_file, const struct pathspec *pathspec)\n+{\n+\tenum path_treatment state, subdir_state, dir_state = path_none;\n+\n+\ttrace2_region_enter_printf(\"dir\", \"read_recursive\", NULL, \"%.*s\", baselen, base);\n+\t...\n+\ttrace2_region_leave_printf(\"dir\", \"read_recursive\", NULL, \"%.*s\", baselen, base);\n+\treturn dir_state;\n+}\n+----------------\n++\n+We can further investigate the time spent scanning for untracked files.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+$ cat ~/log.perf\n+d0 | main                     | version      |     |           |           |            | 2.20.1.162.gb4ccea44db.dirty\n+d0 | main                     | start        |     |           |           |            | git status\n+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n+d0 | main                     | cmd_name     |     |           |           |            | status (status)\n+...\n+d0 | main                     | region_enter | r1  |  0.015047 |           | status     | label:untracked\n+d0 | main                     | region_enter |     |  0.015132 |           | dir        | ..label:read_recursive\n+d0 | main                     | region_enter |     |  0.016341 |           | dir        | ....label:read_recursive vcs-svn/\n+d0 | main                     | region_leave |     |  0.016422 |  0.000081 | dir        | ....label:read_recursive vcs-svn/\n+d0 | main                     | region_enter |     |  0.016446 |           | dir        | ....label:read_recursive xdiff/\n+d0 | main                     | region_leave |     |  0.016522 |  0.000076 | dir        | ....label:read_recursive xdiff/\n+d0 | main                     | region_enter |     |  0.016612 |           | dir        | ....label:read_recursive git-gui/\n+d0 | main                     | region_enter |     |  0.016698 |           | dir        | ......label:read_recursive git-gui/po/\n+d0 | main                     | region_enter |     |  0.016810 |           | dir        | ........label:read_recursive git-gui/po/glossary/\n+d0 | main                     | region_leave |     |  0.016863 |  0.000053 | dir        | ........label:read_recursive git-gui/po/glossary/\n+...\n+d0 | main                     | region_enter |     |  0.031876 |           | dir        | ....label:read_recursive builtin/\n+d0 | main                     | region_leave |     |  0.032270 |  0.000394 | dir        | ....label:read_recursive builtin/\n+d0 | main                     | region_leave |     |  0.032414 |  0.017282 | dir        | ..label:read_recursive\n+d0 | main                     | region_leave | r1  |  0.032454 |  0.017407 | status     | label:untracked\n+...\n+d0 | main                     | exit         |     |  0.034279 |           |            | code:0\n+d0 | main                     | atexit       |     |  0.034322 |           |            | code:0\n+----------------\n++\n+Trace2 regions are similar to the existing trace_performance_enter()\n+and trace_performance_leave() routines, but are thread safe and\n+maintain per-thread stacks of timers.\n+\n+Data Messages::\n+\n+\tData messages added to a region.\n++\n+----------------\n+int read_index_from(struct index_state *istate, const char *path,\n+\tconst char *gitdir)\n+{\n+\ttrace2_region_enter_printf(\"index\", \"do_read_index\", the_repository, \"%s\", path);\n+\n+\t...\n+\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/version\", istate->version);\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/cache_nr\", istate->cache_nr);\n+\n+\ttrace2_region_leave_printf(\"index\", \"do_read_index\", the_repository, \"%s\", path);\n+}\n+----------------\n++\n+This example shows that the index contained 3552 entries.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+$ cat ~/log.perf\n+d0 | main                     | version      |     |           |           |            | 2.20.1.156.gf9916ae094.dirty\n+d0 | main                     | start        |     |           |           |            | git status\n+d0 | main                     | def_repo     | r1  |           |           |            | worktree:/Users/jeffhost/work/gfw\n+d0 | main                     | cmd_name     |     |           |           |            | status (status)\n+d0 | main                     | region_enter | r1  |  0.001791 |           | index      | label:do_read_index .git/index\n+d0 | main                     | data         | r1  |  0.002494 |  0.000703 | index      | ..read/version:2\n+d0 | main                     | data         | r1  |  0.002520 |  0.000729 | index      | ..read/cache_nr:3552\n+d0 | main                     | region_leave | r1  |  0.002539 |  0.000748 | index      | label:do_read_index .git/index\n+...\n+----------------\n+\n+Thread Events::\n+\n+\tThread messages added to a thread-proc.\n++\n+For example, the multithreaded preload-index code can be\n+instrumented with a region around the thread pool and then\n+per-thread start and exit events within the threadproc.\n++\n+----------------\n+static void *preload_thread(void *_data)\n+{\n+\t// start the per-thread clock and emit a message.\n+\ttrace2_thread_start(\"preload_thread\");\n+\n+\t// report which chunk of the array this thread was assigned.\n+\ttrace2_data_intmax(\"index\", the_repository, \"offset\", p->offset);\n+\ttrace2_data_intmax(\"index\", the_repository, \"count\", nr);\n+\n+\tdo {\n+\t    ...\n+\t} while (--nr > 0);\n+\t...\n+\n+\t// report elapsed time taken by this thread.\n+\ttrace2_thread_exit();\n+\treturn NULL;\n+}\n+\n+void preload_index(struct index_state *index,\n+\tconst struct pathspec *pathspec,\n+\tunsigned int refresh_flags)\n+{\n+\ttrace2_region_enter(\"index\", \"preload\", the_repository);\n+\n+\tfor (i = 0; i < threads; i++) {\n+\t    ... /* create thread */\n+\t}\n+\n+\tfor (i = 0; i < threads; i++) {\n+\t    ... /* join thread */\n+\t}\n+\n+\ttrace2_region_leave(\"index\", \"preload\", the_repository);\n+}\n+----------------\n++\n+In this example preload_index() was executed by the `main` thread\n+and started the `preload` region.  Seven threads, named\n+`th01:preload_thread` through `th07:preload_thread`, were started.\n+Events from each thread are atomically appended to the shared target\n+stream as they occur so they may appear in random order with respect\n+other threads. Finally, the main thread waits for the threads to\n+finish and leaves the region.\n++\n+Data events are tagged with the active thread name.  They are used\n+to report the per-thread parameters.\n++\n+----------------\n+$ export GIT_TR2_PERF_BRIEF=1\n+$ export GIT_TR2_PERF=~/log.perf\n+$ git status\n+...\n+$ cat ~/log.perf\n+...\n+d0 | main                     | region_enter | r1  |  0.002595 |           | index      | label:preload\n+d0 | th01:preload_thread      | thread_start |     |  0.002699 |           |            |\n+d0 | th02:preload_thread      | thread_start |     |  0.002721 |           |            |\n+d0 | th01:preload_thread      | data         | r1  |  0.002736 |  0.000037 | index      | offset:0\n+d0 | th02:preload_thread      | data         | r1  |  0.002751 |  0.000030 | index      | offset:2032\n+d0 | th03:preload_thread      | thread_start |     |  0.002711 |           |            |\n+d0 | th06:preload_thread      | thread_start |     |  0.002739 |           |            |\n+d0 | th01:preload_thread      | data         | r1  |  0.002766 |  0.000067 | index      | count:508\n+d0 | th06:preload_thread      | data         | r1  |  0.002856 |  0.000117 | index      | offset:2540\n+d0 | th03:preload_thread      | data         | r1  |  0.002824 |  0.000113 | index      | offset:1016\n+d0 | th04:preload_thread      | thread_start |     |  0.002710 |           |            |\n+d0 | th02:preload_thread      | data         | r1  |  0.002779 |  0.000058 | index      | count:508\n+d0 | th06:preload_thread      | data         | r1  |  0.002966 |  0.000227 | index      | count:508\n+d0 | th07:preload_thread      | thread_start |     |  0.002741 |           |            |\n+d0 | th07:preload_thread      | data         | r1  |  0.003017 |  0.000276 | index      | offset:3048\n+d0 | th05:preload_thread      | thread_start |     |  0.002712 |           |            |\n+d0 | th05:preload_thread      | data         | r1  |  0.003067 |  0.000355 | index      | offset:1524\n+d0 | th05:preload_thread      | data         | r1  |  0.003090 |  0.000378 | index      | count:508\n+d0 | th07:preload_thread      | data         | r1  |  0.003037 |  0.000296 | index      | count:504\n+d0 | th03:preload_thread      | data         | r1  |  0.002971 |  0.000260 | index      | count:508\n+d0 | th04:preload_thread      | data         | r1  |  0.002983 |  0.000273 | index      | offset:508\n+d0 | th04:preload_thread      | data         | r1  |  0.007311 |  0.004601 | index      | count:508\n+d0 | th05:preload_thread      | thread_exit  |     |  0.008781 |  0.006069 |            |\n+d0 | th01:preload_thread      | thread_exit  |     |  0.009561 |  0.006862 |            |\n+d0 | th03:preload_thread      | thread_exit  |     |  0.009742 |  0.007031 |            |\n+d0 | th06:preload_thread      | thread_exit  |     |  0.009820 |  0.007081 |            |\n+d0 | th02:preload_thread      | thread_exit  |     |  0.010274 |  0.007553 |            |\n+d0 | th07:preload_thread      | thread_exit  |     |  0.010477 |  0.007736 |            |\n+d0 | th04:preload_thread      | thread_exit  |     |  0.011657 |  0.008947 |            |\n+d0 | main                     | region_leave | r1  |  0.011717 |  0.009122 | index      | label:preload\n+...\n+d0 | main                     | exit         |     |  0.029996 |           |            | code:0\n+d0 | main                     | atexit       |     |  0.030027 |           |            | code:0\n+----------------\n++\n+In this example, the preload region took 0.009122 seconds.  The 7 threads\n+took between 0.006069 and 0.008947 seconds to work on their portion of\n+the index.  Thread \"th01\" worked on 508 items at offset 0.  Thread \"th02\"\n+worked on 508 items at offset 2032.  Thread \"th04\" worked on 508 itemts\n+at offset 508.\n++\n+This example also shows that thread names are assigned in a racy manner\n+as each thread starts and allocates TLS storage.\n+\n+== Future Work\n+\n+=== Relationship to the Existing Trace Api (api-trace.txt)\n+\n+There are a few issues to resolve before we can completely\n+switch to Trace2.\n+\n+* Updating existing tests that assume GIT_TRACE format messages.\n+\n+* How to best handle custom GIT_TRACE_<key> messages?\n+\n+** The GIT_TRACE_<key> mechanism allows each <key> to write to a\n+different file (in addition to just stderr).\n+\n+** Do we want to maintain that ability or simply write to the existing\n+Trace2 targets (and convert <key> to a \"category\").\n+\n-- \ngitgitgadget\n\n"},{"id":"369987","messageId":"485a4f2a68538aa3fd75ff113585ff543e49b31a.1550874298.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v7.git.gitgitgadget@gmail.com","subject":"[PATCH v7 07/15] trace2:data: add trace2 transport child classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-22T22:25:05Z","receivedAt":"2019-02-22T22:25:10Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 child classification for transport processes.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n connect.c          | 3 +++\n transport-helper.c | 2 ++\n 2 files changed, 5 insertions(+)\n\ndiff --git a/connect.c b/connect.c\nindex 24281b6082..3c6f829a05 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -1251,6 +1251,7 @@ struct child_process *git_connect(int fd[2], const char *url,\n \t\tconn = NULL;\n \t} else if (protocol == PROTO_GIT) {\n \t\tconn = git_connect_git(fd, hostandport, path, prog, version, flags);\n+\t\tconn->trace2_child_class = \"transport/git\";\n \t} else {\n \t\tstruct strbuf cmd = STRBUF_INIT;\n \t\tconst char *const *var;\n@@ -1293,9 +1294,11 @@ struct child_process *git_connect(int fd[2], const char *url,\n \t\t\t\tstrbuf_release(&cmd);\n \t\t\t\treturn NULL;\n \t\t\t}\n+\t\t\tconn->trace2_child_class = \"transport/ssh\";\n \t\t\tfill_ssh_args(conn, ssh_host, port, version, flags);\n \t\t} else {\n \t\t\ttransport_check_allowed(\"file\");\n+\t\t\tconn->trace2_child_class = \"transport/file\";\n \t\t\tif (version > 0) {\n \t\t\t\targv_array_pushf(&conn->env_array, GIT_PROTOCOL_ENVIRONMENT \"=version=%d\",\n \t\t\t\t\t\t version);\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 6cf3bb324e..a01cc0093f 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -127,6 +127,8 @@ static struct child_process *get_helper(struct transport *transport)\n \t\targv_array_pushf(&helper->env_array, \"%s=%s\",\n \t\t\t\t GIT_DIR_ENVIRONMENT, get_git_dir());\n \n+\thelper->trace2_child_class = helper->args.argv[0]; /* \"remote-<name>\" */\n+\n \tcode = start_command(helper);\n \tif (code < 0 && errno == ENOENT)\n \t\tdie(_(\"unable to find remote helper for '%s'\"), data->name);\n-- \ngitgitgadget\n\n"},{"id":"369986","messageId":"18659d61da6aaa0df834f0547b6363c98c0a58b8.1550874298.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v7.git.gitgitgadget@gmail.com","subject":"[PATCH v7 08/15] trace2:data: add trace2 hook classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-22T22:25:06Z","receivedAt":"2019-02-22T22:25:11Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nClassify certain child processes as hooks.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/am.c           | 1 +\n builtin/receive-pack.c | 4 ++++\n builtin/worktree.c     | 1 +\n sequencer.c            | 2 ++\n transport.c            | 1 +\n 5 files changed, 9 insertions(+)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 95370313b6..a81e61c1bc 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -468,6 +468,7 @@ static int run_post_rewrite_hook(const struct am_state *state)\n \n \tcp.in = xopen(am_path(state, \"rewritten\"), O_RDONLY);\n \tcp.stdout_to_stderr = 1;\n+\tcp.trace2_hook_name = \"post-rewrite\";\n \n \tret = run_command(&cp);\n \ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 33187bd8e9..6eef8575b9 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -694,6 +694,8 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,\n \tproc.argv = argv;\n \tproc.in = -1;\n \tproc.stdout_to_stderr = 1;\n+\tproc.trace2_hook_name = hook_name;\n+\n \tif (feed_state->push_options) {\n \t\tint i;\n \t\tfor (i = 0; i < feed_state->push_options->nr; i++)\n@@ -807,6 +809,7 @@ static int run_update_hook(struct command *cmd)\n \tproc.stdout_to_stderr = 1;\n \tproc.err = use_sideband ? -1 : 0;\n \tproc.argv = argv;\n+\tproc.trace2_hook_name = \"update\";\n \n \tcode = start_command(&proc);\n \tif (code)\n@@ -1190,6 +1193,7 @@ static void run_update_post_hook(struct command *commands)\n \tproc.no_stdin = 1;\n \tproc.stdout_to_stderr = 1;\n \tproc.err = use_sideband ? -1 : 0;\n+\tproc.trace2_hook_name = \"post-update\";\n \n \tif (!start_command(&proc)) {\n \t\tif (use_sideband)\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 3f9907fcc9..6cc094a453 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -402,6 +402,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\t\tcp.dir = path;\n \t\t\tcp.env = env;\n \t\t\tcp.argv = NULL;\n+\t\t\tcp.trace2_hook_name = \"post-checkout\";\n \t\t\targv_array_pushl(&cp.args, absolute_path(hook),\n \t\t\t\t\t oid_to_hex(&null_oid),\n \t\t\t\t\t oid_to_hex(&commit->object.oid),\ndiff --git a/sequencer.c b/sequencer.c\nindex 213815dbfc..c1e36196a1 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1103,6 +1103,7 @@ static int run_rewrite_hook(const struct object_id *oldoid,\n \tproc.argv = argv;\n \tproc.in = -1;\n \tproc.stdout_to_stderr = 1;\n+\tproc.trace2_hook_name = \"post-rewrite\";\n \n \tcode = start_command(&proc);\n \tif (code)\n@@ -3784,6 +3785,7 @@ static int pick_commits(struct repository *r,\n \t\t\t\thook.in = open(rebase_path_rewritten_list(),\n \t\t\t\t\tO_RDONLY);\n \t\t\t\thook.stdout_to_stderr = 1;\n+\t\t\t\thook.trace2_hook_name = \"post-rewrite\";\n \t\t\t\targv_array_push(&hook.args, post_rewrite_hook);\n \t\t\t\targv_array_push(&hook.args, \"rebase\");\n \t\t\t\t/* we don't care if this hook failed */\ndiff --git a/transport.c b/transport.c\nindex 99678153c1..ed5a733c4a 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1061,6 +1061,7 @@ static int run_pre_push_hook(struct transport *transport,\n \n \tproc.argv = argv;\n \tproc.in = -1;\n+\tproc.trace2_hook_name = \"pre-push\";\n \n \tif (start_command(&proc)) {\n \t\tfinish_command(&proc);\n-- \ngitgitgadget\n\n"},{"id":"369988","messageId":"2a5aa3a8dc7e580938a72b28f322421f9b1d861e.1550874298.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v7.git.gitgitgadget@gmail.com","subject":"[PATCH v7 11/15] trace2:data: add subverb to checkout command","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-22T22:25:08Z","receivedAt":"2019-02-22T22:25:13Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/checkout.c | 7 +++++++\n 1 file changed, 7 insertions(+)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 6fadf412e8..f911c88bb4 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -262,6 +262,8 @@ static int checkout_paths(const struct checkout_opts *opts,\n \tstruct lock_file lock_file = LOCK_INIT;\n \tint nr_checkouts = 0;\n \n+\ttrace2_cmd_mode(opts->patch_mode ? \"patch\" : \"path\");\n+\n \tif (opts->track != BRANCH_TRACK_UNSPECIFIED)\n \t\tdie(_(\"'%s' cannot be used with updating paths\"), \"--track\");\n \n@@ -952,6 +954,9 @@ static int switch_branches(const struct checkout_opts *opts,\n \tvoid *path_to_free;\n \tstruct object_id rev;\n \tint flag, writeout_error = 0;\n+\n+\ttrace2_cmd_mode(\"branch\");\n+\n \tmemset(&old_branch_info, 0, sizeof(old_branch_info));\n \told_branch_info.path = path_to_free = resolve_refdup(\"HEAD\", 0, &rev, &flag);\n \tif (old_branch_info.path)\n@@ -1189,6 +1194,8 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)\n \tint status;\n \tstruct strbuf branch_ref = STRBUF_INIT;\n \n+\ttrace2_cmd_mode(\"unborn\");\n+\n \tif (!opts->new_branch)\n \t\tdie(_(\"You are on a branch yet to be born\"));\n \tstrbuf_addf(&branch_ref, \"refs/heads/%s\", opts->new_branch);\n-- \ngitgitgadget\n\n"},{"id":"369989","messageId":"3e6fee40b07b29eb62c96c08bae4a615d247de85.1550874298.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v7.git.gitgitgadget@gmail.com","subject":"[PATCH v7 13/15] trace2:data: add subverb for rebase","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-22T22:25:10Z","receivedAt":"2019-02-22T22:25:14Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/rebase.c | 17 +++++++++++++++++\n 1 file changed, 17 insertions(+)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 774264bae8..f5ac4fe2ea 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -850,6 +850,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tACTION_EDIT_TODO,\n \t\tACTION_SHOW_CURRENT_PATCH,\n \t} action = NO_ACTION;\n+\tstatic const char *action_names[] = { N_(\"undefined\"),\n+\t\t\t\t\t      N_(\"continue\"),\n+\t\t\t\t\t      N_(\"skip\"),\n+\t\t\t\t\t      N_(\"abort\"),\n+\t\t\t\t\t      N_(\"quit\"),\n+\t\t\t\t\t      N_(\"edit_todo\"),\n+\t\t\t\t\t      N_(\"show_current_patch\"),\n+\t\t\t\t\t      NULL };\n \tconst char *gpg_sign = NULL;\n \tstruct string_list exec = STRING_LIST_INIT_NODUP;\n \tconst char *rebase_merges = NULL;\n@@ -1039,6 +1047,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tdie(_(\"The --edit-todo action can only be used during \"\n \t\t      \"interactive rebase.\"));\n \n+\tif (trace2_is_enabled()) {\n+\t\tif (is_interactive(&options))\n+\t\t\ttrace2_cmd_mode(\"interactive\");\n+\t\telse if (exec.nr)\n+\t\t\ttrace2_cmd_mode(\"interactive-exec\");\n+\t\telse\n+\t\t\ttrace2_cmd_mode(action_names[action]);\n+\t}\n+\n \tswitch (action) {\n \tcase ACTION_CONTINUE: {\n \t\tstruct object_id head;\n-- \ngitgitgadget\n\n"},{"id":"369990","messageId":"662201f49e2da012422b8a70d39b24e387c8fc7d.1550874298.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v7.git.gitgitgadget@gmail.com","subject":"[PATCH v7 15/15] trace2: add for_each macros to clang-format","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-22T22:25:11Z","receivedAt":"2019-02-22T22:25:16Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n .clang-format | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/.clang-format b/.clang-format\nindex de1c8b5c77..41d4cd23fd 100644\n--- a/.clang-format\n+++ b/.clang-format\n@@ -149,7 +149,7 @@ Cpp11BracedListStyle: false\n \n # A list of macros that should be interpreted as foreach loops instead of as\n # function calls.\n-ForEachMacros: ['for_each_string_list_item']\n+ForEachMacros: ['for_each_string_list_item', 'for_each_wanted_builtin', 'for_each_builtin', 'for_each_ut']\n \n # The maximum number of consecutive empty lines to keep.\n MaxEmptyLinesToKeep: 1\n-- \ngitgitgadget\n"},{"id":"369991","messageId":"08be5aadbef7f6b404d2e1c47cf3657ced585ba1.1550874298.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v7.git.gitgitgadget@gmail.com","subject":"[PATCH v7 09/15] trace2:data: add trace2 instrumentation to index read/write","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-22T22:25:07Z","receivedAt":"2019-02-22T22:25:16Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 events to measure reading and writing the index.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n read-cache.c | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 50 insertions(+), 1 deletion(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex bfff271a3d..240531d70d 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -2232,6 +2232,16 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)\n \t\tload_index_extensions(&p);\n \t}\n \tmunmap((void *)mmap, mmap_size);\n+\n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/version\",\n+\t\t\t   istate->version);\n+\ttrace2_data_intmax(\"index\", the_repository, \"read/cache_nr\",\n+\t\t\t   istate->cache_nr);\n+\n \treturn istate->cache_nr;\n \n unmap:\n@@ -2263,9 +2273,17 @@ int read_index_from(struct index_state *istate, const char *path,\n \tif (istate->initialized)\n \t\treturn istate->cache_nr;\n \n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_region_enter_printf(\"index\", \"do_read_index\", the_repository,\n+\t\t\t\t   \"%s\", path);\n \ttrace_performance_enter();\n \tret = do_read_index(istate, path, 0);\n \ttrace_performance_leave(\"read cache %s\", path);\n+\ttrace2_region_leave_printf(\"index\", \"do_read_index\", the_repository,\n+\t\t\t\t   \"%s\", path);\n \n \tsplit_index = istate->split_index;\n \tif (!split_index || is_null_oid(&split_index->base_oid)) {\n@@ -2281,7 +2299,11 @@ int read_index_from(struct index_state *istate, const char *path,\n \n \tbase_oid_hex = oid_to_hex(&split_index->base_oid);\n \tbase_path = xstrfmt(\"%s/sharedindex.%s\", gitdir, base_oid_hex);\n+\ttrace2_region_enter_printf(\"index\", \"shared/do_read_index\",\n+\t\t\t\t   the_repository, \"%s\", base_path);\n \tret = do_read_index(split_index->base, base_path, 1);\n+\ttrace2_region_leave_printf(\"index\", \"shared/do_read_index\",\n+\t\t\t\t   the_repository, \"%s\", base_path);\n \tif (!oideq(&split_index->base_oid, &split_index->base->oid))\n \t\tdie(_(\"broken index, expect %s in %s, got %s\"),\n \t\t    base_oid_hex, base_path,\n@@ -2988,6 +3010,16 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,\n \tistate->timestamp.sec = (unsigned int)st.st_mtime;\n \tistate->timestamp.nsec = ST_MTIME_NSEC(st);\n \ttrace_performance_since(start, \"write index, changed mask = %x\", istate->cache_changed);\n+\n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_data_intmax(\"index\", the_repository, \"write/version\",\n+\t\t\t   istate->version);\n+\ttrace2_data_intmax(\"index\", the_repository, \"write/cache_nr\",\n+\t\t\t   istate->cache_nr);\n+\n \treturn 0;\n }\n \n@@ -3007,7 +3039,18 @@ static int commit_locked_index(struct lock_file *lk)\n static int do_write_locked_index(struct index_state *istate, struct lock_file *lock,\n \t\t\t\t unsigned flags)\n {\n-\tint ret = do_write_index(istate, lock->tempfile, 0);\n+\tint ret;\n+\n+\t/*\n+\t * TODO trace2: replace \"the_repository\" with the actual repo instance\n+\t * that is associated with the given \"istate\".\n+\t */\n+\ttrace2_region_enter_printf(\"index\", \"do_write_index\", the_repository,\n+\t\t\t\t   \"%s\", lock->tempfile->filename.buf);\n+\tret = do_write_index(istate, lock->tempfile, 0);\n+\ttrace2_region_leave_printf(\"index\", \"do_write_index\", the_repository,\n+\t\t\t\t   \"%s\", lock->tempfile->filename.buf);\n+\n \tif (ret)\n \t\treturn ret;\n \tif (flags & COMMIT_LOCK)\n@@ -3092,7 +3135,13 @@ static int write_shared_index(struct index_state *istate,\n \tint ret;\n \n \tmove_cache_to_base_index(istate);\n+\n+\ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n+\t\t\t\t   the_repository, \"%s\", (*temp)->filename.buf);\n \tret = do_write_index(si->base, *temp, 1);\n+\ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n+\t\t\t\t   the_repository, \"%s\", (*temp)->filename.buf);\n+\n \tif (ret)\n \t\treturn ret;\n \tret = adjust_shared_perm(get_tempfile_path(*temp));\n-- \ngitgitgadget\n\n"},{"id":"369992","messageId":"398edb7738b4fe98318564f9c0bcfb9179ed50b7.1550874298.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v7.git.gitgitgadget@gmail.com","subject":"[PATCH v7 14/15] trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-22T22:25:10Z","receivedAt":"2019-02-22T22:25:18Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate unit tests for Trace2.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                  |   1 +\n t/helper/test-tool.c      |   1 +\n t/helper/test-tool.h      |   1 +\n t/helper/test-trace2.c    | 273 ++++++++++++++++++++++++++++++++++++++\n t/t0210-trace2-normal.sh  | 135 +++++++++++++++++++\n t/t0210/scrub_normal.perl |  48 +++++++\n t/t0211-trace2-perf.sh    | 153 +++++++++++++++++++++\n t/t0211/scrub_perf.perl   |  76 +++++++++++\n t/t0212-trace2-event.sh   | 236 ++++++++++++++++++++++++++++++++\n t/t0212/parse_events.perl | 251 +++++++++++++++++++++++++++++++++++\n 10 files changed, 1175 insertions(+)\n create mode 100644 t/helper/test-trace2.c\n create mode 100755 t/t0210-trace2-normal.sh\n create mode 100644 t/t0210/scrub_normal.perl\n create mode 100755 t/t0211-trace2-perf.sh\n create mode 100644 t/t0211/scrub_perf.perl\n create mode 100755 t/t0212-trace2-event.sh\n create mode 100644 t/t0212/parse_events.perl\n\ndiff --git a/Makefile b/Makefile\nindex 2ab7b73000..040f5846d2 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -762,6 +762,7 @@ TEST_BUILTINS_OBJS += test-string-list.o\n TEST_BUILTINS_OBJS += test-submodule-config.o\n TEST_BUILTINS_OBJS += test-submodule-nested-repo-config.o\n TEST_BUILTINS_OBJS += test-subprocess.o\n+TEST_BUILTINS_OBJS += test-trace2.o\n TEST_BUILTINS_OBJS += test-urlmatch-normalization.o\n TEST_BUILTINS_OBJS += test-wildmatch.o\n TEST_BUILTINS_OBJS += test-windows-named-pipe.o\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 4dbfff7dee..25b1294353 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -51,6 +51,7 @@ static struct test_cmd cmds[] = {\n \t{ \"submodule-config\", cmd__submodule_config },\n \t{ \"submodule-nested-repo-config\", cmd__submodule_nested_repo_config },\n \t{ \"subprocess\", cmd__subprocess },\n+\t{ \"trace2\", cmd__trace2 },\n \t{ \"urlmatch-normalization\", cmd__urlmatch_normalization },\n \t{ \"wildmatch\", cmd__wildmatch },\n #ifdef GIT_WINDOWS_NATIVE\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex ca5c88edb2..61d2938ce5 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -46,6 +46,7 @@ int cmd__string_list(int argc, const char **argv);\n int cmd__submodule_config(int argc, const char **argv);\n int cmd__submodule_nested_repo_config(int argc, const char **argv);\n int cmd__subprocess(int argc, const char **argv);\n+int cmd__trace2(int argc, const char **argv);\n int cmd__urlmatch_normalization(int argc, const char **argv);\n int cmd__wildmatch(int argc, const char **argv);\n #ifdef GIT_WINDOWS_NATIVE\ndiff --git a/t/helper/test-trace2.c b/t/helper/test-trace2.c\nnew file mode 100644\nindex 0000000000..197819c872\n--- /dev/null\n+++ b/t/helper/test-trace2.c\n@@ -0,0 +1,273 @@\n+#include \"test-tool.h\"\n+#include \"cache.h\"\n+#include \"argv-array.h\"\n+#include \"run-command.h\"\n+#include \"exec-cmd.h\"\n+#include \"config.h\"\n+\n+typedef int(fn_unit_test)(int argc, const char **argv);\n+\n+struct unit_test {\n+\tfn_unit_test *ut_fn;\n+\tconst char *ut_name;\n+\tconst char *ut_usage;\n+};\n+\n+#define MyOk 0\n+#define MyError 1\n+\n+static int get_i(int *p_value, const char *data)\n+{\n+\tchar *endptr;\n+\n+\tif (!data || !*data)\n+\t\treturn MyError;\n+\n+\t*p_value = strtol(data, &endptr, 10);\n+\tif (*endptr || errno == ERANGE)\n+\t\treturn MyError;\n+\n+\treturn MyOk;\n+}\n+\n+/*\n+ * Cause process to exit with the requested value via \"return\".\n+ *\n+ * Rely on test-tool.c:cmd_main() to call trace2_cmd_exit()\n+ * with our result.\n+ *\n+ * Test harness can confirm:\n+ * [] the process-exit value.\n+ * [] the \"code\" field in the \"exit\" trace2 event.\n+ * [] the \"code\" field in the \"atexit\" trace2 event.\n+ * [] the \"name\" field in the \"cmd_name\" trace2 event.\n+ * [] \"def_param\" events for all of the \"interesting\" pre-defined\n+ * config settings.\n+ */\n+static int ut_001return(int argc, const char **argv)\n+{\n+\tint rc;\n+\n+\tif (get_i(&rc, argv[0]))\n+\t\tdie(\"expect <exit_code>\");\n+\n+\treturn rc;\n+}\n+\n+/*\n+ * Cause the process to exit with the requested value via \"exit()\".\n+ *\n+ * Test harness can confirm:\n+ * [] the \"code\" field in the \"exit\" trace2 event.\n+ * [] the \"code\" field in the \"atexit\" trace2 event.\n+ * [] the \"name\" field in the \"cmd_name\" trace2 event.\n+ * [] \"def_param\" events for all of the \"interesting\" pre-defined\n+ * config settings.\n+ */\n+static int ut_002exit(int argc, const char **argv)\n+{\n+\tint rc;\n+\n+\tif (get_i(&rc, argv[0]))\n+\t\tdie(\"expect <exit_code>\");\n+\n+\texit(rc);\n+}\n+\n+/*\n+ * Send an \"error\" event with each value in argv.  Normally, git only issues\n+ * a single \"error\" event immediately before issuing an \"exit\" event (such\n+ * as in die() or BUG()), but multiple \"error\" events are allowed.\n+ *\n+ * Test harness can confirm:\n+ * [] a trace2 \"error\" event for each value in argv.\n+ * [] the \"name\" field in the \"cmd_name\" trace2 event.\n+ * [] (optional) the file:line in the \"exit\" event refers to this function.\n+ */\n+static int ut_003error(int argc, const char **argv)\n+{\n+\tint k;\n+\n+\tif (!argv[0] || !*argv[0])\n+\t\tdie(\"expect <error_message>\");\n+\n+\tfor (k = 0; k < argc; k++)\n+\t\terror(\"%s\", argv[k]);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Run a child process and wait for it to finish and exit with its return code.\n+ * test-tool trace2 004child [<child-command-line>]\n+ *\n+ * For example:\n+ * test-tool trace2 004child git version\n+ * test-tool trace2 004child test-tool trace2 001return 0\n+ * test-tool trace2 004child test-tool trace2 004child test-tool trace2 004child\n+ * test-tool trace2 004child git -c alias.xyz=version xyz\n+ *\n+ * Test harness can confirm:\n+ * [] the \"name\" field in the \"cmd_name\" trace2 event.\n+ * [] that the outer process has a single component SID (or depth \"d0\" in\n+ *    the PERF stream).\n+ * [] that \"child_start\" and \"child_exit\" events are generated for the child.\n+ * [] if the child process is an instrumented executable:\n+ *    [] that \"version\", \"start\", ..., \"exit\", and \"atexit\" events are\n+ *       generated by the child process.\n+ *    [] that the child process events have a multiple component SID (or\n+ *       depth \"dN+1\" in the PERF stream).\n+ * [] that the child exit code is propagated to the parent process \"exit\"\n+ *    and \"atexit\" events..\n+ * [] (optional) that the \"t_abs\" field in the child process \"atexit\" event\n+ *    is less than the \"t_rel\" field in the \"child_exit\" event of the parent\n+ *    process.\n+ * [] if the child process is like the alias example above,\n+ *    [] (optional) the child process attempts to run \"git-xyx\" as a dashed\n+ *       command.\n+ *    [] the child process emits an \"alias\" event with \"xyz\" => \"version\"\n+ *    [] the child process runs \"git version\" as a child process.\n+ *    [] the child process has a 3 component SID (or depth \"d2\" in the PERF\n+ *       stream).\n+ */\n+static int ut_004child(int argc, const char **argv)\n+{\n+\tint result;\n+\n+\t/*\n+\t * Allow empty <child_command_line> so we can do arbitrarily deep\n+\t * command nesting and let the last one be null.\n+\t */\n+\tif (!argc)\n+\t\treturn 0;\n+\n+\tresult = run_command_v_opt(argv, 0);\n+\texit(result);\n+}\n+\n+/*\n+ * Exec a git command.  This may either create a child process (Windows)\n+ * or replace the existing process.\n+ * test-tool trace2 005exec <git_command_args>\n+ *\n+ * For example:\n+ * test-tool trace2 005exec version\n+ *\n+ * Test harness can confirm (on Windows):\n+ * [] the \"name\" field in the \"cmd_name\" trace2 event.\n+ * [] that the outer process has a single component SID (or depth \"d0\" in\n+ *    the PERF stream).\n+ * [] that \"exec\" and \"exec_result\" events are generated for the child\n+ *    process (since the Windows compatibility layer fakes an exec() with\n+ *    a CreateProcess(), WaitForSingleObject(), and exit()).\n+ * [] that the child process has multiple component SID (or depth \"dN+1\"\n+ *    in the PERF stream).\n+ *\n+ * Test harness can confirm (on platforms with a real exec() function):\n+ * [] TODO talk about process replacement and how it affects SID.\n+ */\n+static int ut_005exec(int argc, const char **argv)\n+{\n+\tint result;\n+\n+\tif (!argc)\n+\t\treturn 0;\n+\n+\tresult = execv_git_cmd(argv);\n+\treturn result;\n+}\n+\n+static int ut_006data(int argc, const char **argv)\n+{\n+\tconst char *usage_error =\n+\t\t\"expect <cat0> <k0> <v0> [<cat1> <k1> <v1> [...]]\";\n+\n+\tif (argc % 3 != 0)\n+\t\tdie(\"%s\", usage_error);\n+\n+\twhile (argc) {\n+\t\tif (!argv[0] || !*argv[0] || !argv[1] || !*argv[1] ||\n+\t\t    !argv[2] || !*argv[2])\n+\t\t\tdie(\"%s\", usage_error);\n+\n+\t\ttrace2_data_string(argv[0], the_repository, argv[1], argv[2]);\n+\t\targv += 3;\n+\t\targc -= 3;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Usage:\n+ *     test-tool trace2 <ut_name_1> <ut_usage_1>\n+ *     test-tool trace2 <ut_name_2> <ut_usage_2>\n+ *     ...\n+ */\n+#define USAGE_PREFIX \"test-tool trace2\"\n+\n+/* clang-format off */\n+static struct unit_test ut_table[] = {\n+\t{ ut_001return,   \"001return\", \"<exit_code>\" },\n+\t{ ut_002exit,     \"002exit\",   \"<exit_code>\" },\n+\t{ ut_003error,    \"003error\",  \"<error_message>+\" },\n+\t{ ut_004child,    \"004child\",  \"[<child_command_line>]\" },\n+\t{ ut_005exec,     \"005exec\",   \"<git_command_args>\" },\n+\t{ ut_006data,     \"006data\",   \"[<category> <key> <value>]+\" },\n+};\n+/* clang-format on */\n+\n+/* clang-format off */\n+#define for_each_ut(k, ut_k)\t\t\t\\\n+\tfor (k = 0, ut_k = &ut_table[k];\t\\\n+\t     k < ARRAY_SIZE(ut_table);\t\t\\\n+\t     k++, ut_k = &ut_table[k])\n+/* clang-format on */\n+\n+static int print_usage(void)\n+{\n+\tint k;\n+\tstruct unit_test *ut_k;\n+\n+\tfprintf(stderr, \"usage:\\n\");\n+\tfor_each_ut (k, ut_k)\n+\t\tfprintf(stderr, \"\\t%s %s %s\\n\", USAGE_PREFIX, ut_k->ut_name,\n+\t\t\tut_k->ut_usage);\n+\n+\treturn 129;\n+}\n+\n+/*\n+ * Issue various trace2 events for testing.\n+ *\n+ * We assume that these trace2 routines has already been called:\n+ *    [] trace2_initialize()      [common-main.c:main()]\n+ *    [] trace2_cmd_start()       [common-main.c:main()]\n+ *    [] trace2_cmd_name()        [test-tool.c:cmd_main()]\n+ *    [] tracd2_cmd_list_config() [test-tool.c:cmd_main()]\n+ * So that:\n+ *    [] the various trace2 streams are open.\n+ *    [] the process SID has been created.\n+ *    [] the \"version\" event has been generated.\n+ *    [] the \"start\" event has been generated.\n+ *    [] the \"cmd_name\" event has been generated.\n+ *    [] this writes various \"def_param\" events for interesting config values.\n+ *\n+ * We further assume that if we return (rather than exit()), trace2_cmd_exit()\n+ * will be called by test-tool.c:cmd_main().\n+ */\n+int cmd__trace2(int argc, const char **argv)\n+{\n+\tint k;\n+\tstruct unit_test *ut_k;\n+\n+\targc--; /* skip over \"trace2\" arg */\n+\targv++;\n+\n+\tif (argc)\n+\t\tfor_each_ut (k, ut_k)\n+\t\t\tif (!strcmp(argv[0], ut_k->ut_name))\n+\t\t\t\treturn ut_k->ut_fn(argc - 1, argv + 1);\n+\n+\treturn print_usage();\n+}\ndiff --git a/t/t0210-trace2-normal.sh b/t/t0210-trace2-normal.sh\nnew file mode 100755\nindex 0000000000..03a0aedb1d\n--- /dev/null\n+++ b/t/t0210-trace2-normal.sh\n@@ -0,0 +1,135 @@\n+#!/bin/sh\n+\n+test_description='test trace2 facility (normal target)'\n+. ./test-lib.sh\n+\n+# Add t/helper directory to PATH so that we can use a relative\n+# path to run nested instances of test-tool.exe (see 004child).\n+# This helps with HEREDOC comparisons later.\n+TTDIR=\"$GIT_BUILD_DIR/t/helper/\" && export TTDIR\n+PATH=\"$TTDIR:$PATH\" && export PATH\n+\n+# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe\n+# Warning: to do the actual diff/comparison, so the HEREDOCs here\n+# Warning: only cover our actual calls to test-tool and/or git.\n+# Warning: So you may see extra lines in artifact files when\n+# Warning: interactively debugging.\n+\n+# Turn off any inherited trace2 settings for this test.\n+unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT\n+unset GIT_TR2_BRIEF\n+unset GIT_TR2_CONFIG_PARAMS\n+\n+V=$(git version | sed -e 's/^git version //') && export V\n+\n+# There are multiple trace2 targets: normal, perf, and event.\n+# Trace2 events will/can be written to each active target (subject\n+# to whatever filtering that target decides to do).\n+# This script tests the normal target in isolation.\n+#\n+# Defer setting GIT_TR2 until the actual command line we want to test\n+# because hidden git and test-tool commands run by the test harness\n+# can contaminate our output.\n+\n+# Enable \"brief\" feature which turns off \"<clock> <file>:<line> \" prefix.\n+GIT_TR2_BRIEF=1 && export GIT_TR2_BRIEF\n+\n+# Basic tests of the trace2 normal stream.  Since this stream is used\n+# primarily with printf-style debugging/tracing, we do limited testing\n+# here.\n+#\n+# We do confirm the following API features:\n+# [] the 'version <v>' event\n+# [] the 'start <argv>' event\n+# [] the 'cmd_name <name>' event\n+# [] the 'exit <time> code:<code>' event\n+# [] the 'atexit <time> code:<code>' event\n+#\n+# Fields of the form _FIELD_ are tokens that have been replaced (such\n+# as the elapsed time).\n+\n+# Verb 001return\n+#\n+# Implicit return from cmd_<verb> function propagates <code>.\n+\n+test_expect_success 'normal stream, return code 0' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\tGIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 001return 0\n+\t\tcmd_name trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:0\n+\t\tatexit elapsed:_TIME_ code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'normal stream, return code 1' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\ttest_must_fail env GIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 001return 1 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 001return 1\n+\t\tcmd_name trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:1\n+\t\tatexit elapsed:_TIME_ code:1\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 002exit\n+#\n+# Explicit exit(code) from within cmd_<verb> propagates <code>.\n+\n+test_expect_success 'normal stream, exit code 0' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\tGIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 002exit 0 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 002exit 0\n+\t\tcmd_name trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:0\n+\t\tatexit elapsed:_TIME_ code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'normal stream, exit code 1' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\ttest_must_fail env GIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 002exit 1 &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 002exit 1\n+\t\tcmd_name trace2 (trace2)\n+\t\texit elapsed:_TIME_ code:1\n+\t\tatexit elapsed:_TIME_ code:1\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 003error\n+#\n+# To the above, add multiple 'error <msg>' events\n+\n+test_expect_success 'normal stream, error event' '\n+\ttest_when_finished \"rm trace.normal actual expect\" &&\n+\tGIT_TR2=\"$(pwd)/trace.normal\" test-tool trace2 003error \"hello world\" \"this is a test\" &&\n+\tperl \"$TEST_DIRECTORY/t0210/scrub_normal.perl\" <trace.normal >actual &&\n+\tcat >expect <<-EOF &&\n+\t\tversion $V\n+\t\tstart _EXE_ trace2 003error '\\''hello world'\\'' '\\''this is a test'\\''\n+\t\tcmd_name trace2 (trace2)\n+\t\terror hello world\n+\t\terror this is a test\n+\t\texit elapsed:_TIME_ code:0\n+\t\tatexit elapsed:_TIME_ code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t0210/scrub_normal.perl b/t/t0210/scrub_normal.perl\nnew file mode 100644\nindex 0000000000..c65d1a815e\n--- /dev/null\n+++ b/t/t0210/scrub_normal.perl\n@@ -0,0 +1,48 @@\n+#!/usr/bin/perl\n+#\n+# Scrub the variable fields from the normal trace2 output to\n+# make testing easier.\n+\n+use strict;\n+use warnings;\n+\n+my $float = '[0-9]*\\.[0-9]+([eE][-+]?[0-9]+)?';\n+\n+# This code assumes that the trace2 data was written with bare\n+# turned on (which omits the \"<clock> <file>:<line>\" prefix.\n+\n+while (<>) {\n+    # Various messages include an elapsed time in the middle\n+    # of the message.  Replace the time with a placeholder to\n+    # simplify our HEREDOC in the test script.\n+    s/elapsed:$float/elapsed:_TIME_/g;\n+\n+    my $line = $_;\n+\n+    # we expect:\n+    #    start <argv0> [<argv1> [<argv2> [...]]]\n+    #\n+    # where argv0 might be a relative or absolute path, with\n+    # or without quotes, and platform dependent.  Replace argv0\n+    # with a token for HEREDOC matching in the test script.\n+\n+    if ($line =~ m/^start/) {\n+\t$line =~ /^start\\s+(.*)/;\n+\tmy $argv = $1;\n+\t$argv =~ m/(\\'[^\\']*\\'|[^ ]+)\\s+(.*)/;\n+\tmy $argv_0 = $1;\n+\tmy $argv_rest = $2;\n+\n+\tprint \"start _EXE_ $argv_rest\\n\";\n+    }\n+    elsif ($line =~ m/^cmd_path/) {\n+\t# Likewise, the 'cmd_path' message breaks out argv[0].\n+\t#\n+\t# This line is only emitted when RUNTIME_PREFIX is defined,\n+\t# so just omit it for testing purposes.\n+\t# print \"cmd_path _EXE_\\n\";\n+    }\n+    else {\n+\tprint \"$line\";\n+    }\n+}\ndiff --git a/t/t0211-trace2-perf.sh b/t/t0211-trace2-perf.sh\nnew file mode 100755\nindex 0000000000..953e2f7847\n--- /dev/null\n+++ b/t/t0211-trace2-perf.sh\n@@ -0,0 +1,153 @@\n+#!/bin/sh\n+\n+test_description='test trace2 facility (perf target)'\n+. ./test-lib.sh\n+\n+# Add t/helper directory to PATH so that we can use a relative\n+# path to run nested instances of test-tool.exe (see 004child).\n+# This helps with HEREDOC comparisons later.\n+TTDIR=\"$GIT_BUILD_DIR/t/helper/\" && export TTDIR\n+PATH=\"$TTDIR:$PATH\" && export PATH\n+\n+# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe\n+# Warning: to do the actual diff/comparison, so the HEREDOCs here\n+# Warning: only cover our actual calls to test-tool and/or git.\n+# Warning: So you may see extra lines in artifact files when\n+# Warning: interactively debugging.\n+\n+# Turn off any inherited trace2 settings for this test.\n+unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT\n+unset GIT_TR2_PERF_BRIEF\n+unset GIT_TR2_CONFIG_PARAMS\n+\n+V=$(git version | sed -e 's/^git version //') && export V\n+\n+# There are multiple trace2 targets: normal, perf, and event.\n+# Trace2 events will/can be written to each active target (subject\n+# to whatever filtering that target decides to do).\n+# Test each target independently.\n+#\n+# Defer setting GIT_TR2_PERF until the actual command we want to\n+# test because hidden git and test-tool commands in the test\n+# harness can contaminate our output.\n+\n+# Enable \"brief\" feature which turns off the prefix:\n+#     \"<clock> <file>:<line> | <nr_parents> | \"\n+GIT_TR2_PERF_BRIEF=1 && export GIT_TR2_PERF_BRIEF\n+\n+# Repeat some of the t0210 tests using the perf target stream instead of\n+# the normal stream.\n+#\n+# Tokens here of the form _FIELD_ have been replaced in the observed output.\n+\n+# Verb 001return\n+#\n+# Implicit return from cmd_<verb> function propagates <code>.\n+\n+test_expect_success 'perf stream, return code 0' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 001return 0\n+\t\td0|main|cmd_name|||||trace2 (trace2)\n+\t\td0|main|exit||_T_ABS_|||code:0\n+\t\td0|main|atexit||_T_ABS_|||code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'perf stream, return code 1' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\ttest_must_fail env GIT_TR2_PERF=\"$(pwd)/trace.perf\" test-tool trace2 001return 1 &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 001return 1\n+\t\td0|main|cmd_name|||||trace2 (trace2)\n+\t\td0|main|exit||_T_ABS_|||code:1\n+\t\td0|main|atexit||_T_ABS_|||code:1\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 003error\n+#\n+# To the above, add multiple 'error <msg>' events\n+\n+test_expect_success 'perf stream, error event' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" test-tool trace2 003error \"hello world\" \"this is a test\" &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 003error '\\''hello world'\\'' '\\''this is a test'\\''\n+\t\td0|main|cmd_name|||||trace2 (trace2)\n+\t\td0|main|error|||||hello world\n+\t\td0|main|error|||||this is a test\n+\t\td0|main|exit||_T_ABS_|||code:0\n+\t\td0|main|atexit||_T_ABS_|||code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 004child\n+#\n+# Test nested spawning of child processes.\n+#\n+# Conceptually, this looks like:\n+#    P1: TT trace2 004child\n+#    P2: |--- TT trace2 004child\n+#    P3:      |--- TT trace2 001return 0\n+#\n+# Which should generate events:\n+#    P1: version\n+#    P1: start\n+#    P1: cmd_name\n+#    P1: child_start\n+#        P2: version\n+#        P2: start\n+#        P2: cmd_name\n+#        P2: child_start\n+#            P3: version\n+#            P3: start\n+#            P3: cmd_name\n+#            P3: exit\n+#            P3: atexit\n+#        P2: child_exit\n+#        P2: exit\n+#        P2: atexit\n+#    P1: child_exit\n+#    P1: exit\n+#    P1: atexit\n+\n+test_expect_success 'perf stream, child processes' '\n+\ttest_when_finished \"rm trace.perf actual expect\" &&\n+\tGIT_TR2_PERF=\"$(pwd)/trace.perf\" test-tool trace2 004child test-tool trace2 004child test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0211/scrub_perf.perl\" <trace.perf >actual &&\n+\tcat >expect <<-EOF &&\n+\t\td0|main|version|||||$V\n+\t\td0|main|start|||||_EXE_ trace2 004child test-tool trace2 004child test-tool trace2 001return 0\n+\t\td0|main|cmd_name|||||trace2 (trace2)\n+\t\td0|main|child_start||_T_ABS_|||[ch0] class:? argv: test-tool trace2 004child test-tool trace2 001return 0\n+\t\td1|main|version|||||$V\n+\t\td1|main|start|||||_EXE_ trace2 004child test-tool trace2 001return 0\n+\t\td1|main|cmd_name|||||trace2 (trace2/trace2)\n+\t\td1|main|child_start||_T_ABS_|||[ch0] class:? argv: test-tool trace2 001return 0\n+\t\td2|main|version|||||$V\n+\t\td2|main|start|||||_EXE_ trace2 001return 0\n+\t\td2|main|cmd_name|||||trace2 (trace2/trace2/trace2)\n+\t\td2|main|exit||_T_ABS_|||code:0\n+\t\td2|main|atexit||_T_ABS_|||code:0\n+\t\td1|main|child_exit||_T_ABS_|_T_REL_||[ch0] pid:_PID_ code:0\n+\t\td1|main|exit||_T_ABS_|||code:0\n+\t\td1|main|atexit||_T_ABS_|||code:0\n+\t\td0|main|child_exit||_T_ABS_|_T_REL_||[ch0] pid:_PID_ code:0\n+\t\td0|main|exit||_T_ABS_|||code:0\n+\t\td0|main|atexit||_T_ABS_|||code:0\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t0211/scrub_perf.perl b/t/t0211/scrub_perf.perl\nnew file mode 100644\nindex 0000000000..351af7844e\n--- /dev/null\n+++ b/t/t0211/scrub_perf.perl\n@@ -0,0 +1,76 @@\n+#!/usr/bin/perl\n+#\n+# Scrub the variable fields from the perf trace2 output to\n+# make testing easier.\n+\n+use strict;\n+use warnings;\n+\n+my $qpath = '\\'[^\\']*\\'|[^ ]*';\n+\n+my $col_depth=0;\n+my $col_thread=1;\n+my $col_event=2;\n+my $col_repo=3;\n+my $col_t_abs=4;\n+my $col_t_rel=5;\n+my $col_category=6;\n+my $col_rest=7;\n+\n+# This code assumes that the trace2 data was written with bare\n+# turned on (which omits the \"<clock> <file>:<line> | <parents>\"\n+# prefix.\n+\n+while (<>) {\n+    my @tokens = split /\\|/;\n+\n+    foreach my $col (@tokens) { $col =~ s/^\\s+|\\s+$//g; }\n+\n+    if ($tokens[$col_event] =~ m/^start/) {\n+\t# The 'start' message lists the contents of argv in $col_rest.\n+\t# On some platforms (Windows), argv[0] is *sometimes* a canonical\n+\t# absolute path to the EXE rather than the value passed in the\n+\t# shell script.  Replace it with a placeholder to simplify our\n+\t# HEREDOC in the test script.\n+\tmy $argv0;\n+\tmy $argvRest;\n+\t$tokens[$col_rest] =~ s/^($qpath)\\W*(.*)/_EXE_ $2/;\n+    }\n+    elsif ($tokens[$col_event] =~ m/cmd_path/) {\n+\t# Likewise, the 'cmd_path' message breaks out argv[0].\n+\t#\n+\t# This line is only emitted when RUNTIME_PREFIX is defined,\n+\t# so just omit it for testing purposes.\n+\t# $tokens[$col_rest] = \"_EXE_\";\n+\tgoto SKIP_LINE;\n+    }\n+    elsif ($tokens[$col_event] =~ m/child_exit/) {\n+\t$tokens[$col_rest] =~ s/ pid:\\d* / pid:_PID_ /;\n+    }\n+    elsif ($tokens[$col_event] =~ m/data/) {\n+\tif ($tokens[$col_category] =~ m/process/) {\n+\t    # 'data' and 'data_json' events containing 'process'\n+\t    # category data are assumed to be platform-specific\n+\t    # and highly variable.  Just omit them.\n+\t    goto SKIP_LINE;\n+\t}\n+    }\n+\n+    # t_abs and t_rel are either blank or a float.  Replace the float\n+    # with a constant for matching the HEREDOC in the test script.\n+    if ($tokens[$col_t_abs] =~ m/\\d/) {\n+\t$tokens[$col_t_abs] = \"_T_ABS_\";\n+    }\n+    if ($tokens[$col_t_rel] =~ m/\\d/) {\n+\t$tokens[$col_t_rel] = \"_T_REL_\";\n+    }\n+\n+    my $out;\n+\n+    $out = join('|', @tokens);\n+    print \"$out\\n\";\n+\n+  SKIP_LINE:\n+}\n+\n+\ndiff --git a/t/t0212-trace2-event.sh b/t/t0212-trace2-event.sh\nnew file mode 100755\nindex 0000000000..028b6c5671\n--- /dev/null\n+++ b/t/t0212-trace2-event.sh\n@@ -0,0 +1,236 @@\n+#!/bin/sh\n+\n+test_description='test trace2 facility'\n+. ./test-lib.sh\n+\n+perl -MJSON::PP -e 0 >/dev/null 2>&1 && test_set_prereq JSON_PP\n+\n+# Add t/helper directory to PATH so that we can use a relative\n+# path to run nested instances of test-tool.exe (see 004child).\n+# This helps with HEREDOC comparisons later.\n+TTDIR=\"$GIT_BUILD_DIR/t/helper/\" && export TTDIR\n+PATH=\"$TTDIR:$PATH\" && export PATH\n+\n+# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe\n+# Warning: to do the actual diff/comparison, so the HEREDOCs here\n+# Warning: only cover our actual calls to test-tool and/or git.\n+# Warning: So you may see extra lines in artifact files when\n+# Warning: interactively debugging.\n+\n+# Turn off any inherited trace2 settings for this test.\n+unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT\n+unset GIT_TR2_BARE\n+unset GIT_TR2_CONFIG_PARAMS\n+\n+V=$(git version | sed -e 's/^git version //') && export V\n+\n+# There are multiple trace2 targets: normal, perf, and event.\n+# Trace2 events will/can be written to each active target (subject\n+# to whatever filtering that target decides to do).\n+# Test each target independently.\n+#\n+# Defer setting GIT_TR2_PERF until the actual command we want to\n+# test because hidden git and test-tool commands in the test\n+# harness can contaminate our output.\n+\n+# We don't bother repeating the 001return and 002exit tests, since they\n+# have coverage in the normal and perf targets.\n+\n+# Verb 003error\n+#\n+# To the above, add multiple 'error <msg>' events\n+\n+test_expect_success JSON_PP 'event stream, error event' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 003error \"hello world\" \"this is a test\" &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"003error\",\n+\t|      \"hello world\",\n+\t|      \"this is a test\"\n+\t|    ],\n+\t|    \"errors\":[\n+\t|      \"%s\",\n+\t|      \"%s\"\n+\t|    ],\n+\t|    \"exit_code\":0,\n+\t|    \"hierarchy\":\"trace2\",\n+\t|    \"name\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Verb 004child\n+#\n+# Test nested spawning of child processes.\n+#\n+# Conceptually, this looks like:\n+#    P1: TT trace2 004child\n+#    P2: |--- TT trace2 004child\n+#    P3:      |--- TT trace2 001return 0\n+\n+test_expect_success JSON_PP 'event stream, return code 0' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 004child test-tool trace2 004child test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"004child\",\n+\t|      \"test-tool\",\n+\t|      \"trace2\",\n+\t|      \"004child\",\n+\t|      \"test-tool\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"child\":{\n+\t|      \"0\":{\n+\t|        \"child_argv\":[\n+\t|          \"_EXE_\",\n+\t|          \"trace2\",\n+\t|          \"004child\",\n+\t|          \"test-tool\",\n+\t|          \"trace2\",\n+\t|          \"001return\",\n+\t|          \"0\"\n+\t|        ],\n+\t|        \"child_class\":\"?\",\n+\t|        \"child_code\":0,\n+\t|        \"use_shell\":0\n+\t|      }\n+\t|    },\n+\t|    \"exit_code\":0,\n+\t|    \"hierarchy\":\"trace2\",\n+\t|    \"name\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  },\n+\t|  \"_SID0_/_SID1_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"004child\",\n+\t|      \"test-tool\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"child\":{\n+\t|      \"0\":{\n+\t|        \"child_argv\":[\n+\t|          \"_EXE_\",\n+\t|          \"trace2\",\n+\t|          \"001return\",\n+\t|          \"0\"\n+\t|        ],\n+\t|        \"child_class\":\"?\",\n+\t|        \"child_code\":0,\n+\t|        \"use_shell\":0\n+\t|      }\n+\t|    },\n+\t|    \"exit_code\":0,\n+\t|    \"hierarchy\":\"trace2/trace2\",\n+\t|    \"name\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  },\n+\t|  \"_SID0_/_SID1_/_SID2_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"exit_code\":0,\n+\t|    \"hierarchy\":\"trace2/trace2/trace2\",\n+\t|    \"name\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+# Test listing of all \"interesting\" config settings.\n+\n+test_expect_success JSON_PP 'event stream, list config' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tgit config --local t0212.abc 1 &&\n+\tgit config --local t0212.def \"hello world\" &&\n+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" GIT_TR2_CONFIG_PARAMS=\"t0212.*\" test-tool trace2 001return 0 &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"001return\",\n+\t|      \"0\"\n+\t|    ],\n+\t|    \"exit_code\":0,\n+\t|    \"hierarchy\":\"trace2\",\n+\t|    \"name\":\"trace2\",\n+\t|    \"params\":[\n+\t|      {\n+\t|        \"param\":\"t0212.abc\",\n+\t|        \"value\":\"1\"\n+\t|      },\n+\t|      {\n+\t|        \"param\":\"t0212.def\",\n+\t|        \"value\":\"hello world\"\n+\t|      }\n+\t|    ],\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success JSON_PP 'basic trace2_data' '\n+\ttest_when_finished \"rm trace.event actual expect\" &&\n+\tGIT_TR2_EVENT=\"$(pwd)/trace.event\" test-tool trace2 006data test_category k1 v1 test_category k2 v2 &&\n+\tperl \"$TEST_DIRECTORY/t0212/parse_events.perl\" <trace.event >actual &&\n+\tsed -e \"s/^|//\" >expect <<-EOF &&\n+\t|VAR1 = {\n+\t|  \"_SID0_\":{\n+\t|    \"argv\":[\n+\t|      \"_EXE_\",\n+\t|      \"trace2\",\n+\t|      \"006data\",\n+\t|      \"test_category\",\n+\t|      \"k1\",\n+\t|      \"v1\",\n+\t|      \"test_category\",\n+\t|      \"k2\",\n+\t|      \"v2\"\n+\t|    ],\n+\t|    \"data\":{\n+\t|      \"test_category\":{\n+\t|        \"k1\":\"v1\",\n+\t|        \"k2\":\"v2\"\n+\t|      }\n+\t|    },\n+\t|    \"exit_code\":0,\n+\t|    \"hierarchy\":\"trace2\",\n+\t|    \"name\":\"trace2\",\n+\t|    \"version\":\"$V\"\n+\t|  }\n+\t|};\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t0212/parse_events.perl b/t/t0212/parse_events.perl\nnew file mode 100644\nindex 0000000000..a2776ba216\n--- /dev/null\n+++ b/t/t0212/parse_events.perl\n@@ -0,0 +1,251 @@\n+#!/usr/bin/perl\n+#\n+# Parse event stream and convert individual events into a summary\n+# record for the process.\n+#\n+# Git.exe generates one or more \"event\" records for each API method,\n+# such as \"start <argv>\" and \"exit <code>\", during the life of the git\n+# process.  Additionally, the input may contain interleaved events\n+# from multiple concurrent git processes and/or multiple threads from\n+# within a git process.\n+#\n+# Accumulate events for each process (based on its unique SID) in a\n+# dictionary and emit process summary records.\n+#\n+# Convert some of the variable fields (such as elapsed time) into\n+# placeholders (or omit them) to make HEREDOC comparisons easier in\n+# the test scripts.\n+#\n+# We may also omit fields not (currently) useful for testing purposes.\n+\n+use strict;\n+use warnings;\n+use JSON::PP;\n+use Data::Dumper;\n+use Getopt::Long;\n+\n+# The version of the trace2 event target format that we understand.\n+# This is reported in the 'version' event in the 'evt' field.\n+# It comes from the GIT_TR2_EVENT_VERSION macro in trace2/tr2_tgt_event.c\n+my $evt_version = '1';\n+\n+my $show_children = 1;\n+my $show_exec     = 1;\n+my $show_threads  = 1;\n+\n+# A hack to generate test HEREDOC data for pasting into the test script.\n+# Usage:\n+#    cd \"t/trash directory.t0212-trace2-event\"\n+#    $TT trace ... >trace.event\n+#    VV=$(../../git.exe version | sed -e 's/^git version //')\n+#    perl ../t0212/parse_events.perl --HEREDOC --VERSION=$VV <trace.event >heredoc\n+# Then paste heredoc into your new test.\n+\n+my $gen_heredoc = 0;\n+my $gen_version = '';\n+\n+GetOptions(\"children!\" => \\$show_children,\n+\t   \"exec!\"     => \\$show_exec,\n+\t   \"threads!\"  => \\$show_threads,\n+\t   \"HEREDOC!\"  => \\$gen_heredoc,\n+\t   \"VERSION=s\" => \\$gen_version    )\n+    or die(\"Error in command line arguments\\n\");\n+\n+\n+# SIDs contains timestamps and PIDs of the process and its parents.\n+# This makes it difficult to match up in a HEREDOC in the test script.\n+# Build a map from actual SIDs to predictable constant values and yet\n+# keep the parent/child relationships.  For example:\n+# {..., \"sid\":\"1539706952458276-8652\", ...}\n+# {..., \"sid\":\"1539706952458276-8652/1539706952649493-15452\", ...}\n+# becomes:\n+# {..., \"sid\":\"_SID1_\", ...}\n+# {..., \"sid\":\"_SID1_/_SID2_\", ...}\n+my $sid_map;\n+my $sid_count = 0;\n+\n+my $processes;\n+\n+while (<>) {\n+    my $line = decode_json( $_ );\n+\n+    my $sid = \"\";\n+    my $sid_sep = \"\";\n+\n+    my $raw_sid = $line->{'sid'};\n+    my @raw_sid_parts = split /\\//, $raw_sid;\n+    foreach my $raw_sid_k (@raw_sid_parts) {\n+\tif (!exists $sid_map->{$raw_sid_k}) {\n+\t    $sid_map->{$raw_sid_k} = '_SID' . $sid_count . '_';\n+\t    $sid_count++;\n+\t}\n+\t$sid = $sid . $sid_sep . $sid_map->{$raw_sid_k};\n+\t$sid_sep = '/';\n+    }\n+    \n+    my $event = $line->{'event'};\n+\n+    if ($event eq 'version') {\n+\t$processes->{$sid}->{'version'} = $line->{'exe'};\n+\tif ($gen_heredoc == 1 && $gen_version eq $line->{'exe'}) {\n+\t    # If we are generating data FOR the test script, replace\n+\t    # the reported git.exe version with a reference to an\n+\t    # environment variable.  When our output is pasted into\n+\t    # the test script, it will then be expanded in future\n+\t    # test runs to the THEN current version of git.exe.\n+\t    # We assume that the test script uses env var $V.\n+\t    $processes->{$sid}->{'version'} = \"\\$V\";\n+\t}\n+    }\n+\n+    elsif ($event eq 'start') {\n+\t$processes->{$sid}->{'argv'} = $line->{'argv'};\n+\t$processes->{$sid}->{'argv'}[0] = \"_EXE_\";\n+    }\n+\n+    elsif ($event eq 'exit') {\n+\t$processes->{$sid}->{'exit_code'} = $line->{'code'};\n+    }\n+\n+    elsif ($event eq 'atexit') {\n+\t$processes->{$sid}->{'exit_code'} = $line->{'code'};\n+    }\n+\n+    elsif ($event eq 'error') {\n+\t# For HEREDOC purposes, use the error message format string if\n+\t# available, rather than the formatted message (which probably\n+\t# has an absolute pathname).\n+\tif (exists $line->{'fmt'}) {\n+\t    push( @{$processes->{$sid}->{'errors'}}, $line->{'fmt'} );\n+\t}\n+\telsif (exists $line->{'msg'}) {\n+\t    push( @{$processes->{$sid}->{'errors'}}, $line->{'msg'} );\n+\t}\n+    }\n+\n+    elsif ($event eq 'cmd_path') {\n+\t## $processes->{$sid}->{'path'} = $line->{'path'};\n+\t#\n+\t# Like in the 'start' event, we need to replace the value of\n+\t# argv[0] with a token for HEREDOC purposes.  However, the\n+\t# event is only emitted when RUNTIME_PREFIX is defined, so\n+\t# just omit it for testing purposes.\n+\t# $processes->{$sid}->{'path'} = \"_EXE_\";\n+    }\n+    \n+    elsif ($event eq 'cmd_name') {\n+\t$processes->{$sid}->{'name'} = $line->{'name'};\n+\t$processes->{$sid}->{'hierarchy'} = $line->{'hierarchy'};\n+    }\n+\n+    elsif ($event eq 'alias') {\n+\t$processes->{$sid}->{'alias'}->{'key'} = $line->{'alias'};\n+\t$processes->{$sid}->{'alias'}->{'argv'} = $line->{'argv'};\n+    }\n+\n+    elsif ($event eq 'def_param') {\n+\tmy $kv;\n+\t$kv->{'param'} = $line->{'param'};\n+\t$kv->{'value'} = $line->{'value'};\n+\tpush( @{$processes->{$sid}->{'params'}}, $kv );\n+    }\n+\n+    elsif ($event eq 'child_start') {\n+\tif ($show_children == 1) {\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_class'} = $line->{'child_class'};\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_argv'} = $line->{'argv'};\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_argv'}[0] = \"_EXE_\";\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'use_shell'} = $line->{'use_shell'} ? 1 : 0;\n+\t}\n+    }\n+\n+    elsif ($event eq 'child_exit') {\n+\tif ($show_children == 1) {\n+\t    $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_code'} = $line->{'code'};\n+\t}\n+    }\n+\n+    # TODO decide what information we want to test from thread events.\n+\n+    elsif ($event eq 'thread_start') {\n+\tif ($show_threads == 1) {\n+\t}\n+    }\n+\n+    elsif ($event eq 'thread_exit') {\n+\tif ($show_threads == 1) {\n+\t}\n+    }\n+\n+    # TODO decide what information we want to test from exec events.\n+\n+    elsif ($event eq 'exec') {\n+\tif ($show_exec == 1) {\n+\t}\n+    }\n+\n+    elsif ($event eq 'exec_result') {\n+\tif ($show_exec == 1) {\n+\t}\n+    }\n+\n+    elsif ($event eq 'def_param') {\n+\t# Accumulate parameter key/value pairs by key rather than in an array\n+\t# so that we get overwrite (last one wins) effects.\n+\t$processes->{$sid}->{'params'}->{$line->{'param'}} = $line->{'value'};\n+    }\n+\n+    elsif ($event eq 'def_repo') {\n+\t# $processes->{$sid}->{'repos'}->{$line->{'repo'}} = $line->{'worktree'};\n+\t$processes->{$sid}->{'repos'}->{$line->{'repo'}} = \"_WORKTREE_\";\n+    }\n+\n+    # A series of potentially nested and threaded region and data events\n+    # is fundamentally incompatibile with the type of summary record we\n+    # are building in this script.  Since they are intended for\n+    # perf-trace-like analysis rather than a result summary, we ignore\n+    # most of them here.\n+\n+    # elsif ($event eq 'region_enter') {\n+    # }\n+    # elsif ($event eq 'region_leave') {\n+    # }\n+\n+    elsif ($event eq 'data') {\n+\tmy $cat = $line->{'category'};\n+\tif ($cat eq 'test_category') {\n+\t    \n+\t    my $key = $line->{'key'};\n+\t    my $value = $line->{'value'};\n+\t    $processes->{$sid}->{'data'}->{$cat}->{$key} = $value;\n+\t}\n+    }\n+\n+    # This trace2 target does not emit 'printf' events.\n+    #\n+    # elsif ($event eq 'printf') {\n+    # }\n+}\n+\n+# Dump the resulting hash into something that we can compare against\n+# in the test script.  These options make Dumper output look a little\n+# bit like JSON.  Also convert variable references of the form \"$VAR*\"\n+# so that the matching HEREDOC doesn't need to escape it.\n+\n+$Data::Dumper::Sortkeys = 1;\n+$Data::Dumper::Indent = 1;\n+$Data::Dumper::Purity = 1;\n+$Data::Dumper::Pair = ':';\n+\n+my $out = Dumper($processes);\n+$out =~ s/'/\"/g;\n+$out =~ s/\\$VAR/VAR/g;\n+\n+# Finally, if we're running this script to generate (manually confirmed)\n+# data to add to the test script, guard the indentation.\n+\n+if ($gen_heredoc == 1) {\n+    $out =~ s/^/\\t\\|/gms;\n+}\n+\n+print $out;\n-- \ngitgitgadget\n\n"},{"id":"369993","messageId":"4e679241fca81c2be08111ce13351cdf412ef6ce.1550874298.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v7.git.gitgitgadget@gmail.com","subject":"[PATCH v7 10/15] trace2:data: pack-objects: add trace2 regions","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-22T22:25:07Z","receivedAt":"2019-02-22T22:25:19Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nWhen studying the performance of 'git push' we would like to know\nhow much time is spent at various parts of the command. One area\nthat could cause performance trouble is 'git pack-objects'.\n\nAdd trace2 regions around the three main actions taken in this\ncommand:\n\n1. Enumerate objects.\n2. Prepare pack.\n3. Write pack-file.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/pack-objects.c | 16 +++++++++++++++-\n 1 file changed, 15 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 0a70d04604..8a64c2868e 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -33,6 +33,7 @@\n #include \"object-store.h\"\n #include \"dir.h\"\n #include \"midx.h\"\n+#include \"trace2.h\"\n \n #define IN_PACK(obj) oe_in_pack(&to_pack, obj)\n #define SIZE(obj) oe_size(&to_pack, obj)\n@@ -3472,6 +3473,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n+\ttrace2_region_enter(\"pack-objects\", \"enumerate-objects\",\n+\t\t\t    the_repository);\n \tprepare_packing_data(the_repository, &to_pack);\n \n \tif (progress)\n@@ -3486,12 +3489,23 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)\n \tif (include_tag && nr_result)\n \t\tfor_each_ref(add_ref_tag, NULL);\n \tstop_progress(&progress_state);\n+\ttrace2_region_leave(\"pack-objects\", \"enumerate-objects\",\n+\t\t\t    the_repository);\n \n \tif (non_empty && !nr_result)\n \t\treturn 0;\n-\tif (nr_result)\n+\tif (nr_result) {\n+\t\ttrace2_region_enter(\"pack-objects\", \"prepare-pack\",\n+\t\t\t\t    the_repository);\n \t\tprepare_pack(window, depth);\n+\t\ttrace2_region_leave(\"pack-objects\", \"prepare-pack\",\n+\t\t\t\t    the_repository);\n+\t}\n+\n+\ttrace2_region_enter(\"pack-objects\", \"write-pack-file\", the_repository);\n \twrite_pack_file();\n+\ttrace2_region_leave(\"pack-objects\", \"write-pack-file\", the_repository);\n+\n \tif (progress)\n \t\tfprintf_ln(stderr,\n \t\t\t   _(\"Total %\"PRIu32\" (delta %\"PRIu32\"),\"\n-- \ngitgitgadget\n\n"},{"id":"369994","messageId":"ebaa9b1d807957ed41b277fdfbf7ecabfd60e92d.1550874298.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v7.git.gitgitgadget@gmail.com","subject":"[PATCH v7 12/15] trace2:data: add subverb to reset command","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-22T22:25:09Z","receivedAt":"2019-02-22T22:25:21Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n builtin/reset.c | 6 ++++++\n 1 file changed, 6 insertions(+)\n\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex 59898c972e..4e34c61401 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -340,6 +340,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \tif (patch_mode) {\n \t\tif (reset_type != NONE)\n \t\t\tdie(_(\"--patch is incompatible with --{hard,mixed,soft}\"));\n+\t\ttrace2_cmd_mode(\"patch-interactive\");\n \t\treturn run_add_interactive(rev, \"--patch=reset\", &pathspec);\n \t}\n \n@@ -356,6 +357,11 @@ int cmd_reset(int argc, const char **argv, const char *prefix)\n \tif (reset_type == NONE)\n \t\treset_type = MIXED; /* by default */\n \n+\tif (pathspec.nr)\n+\t\ttrace2_cmd_mode(\"path\");\n+\telse\n+\t\ttrace2_cmd_mode(reset_type_names[reset_type]);\n+\n \tif (reset_type != SOFT && (reset_type != MIXED || get_git_work_tree()))\n \t\tsetup_work_tree();\n \n-- \ngitgitgadget\n\n"},{"id":"369995","messageId":"90e4e6af9300751fd2e7e98601ed0e60ca048e05.1550874298.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v7.git.gitgitgadget@gmail.com","subject":"[PATCH v7 02/15] trace2: create new combined trace facility","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-22T22:25:01Z","receivedAt":"2019-02-22T22:25:22Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nCreate a new unified tracing facility for git.  The eventual intent is to\nreplace the current trace_printf* and trace_performance* routines with a\nunified set of git_trace2* routines.\n\nIn addition to the usual printf-style API, trace2 provides higer-level\nevent verbs with fixed-fields allowing structured data to be written.\nThis makes post-processing and analysis easier for external tools.\n\nTrace2 defines 3 output targets.  These are set using the environment\nvariables \"GIT_TR2\", \"GIT_TR2_PERF\", and \"GIT_TR2_EVENT\".  These may be\nset to \"1\" or to an absolute pathname (just like the current GIT_TRACE).\n\n* GIT_TR2 is intended to be a replacement for GIT_TRACE and logs command\n  summary data.\n\n* GIT_TR2_PERF is intended as a replacement for GIT_TRACE_PERFORMANCE.\n  It extends the output with columns for the command process, thread,\n  repo, absolute and relative elapsed times.  It reports events for\n  child process start/stop, thread start/stop, and per-thread function\n  nesting.\n\n* GIT_TR2_EVENT is a new structured format. It writes event data as a\n  series of JSON records.\n\nCalls to trace2 functions log to any of the 3 output targets enabled\nwithout the need to call different trace_printf* or trace_performance*\nroutines.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                      |  14 +-\n builtin/submodule--helper.c   |   9 +-\n cache.h                       |   1 +\n common-main.c                 |  12 +-\n compat/mingw.c                |  11 +-\n compat/mingw.h                |   3 +-\n config.c                      |   2 +\n exec-cmd.c                    |   2 +\n git-compat-util.h             |   7 +\n git.c                         |  65 +++\n remote-curl.c                 |   7 +\n repository.c                  |   2 +\n repository.h                  |   3 +\n run-command.c                 |  59 ++-\n run-command.h                 |  13 +-\n sh-i18n--envsubst.c           |   3 +\n submodule.c                   |  11 +-\n t/helper/test-parse-options.c |   3 +\n t/helper/test-tool.c          |   3 +\n t/t0001-init.sh               |   1 +\n trace2.c                      | 761 ++++++++++++++++++++++++++++++++++\n trace2.h                      | 371 +++++++++++++++++\n trace2/tr2_cfg.c              |  90 ++++\n trace2/tr2_cfg.h              |  19 +\n trace2/tr2_cmd_name.c         |  30 ++\n trace2/tr2_cmd_name.h         |  24 ++\n trace2/tr2_dst.c              | 252 +++++++++++\n trace2/tr2_dst.h              |  36 ++\n trace2/tr2_sid.c              |  67 +++\n trace2/tr2_sid.h              |  18 +\n trace2/tr2_tbuf.c             |  32 ++\n trace2/tr2_tbuf.h             |  23 +\n trace2/tr2_tgt.h              | 133 ++++++\n trace2/tr2_tgt_event.c        | 588 ++++++++++++++++++++++++++\n trace2/tr2_tgt_normal.c       | 323 +++++++++++++++\n trace2/tr2_tgt_perf.c         | 534 ++++++++++++++++++++++++\n trace2/tr2_tls.c              | 164 ++++++++\n trace2/tr2_tls.h              |  97 +++++\n usage.c                       |  31 ++\n 39 files changed, 3806 insertions(+), 18 deletions(-)\n create mode 100644 trace2.c\n create mode 100644 trace2.h\n create mode 100644 trace2/tr2_cfg.c\n create mode 100644 trace2/tr2_cfg.h\n create mode 100644 trace2/tr2_cmd_name.c\n create mode 100644 trace2/tr2_cmd_name.h\n create mode 100644 trace2/tr2_dst.c\n create mode 100644 trace2/tr2_dst.h\n create mode 100644 trace2/tr2_sid.c\n create mode 100644 trace2/tr2_sid.h\n create mode 100644 trace2/tr2_tbuf.c\n create mode 100644 trace2/tr2_tbuf.h\n create mode 100644 trace2/tr2_tgt.h\n create mode 100644 trace2/tr2_tgt_event.c\n create mode 100644 trace2/tr2_tgt_normal.c\n create mode 100644 trace2/tr2_tgt_perf.c\n create mode 100644 trace2/tr2_tls.c\n create mode 100644 trace2/tr2_tls.h\n\ndiff --git a/Makefile b/Makefile\nindex 6e8d017e8e..2ab7b73000 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1005,6 +1005,16 @@ LIB_OBJS += tempfile.o\n LIB_OBJS += thread-utils.o\n LIB_OBJS += tmp-objdir.o\n LIB_OBJS += trace.o\n+LIB_OBJS += trace2.o\n+LIB_OBJS += trace2/tr2_cfg.o\n+LIB_OBJS += trace2/tr2_cmd_name.o\n+LIB_OBJS += trace2/tr2_dst.o\n+LIB_OBJS += trace2/tr2_sid.o\n+LIB_OBJS += trace2/tr2_tbuf.o\n+LIB_OBJS += trace2/tr2_tgt_event.o\n+LIB_OBJS += trace2/tr2_tgt_normal.o\n+LIB_OBJS += trace2/tr2_tgt_perf.o\n+LIB_OBJS += trace2/tr2_tls.o\n LIB_OBJS += trailer.o\n LIB_OBJS += transport.o\n LIB_OBJS += transport-helper.o\n@@ -1581,7 +1591,9 @@ ifdef NO_INET_PTON\n \tLIB_OBJS += compat/inet_pton.o\n \tBASIC_CFLAGS += -DNO_INET_PTON\n endif\n-ifndef NO_UNIX_SOCKETS\n+ifdef NO_UNIX_SOCKETS\n+\tBASIC_CFLAGS += -DNO_UNIX_SOCKETS\n+else\n \tLIB_OBJS += unix-socket.o\n \tPROGRAM_OBJS += credential-cache.o\n \tPROGRAM_OBJS += credential-cache--daemon.o\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 0e140f176c..f10dc5134a 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -1815,11 +1815,10 @@ static int update_submodules(struct submodule_update_clone *suc)\n {\n \tint i;\n \n-\trun_processes_parallel(suc->max_jobs,\n-\t\t\t       update_clone_get_next_task,\n-\t\t\t       update_clone_start_failure,\n-\t\t\t       update_clone_task_finished,\n-\t\t\t       suc);\n+\trun_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task,\n+\t\t\t\t   update_clone_start_failure,\n+\t\t\t\t   update_clone_task_finished, suc, \"submodule\",\n+\t\t\t\t   \"parallel/update\");\n \n \t/*\n \t * We saved the output and put it out all at once now.\ndiff --git a/cache.h b/cache.h\nindex 009e8b3b15..3d12ffe01f 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -9,6 +9,7 @@\n #include \"gettext.h\"\n #include \"convert.h\"\n #include \"trace.h\"\n+#include \"trace2.h\"\n #include \"string-list.h\"\n #include \"pack-revindex.h\"\n #include \"hash.h\"\ndiff --git a/common-main.c b/common-main.c\nindex 3728f66b4c..6dbdc4adf2 100644\n--- a/common-main.c\n+++ b/common-main.c\n@@ -25,12 +25,18 @@ static void restore_sigpipe_to_default(void)\n \n int main(int argc, const char **argv)\n {\n+\tint result;\n+\n \t/*\n \t * Always open file descriptors 0/1/2 to avoid clobbering files\n \t * in die().  It also avoids messing up when the pipes are dup'ed\n \t * onto stdin/stdout/stderr in the child processes we spawn.\n \t */\n \tsanitize_stdfds();\n+\trestore_sigpipe_to_default();\n+\n+\ttrace2_initialize();\n+\ttrace2_cmd_start(argv);\n \n \tgit_resolve_executable_dir(argv[0]);\n \n@@ -40,7 +46,9 @@ int main(int argc, const char **argv)\n \n \tattr_start();\n \n-\trestore_sigpipe_to_default();\n+\tresult = cmd_main(argc, argv);\n+\n+\ttrace2_cmd_exit(result);\n \n-\treturn cmd_main(argc, argv);\n+\treturn result;\n }\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex b459e1a291..111fe68baf 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -1476,19 +1476,23 @@ static int try_shell_exec(const char *cmd, char *const *argv)\n \t\treturn 0;\n \tprog = path_lookup(interpr, 1);\n \tif (prog) {\n+\t\tint exec_id;\n \t\tint argc = 0;\n \t\tconst char **argv2;\n \t\twhile (argv[argc]) argc++;\n \t\tALLOC_ARRAY(argv2, argc + 1);\n \t\targv2[0] = (char *)cmd;\t/* full path to the script file */\n \t\tmemcpy(&argv2[1], &argv[1], sizeof(*argv) * argc);\n+\t\texec_id = trace2_exec(prog, argv2);\n \t\tpid = mingw_spawnv(prog, argv2, 1);\n \t\tif (pid >= 0) {\n \t\t\tint status;\n \t\t\tif (waitpid(pid, &status, 0) < 0)\n \t\t\t\tstatus = 255;\n+\t\t\ttrace2_exec_result(exec_id, status);\n \t\t\texit(status);\n \t\t}\n+\t\ttrace2_exec_result(exec_id, -1);\n \t\tpid = 1;\t/* indicate that we tried but failed */\n \t\tfree(prog);\n \t\tfree(argv2);\n@@ -1501,12 +1505,17 @@ int mingw_execv(const char *cmd, char *const *argv)\n \t/* check if git_command is a shell script */\n \tif (!try_shell_exec(cmd, argv)) {\n \t\tint pid, status;\n+\t\tint exec_id;\n \n+\t\texec_id = trace2_exec(cmd, (const char **)argv);\n \t\tpid = mingw_spawnv(cmd, (const char **)argv, 0);\n-\t\tif (pid < 0)\n+\t\tif (pid < 0) {\n+\t\t\ttrace2_exec_result(exec_id, -1);\n \t\t\treturn -1;\n+\t\t}\n \t\tif (waitpid(pid, &status, 0) < 0)\n \t\t\tstatus = 255;\n+\t\ttrace2_exec_result(exec_id, status);\n \t\texit(status);\n \t}\n \treturn -1;\ndiff --git a/compat/mingw.h b/compat/mingw.h\nindex 30d9fb3e36..4d73f8aa9d 100644\n--- a/compat/mingw.h\n+++ b/compat/mingw.h\n@@ -147,8 +147,7 @@ static inline int fcntl(int fd, int cmd, ...)\n \terrno = EINVAL;\n \treturn -1;\n }\n-/* bash cannot reliably detect negative return codes as failure */\n-#define exit(code) exit((code) & 0xff)\n+\n #define sigemptyset(x) (void)0\n static inline int sigaddset(sigset_t *set, int signum)\n { return 0; }\ndiff --git a/config.c b/config.c\nindex ff521eb27a..4beeb7795a 100644\n--- a/config.c\n+++ b/config.c\n@@ -2656,6 +2656,8 @@ int git_config_set_gently(const char *key, const char *value)\n void git_config_set(const char *key, const char *value)\n {\n \tgit_config_set_multivar(key, value, NULL, 0);\n+\n+\ttrace2_cmd_set_config(key, value);\n }\n \n /*\ndiff --git a/exec-cmd.c b/exec-cmd.c\nindex 4f81f44310..7deeab3039 100644\n--- a/exec-cmd.c\n+++ b/exec-cmd.c\n@@ -209,6 +209,8 @@ static int git_get_exec_path(struct strbuf *buf, const char *argv0)\n \t\treturn -1;\n \t}\n \n+\ttrace2_cmd_path(buf->buf);\n+\n \treturn 0;\n }\n \ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 29a19902aa..1d0aedc11f 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -1251,6 +1251,13 @@ static inline int is_missing_file_error(int errno_)\n \n extern int cmd_main(int, const char **);\n \n+/*\n+ * Intercept all calls to exit() and route them to trace2 to\n+ * optionally emit a message before calling the real exit().\n+ */\n+int trace2_cmd_exit_fl(const char *file, int line, int code);\n+#define exit(code) exit(trace2_cmd_exit_fl(__FILE__, __LINE__, (code)))\n+\n /*\n  * You can mark a stack variable with UNLEAK(var) to avoid it being\n  * reported as a leak by tools like LSAN or valgrind. The argument\ndiff --git a/git.c b/git.c\nindex 0ce0e13f0f..67ba8de9d1 100644\n--- a/git.c\n+++ b/git.c\n@@ -147,16 +147,20 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\t\tgit_set_exec_path(cmd + 1);\n \t\t\telse {\n \t\t\t\tputs(git_exec_path());\n+\t\t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\t\texit(0);\n \t\t\t}\n \t\t} else if (!strcmp(cmd, \"--html-path\")) {\n \t\t\tputs(system_path(GIT_HTML_PATH));\n+\t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"--man-path\")) {\n \t\t\tputs(system_path(GIT_MAN_PATH));\n+\t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"--info-path\")) {\n \t\t\tputs(system_path(GIT_INFO_PATH));\n+\t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\texit(0);\n \t\t} else if (!strcmp(cmd, \"-p\") || !strcmp(cmd, \"--paginate\")) {\n \t\t\tuse_pager = 1;\n@@ -285,6 +289,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\t(*argv)++;\n \t\t\t(*argc)--;\n \t\t} else if (skip_prefix(cmd, \"--list-cmds=\", &cmd)) {\n+\t\t\ttrace2_cmd_name(\"_query_\");\n \t\t\tif (!strcmp(cmd, \"parseopt\")) {\n \t\t\t\tstruct string_list list = STRING_LIST_INIT_DUP;\n \t\t\t\tint i;\n@@ -332,9 +337,14 @@ static int handle_alias(int *argcp, const char ***argv)\n \t\t\tcommit_pager_choice();\n \n \t\t\tchild.use_shell = 1;\n+\t\t\tchild.trace2_child_class = \"shell_alias\";\n \t\t\targv_array_push(&child.args, alias_string + 1);\n \t\t\targv_array_pushv(&child.args, (*argv) + 1);\n \n+\t\t\ttrace2_cmd_alias(alias_command, child.args.argv);\n+\t\t\ttrace2_cmd_list_config();\n+\t\t\ttrace2_cmd_name(\"_run_shell_alias_\");\n+\n \t\t\tret = run_command(&child);\n \t\t\tif (ret >= 0)   /* normal exit */\n \t\t\t\texit(ret);\n@@ -369,6 +379,9 @@ static int handle_alias(int *argcp, const char ***argv)\n \t\t/* insert after command name */\n \t\tmemcpy(new_argv + count, *argv + 1, sizeof(char *) * *argcp);\n \n+\t\ttrace2_cmd_alias(alias_command, new_argv);\n+\t\ttrace2_cmd_list_config();\n+\n \t\t*argv = new_argv;\n \t\t*argcp += count - 1;\n \n@@ -417,6 +430,8 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)\n \t\tsetup_work_tree();\n \n \ttrace_argv_printf(argv, \"trace: built-in: git\");\n+\ttrace2_cmd_name(p->cmd);\n+\ttrace2_cmd_list_config();\n \n \tvalidate_cache_entries(&the_index);\n \tstatus = p->fn(argc, argv, prefix);\n@@ -666,7 +681,14 @@ static void execv_dashed_external(const char **argv)\n \tcmd.clean_on_exit = 1;\n \tcmd.wait_after_clean = 1;\n \tcmd.silent_exec_failure = 1;\n+\tcmd.trace2_child_class = \"dashed\";\n \n+\ttrace2_cmd_name(\"_run_dashed_\");\n+\n+\t/*\n+\t * The code in run_command() logs trace2 child_start/child_exit\n+\t * events, so we do not need to report exec/exec_result events here.\n+\t */\n \ttrace_argv_printf(cmd.args.argv, \"trace: exec:\");\n \n \t/*\n@@ -676,6 +698,12 @@ static void execv_dashed_external(const char **argv)\n \t * the program.\n \t */\n \tstatus = run_command(&cmd);\n+\n+\t/*\n+\t * If the child process ran and we are now going to exit, emit a\n+\t * generic string as our trace2 command verb to indicate that we\n+\t * launched a dashed command.\n+\t */\n \tif (status >= 0)\n \t\texit(status);\n \telse if (errno != ENOENT)\n@@ -701,6 +729,43 @@ static int run_argv(int *argcp, const char ***argv)\n \t\tif (!done_alias)\n \t\t\thandle_builtin(*argcp, *argv);\n \n+#if 0 // TODO In GFW, need to amend a7924b655e940b06cb547c235d6bed9767929673 to include trace2_ and _tr2 lines.\n+\t\telse if (get_builtin(**argv)) {\n+\t\t\tstruct argv_array args = ARGV_ARRAY_INIT;\n+\t\t\tint i;\n+\n+\t\t\t/*\n+\t\t\t * The current process is committed to launching a\n+\t\t\t * child process to run the command named in (**argv)\n+\t\t\t * and exiting.  Log a generic string as the trace2\n+\t\t\t * command verb to indicate this.  Note that the child\n+\t\t\t * process will log the actual verb when it runs.\n+\t\t\t */\n+\t\t\ttrace2_cmd_name(\"_run_git_alias_\");\n+\n+\t\t\tif (get_super_prefix())\n+\t\t\t\tdie(\"%s doesn't support --super-prefix\", **argv);\n+\n+\t\t\tcommit_pager_choice();\n+\n+\t\t\targv_array_push(&args, \"git\");\n+\t\t\tfor (i = 0; i < *argcp; i++)\n+\t\t\t\targv_array_push(&args, (*argv)[i]);\n+\n+\t\t\ttrace_argv_printf(args.argv, \"trace: exec:\");\n+\n+\t\t\t/*\n+\t\t\t * if we fail because the command is not found, it is\n+\t\t\t * OK to return. Otherwise, we just pass along the status code.\n+\t\t\t */\n+\t\t\ti = run_command_v_opt_tr2(args.argv, RUN_SILENT_EXEC_FAILURE |\n+\t\t\t\t\t\t  RUN_CLEAN_ON_EXIT, \"git_alias\");\n+\t\t\tif (i >= 0 || errno != ENOENT)\n+\t\t\t\texit(i);\n+\t\t\tdie(\"could not execute builtin %s\", **argv);\n+\t\t}\n+#endif // a7924b655e940b06cb547c235d6bed9767929673\n+\n \t\t/* .. then try the external ones */\n \t\texecv_dashed_external(*argv);\n \ndiff --git a/remote-curl.c b/remote-curl.c\nindex 6ff9c66b90..ef7f4903c9 100644\n--- a/remote-curl.c\n+++ b/remote-curl.c\n@@ -1360,6 +1360,13 @@ int cmd_main(int argc, const char **argv)\n \tstring_list_init(&options.deepen_not, 1);\n \tstring_list_init(&options.push_options, 1);\n \n+\t/*\n+\t * Just report \"remote-curl\" here (folding all the various aliases\n+\t * (\"git-remote-http\", \"git-remote-https\", and etc.) here since they\n+\t * are all just copies of the same actual executable.\n+\t */\n+\ttrace2_cmd_name(\"remote-curl\");\n+\n \tremote = remote_get(argv[1]);\n \n \tif (argc > 2) {\ndiff --git a/repository.c b/repository.c\nindex 20c509a922..b8da4d0bbf 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -119,6 +119,8 @@ static int repo_init_gitdir(struct repository *repo, const char *gitdir)\n void repo_set_worktree(struct repository *repo, const char *path)\n {\n \trepo->worktree = real_pathdup(path, 1);\n+\n+\ttrace2_def_repo(repo);\n }\n \n static int read_and_verify_repository_format(struct repository_format *format,\ndiff --git a/repository.h b/repository.h\nindex 0e482b7d49..4e2ad6e0be 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -90,6 +90,9 @@ struct repository {\n \t/* Repository's current hash algorithm, as serialized on disk. */\n \tconst struct git_hash_algo *hash_algo;\n \n+\t/* A unique-id for tracing purposes. */\n+\tint trace2_repo_id;\n+\n \t/* Configurations */\n \n \t/* Indicate if a repository has a different 'commondir' from 'gitdir' */\ndiff --git a/run-command.c b/run-command.c\nindex 3db26b7b0e..3449db319b 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -219,9 +219,29 @@ static int exists_in_PATH(const char *file)\n \n int sane_execvp(const char *file, char * const argv[])\n {\n+#ifndef GIT_WINDOWS_NATIVE\n+\t/*\n+\t * execvp() doesn't return, so we all we can do is tell trace2\n+\t * what we are about to do and let it leave a hint in the log\n+\t * (unless of course the execvp() fails).\n+\t *\n+\t * we skip this for Windows because the compat layer already\n+\t * has to emulate the execvp() call anyway.\n+\t */\n+\tint exec_id = trace2_exec(file, (const char **)argv);\n+#endif\n+\n \tif (!execvp(file, argv))\n \t\treturn 0; /* cannot happen ;-) */\n \n+#ifndef GIT_WINDOWS_NATIVE\n+\t{\n+\t\tint ec = errno;\n+\t\ttrace2_exec_result(exec_id, ec);\n+\t\terrno = ec;\n+\t}\n+#endif\n+\n \t/*\n \t * When a command can't be found because one of the directories\n \t * listed in $PATH is unsearchable, execvp reports EACCES, but\n@@ -712,6 +732,7 @@ int start_command(struct child_process *cmd)\n \t\tcmd->err = fderr[0];\n \t}\n \n+\ttrace2_child_start(cmd);\n \ttrace_run_command(cmd);\n \n \tfflush(NULL);\n@@ -926,6 +947,8 @@ int start_command(struct child_process *cmd)\n #endif\n \n \tif (cmd->pid < 0) {\n+\t\ttrace2_child_exit(cmd, -1);\n+\n \t\tif (need_in)\n \t\t\tclose_pair(fdin);\n \t\telse if (cmd->in)\n@@ -964,13 +987,16 @@ int start_command(struct child_process *cmd)\n int finish_command(struct child_process *cmd)\n {\n \tint ret = wait_or_whine(cmd->pid, cmd->argv[0], 0);\n+\ttrace2_child_exit(cmd, ret);\n \tchild_process_clear(cmd);\n \treturn ret;\n }\n \n int finish_command_in_signal(struct child_process *cmd)\n {\n-\treturn wait_or_whine(cmd->pid, cmd->argv[0], 1);\n+\tint ret = wait_or_whine(cmd->pid, cmd->argv[0], 1);\n+\ttrace2_child_exit(cmd, ret);\n+\treturn ret;\n }\n \n \n@@ -992,7 +1018,18 @@ int run_command_v_opt(const char **argv, int opt)\n \treturn run_command_v_opt_cd_env(argv, opt, NULL, NULL);\n }\n \n+int run_command_v_opt_tr2(const char **argv, int opt, const char *tr2_class)\n+{\n+\treturn run_command_v_opt_cd_env_tr2(argv, opt, NULL, NULL, tr2_class);\n+}\n+\n int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env)\n+{\n+\treturn run_command_v_opt_cd_env_tr2(argv, opt, dir, env, NULL);\n+}\n+\n+int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,\n+\t\t\t\t const char *const *env, const char *tr2_class)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \tcmd.argv = argv;\n@@ -1004,6 +1041,7 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const\n \tcmd.clean_on_exit = opt & RUN_CLEAN_ON_EXIT ? 1 : 0;\n \tcmd.dir = dir;\n \tcmd.env = env;\n+\tcmd.trace2_child_class = tr2_class;\n \treturn run_command(&cmd);\n }\n \n@@ -1319,6 +1357,7 @@ int run_hook_ve(const char *const *env, const char *name, va_list args)\n \thook.env = env;\n \thook.no_stdin = 1;\n \thook.stdout_to_stderr = 1;\n+\thook.trace2_hook_name = name;\n \n \treturn run_command(&hook);\n }\n@@ -1807,3 +1846,21 @@ int run_processes_parallel(int n,\n \tpp_cleanup(&pp);\n \treturn 0;\n }\n+\n+int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task,\n+\t\t\t       start_failure_fn start_failure,\n+\t\t\t       task_finished_fn task_finished, void *pp_cb,\n+\t\t\t       const char *tr2_category, const char *tr2_label)\n+{\n+\tint result;\n+\n+\ttrace2_region_enter_printf(tr2_category, tr2_label, NULL, \"max:%d\",\n+\t\t\t\t   ((n < 1) ? online_cpus() : n));\n+\n+\tresult = run_processes_parallel(n, get_next_task, start_failure,\n+\t\t\t\t\ttask_finished, pp_cb);\n+\n+\ttrace2_region_leave(tr2_category, tr2_label, NULL);\n+\n+\treturn result;\n+}\ndiff --git a/run-command.h b/run-command.h\nindex 68f5369fc2..a6950691c0 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -10,6 +10,12 @@ struct child_process {\n \tstruct argv_array args;\n \tstruct argv_array env_array;\n \tpid_t pid;\n+\n+\tint trace2_child_id;\n+\tuint64_t trace2_child_us_start;\n+\tconst char *trace2_child_class;\n+\tconst char *trace2_hook_name;\n+\n \t/*\n \t * Using .in, .out, .err:\n \t * - Specify 0 for no redirections (child inherits stdin, stdout,\n@@ -73,12 +79,14 @@ extern int run_hook_ve(const char *const *env, const char *name, va_list args);\n #define RUN_USING_SHELL 16\n #define RUN_CLEAN_ON_EXIT 32\n int run_command_v_opt(const char **argv, int opt);\n-\n+int run_command_v_opt_tr2(const char **argv, int opt, const char *tr2_class);\n /*\n  * env (the environment) is to be formatted like environ: \"VAR=VALUE\".\n  * To unset an environment variable use just \"VAR\".\n  */\n int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env);\n+int run_command_v_opt_cd_env_tr2(const char **argv, int opt, const char *dir,\n+\t\t\t\t const char *const *env, const char *tr2_class);\n \n /**\n  * Execute the given command, sending \"in\" to its stdin, and capturing its\n@@ -220,5 +228,8 @@ int run_processes_parallel(int n,\n \t\t\t   start_failure_fn,\n \t\t\t   task_finished_fn,\n \t\t\t   void *pp_cb);\n+int run_processes_parallel_tr2(int n, get_next_task_fn, start_failure_fn,\n+\t\t\t       task_finished_fn, void *pp_cb,\n+\t\t\t       const char *tr2_category, const char *tr2_label);\n \n #endif\ndiff --git a/sh-i18n--envsubst.c b/sh-i18n--envsubst.c\nindex 09c6b445b9..cecfdd36c7 100644\n--- a/sh-i18n--envsubst.c\n+++ b/sh-i18n--envsubst.c\n@@ -14,6 +14,7 @@\n  */\n \n #include \"git-compat-util.h\"\n+#include \"trace2.h\"\n \n /* Substitution of environment variables in shell format strings.\n    Copyright (C) 2003-2007 Free Software Foundation, Inc.\n@@ -67,6 +68,8 @@ cmd_main (int argc, const char *argv[])\n   /* Default values for command line options.  */\n   /* unsigned short int show_variables = 0; */\n \n+  trace2_cmd_name(\"sh-i18n--envsubst\");\n+\n   switch (argc)\n \t{\n \tcase 1:\ndiff --git a/submodule.c b/submodule.c\nindex 7b5cea8522..a01c9dccf6 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1568,11 +1568,12 @@ int fetch_populated_submodules(struct repository *r,\n \n \tcalculate_changed_submodule_paths(r, &spf.changed_submodule_names);\n \tstring_list_sort(&spf.changed_submodule_names);\n-\trun_processes_parallel(max_parallel_jobs,\n-\t\t\t       get_next_submodule,\n-\t\t\t       fetch_start_failure,\n-\t\t\t       fetch_finish,\n-\t\t\t       &spf);\n+\trun_processes_parallel_tr2(max_parallel_jobs,\n+\t\t\t\t   get_next_submodule,\n+\t\t\t\t   fetch_start_failure,\n+\t\t\t\t   fetch_finish,\n+\t\t\t\t   &spf,\n+\t\t\t\t   \"submodule\", \"parallel/fetch\");\n \n \targv_array_clear(&spf.args);\n out:\ndiff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c\nindex 47fee660b8..cc88fba057 100644\n--- a/t/helper/test-parse-options.c\n+++ b/t/helper/test-parse-options.c\n@@ -2,6 +2,7 @@\n #include \"cache.h\"\n #include \"parse-options.h\"\n #include \"string-list.h\"\n+#include \"trace2.h\"\n \n static int boolean = 0;\n static int integer = 0;\n@@ -153,6 +154,8 @@ int cmd__parse_options(int argc, const char **argv)\n \tint i;\n \tint ret = 0;\n \n+\ttrace2_cmd_name(\"_parse_\");\n+\n \targc = parse_options(argc, (const char **)argv, prefix, options, usage, 0);\n \n \tif (length_cb.called) {\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex 5b137874e1..4dbfff7dee 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -1,5 +1,6 @@\n #include \"git-compat-util.h\"\n #include \"test-tool.h\"\n+#include \"trace2.h\"\n \n struct test_cmd {\n \tconst char *name;\n@@ -80,6 +81,8 @@ int cmd_main(int argc, const char **argv)\n \t\tif (!strcmp(cmds[i].name, argv[1])) {\n \t\t\targv++;\n \t\t\targc--;\n+\t\t\ttrace2_cmd_name(cmds[i].name);\n+\t\t\ttrace2_cmd_list_config();\n \t\t\treturn cmds[i].fn(argc, argv);\n \t\t}\n \t}\ndiff --git a/t/t0001-init.sh b/t/t0001-init.sh\nindex 42a263cada..5e27604b24 100755\n--- a/t/t0001-init.sh\n+++ b/t/t0001-init.sh\n@@ -93,6 +93,7 @@ test_expect_success 'No extra GIT_* on alias scripts' '\n \t\tsed -n \\\n \t\t\t-e \"/^GIT_PREFIX=/d\" \\\n \t\t\t-e \"/^GIT_TEXTDOMAINDIR=/d\" \\\n+\t\t\t-e \"/^GIT_TR2_PARENT/d\" \\\n \t\t\t-e \"/^GIT_/s/=.*//p\" |\n \t\tsort\n \tEOF\ndiff --git a/trace2.c b/trace2.c\nnew file mode 100644\nindex 0000000000..ccccd4ef09\n--- /dev/null\n+++ b/trace2.c\n@@ -0,0 +1,761 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"json-writer.h\"\n+#include \"quote.h\"\n+#include \"run-command.h\"\n+#include \"sigchain.h\"\n+#include \"thread-utils.h\"\n+#include \"version.h\"\n+#include \"trace2/tr2_cfg.h\"\n+#include \"trace2/tr2_cmd_name.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_sid.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+static int trace2_enabled;\n+\n+static int tr2_next_child_id; /* modify under lock */\n+static int tr2_next_exec_id; /* modify under lock */\n+static int tr2_next_repo_id = 1; /* modify under lock. zero is reserved */\n+\n+/*\n+ * A table of the builtin TRACE2 targets.  Each of these may be independently\n+ * enabled or disabled.  Each TRACE2 API method will try to write an event to\n+ * *each* of the enabled targets.\n+ */\n+/* clang-format off */\n+static struct tr2_tgt *tr2_tgt_builtins[] =\n+{\n+\t&tr2_tgt_normal,\n+\t&tr2_tgt_perf,\n+\t&tr2_tgt_event,\n+\tNULL\n+};\n+/* clang-format on */\n+\n+/* clang-format off */\n+#define for_each_builtin(j, tgt_j)\t\t\t\\\n+\tfor (j = 0, tgt_j = tr2_tgt_builtins[j];\t\\\n+\t     tgt_j;\t\t\t\t\t\\\n+\t     j++, tgt_j = tr2_tgt_builtins[j])\n+/* clang-format on */\n+\n+/* clang-format off */\n+#define for_each_wanted_builtin(j, tgt_j)            \\\n+\tfor_each_builtin(j, tgt_j)                   \\\n+\t\tif (tr2_dst_trace_want(tgt_j->pdst))\n+/* clang-format on */\n+\n+/*\n+ * Force (rather than lazily) initialize any of the requested\n+ * builtin TRACE2 targets at startup (and before we've seen an\n+ * actual TRACE2 event call) so we can see if we need to setup\n+ * the TR2 and TLS machinery.\n+ *\n+ * Return the number of builtin targets enabled.\n+ */\n+static int tr2_tgt_want_builtins(void)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tint sum = 0;\n+\n+\tfor_each_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_init())\n+\t\t\tsum++;\n+\n+\treturn sum;\n+}\n+\n+/*\n+ * Properly terminate each builtin target.  Give each target\n+ * a chance to write a summary event and/or flush if necessary\n+ * and then close the fd.\n+ */\n+static void tr2_tgt_disable_builtins(void)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tfor_each_builtin (j, tgt_j)\n+\t\ttgt_j->pfn_term();\n+}\n+\n+static int tr2main_exit_code;\n+\n+/*\n+ * Our atexit routine should run after everything has finished.\n+ *\n+ * Note that events generated here might not actually appear if\n+ * we are writing to fd 1 or 2 and our atexit routine runs after\n+ * the pager's atexit routine (since it closes them to shutdown\n+ * the pipes).\n+ */\n+static void tr2main_atexit_handler(void)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Clear any unbalanced regions so that our atexit message\n+\t * does not appear nested.  This improves the appearance of\n+\t * the trace output if someone calls die(), for example.\n+\t */\n+\ttr2tls_pop_unwind_self();\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_atexit)\n+\t\t\ttgt_j->pfn_atexit(us_elapsed_absolute,\n+\t\t\t\t\t  tr2main_exit_code);\n+\n+\ttr2_tgt_disable_builtins();\n+\n+\ttr2tls_release();\n+\ttr2_sid_release();\n+\ttr2_cmd_name_release();\n+\ttr2_cfg_free_patterns();\n+\n+\ttrace2_enabled = 0;\n+}\n+\n+static void tr2main_signal_handler(int signo)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_signal)\n+\t\t\ttgt_j->pfn_signal(us_elapsed_absolute, signo);\n+\n+\tsigchain_pop(signo);\n+\traise(signo);\n+}\n+\n+void trace2_initialize_fl(const char *file, int line)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (trace2_enabled)\n+\t\treturn;\n+\n+\tif (!tr2_tgt_want_builtins())\n+\t\treturn;\n+\ttrace2_enabled = 1;\n+\n+\ttr2_sid_get();\n+\n+\tatexit(tr2main_atexit_handler);\n+\tsigchain_push(SIGPIPE, tr2main_signal_handler);\n+\ttr2tls_init();\n+\n+\t/*\n+\t * Emit 'version' message on each active builtin target.\n+\t */\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_version_fl)\n+\t\t\ttgt_j->pfn_version_fl(file, line);\n+}\n+\n+int trace2_is_enabled(void)\n+{\n+\treturn trace2_enabled;\n+}\n+\n+void trace2_cmd_start_fl(const char *file, int line, const char **argv)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_start_fl)\n+\t\t\ttgt_j->pfn_start_fl(file, line, argv);\n+}\n+\n+int trace2_cmd_exit_fl(const char *file, int line, int code)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tcode &= 0xff;\n+\n+\tif (!trace2_enabled)\n+\t\treturn code;\n+\n+\ttr2main_exit_code = code;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_exit_fl)\n+\t\t\ttgt_j->pfn_exit_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t   code);\n+\n+\treturn code;\n+}\n+\n+void trace2_cmd_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t    va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\t/*\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy (because an 'ap' can only be walked once).\n+\t */\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_error_va_fl)\n+\t\t\ttgt_j->pfn_error_va_fl(file, line, fmt, ap);\n+}\n+\n+void trace2_cmd_path_fl(const char *file, int line, const char *pathname)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_command_path_fl)\n+\t\t\ttgt_j->pfn_command_path_fl(file, line, pathname);\n+}\n+\n+void trace2_cmd_name_fl(const char *file, int line, const char *name)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tconst char *hierarchy;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\ttr2_cmd_name_append_hierarchy(name);\n+\thierarchy = tr2_cmd_name_get_hierarchy();\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_command_name_fl)\n+\t\t\ttgt_j->pfn_command_name_fl(file, line, name, hierarchy);\n+}\n+\n+void trace2_cmd_mode_fl(const char *file, int line, const char *mode)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_command_mode_fl)\n+\t\t\ttgt_j->pfn_command_mode_fl(file, line, mode);\n+}\n+\n+void trace2_cmd_alias_fl(const char *file, int line, const char *alias,\n+\t\t\t const char **argv)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_alias_fl)\n+\t\t\ttgt_j->pfn_alias_fl(file, line, alias, argv);\n+}\n+\n+void trace2_cmd_list_config_fl(const char *file, int line)\n+{\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\ttr2_cfg_list_config_fl(file, line);\n+}\n+\n+void trace2_cmd_set_config_fl(const char *file, int line, const char *key,\n+\t\t\t      const char *value)\n+{\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\ttr2_cfg_set_fl(file, line, key, value);\n+}\n+\n+void trace2_child_start_fl(const char *file, int line,\n+\t\t\t   struct child_process *cmd)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tcmd->trace2_child_id = tr2tls_locked_increment(&tr2_next_child_id);\n+\tcmd->trace2_child_us_start = us_now;\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_child_start_fl)\n+\t\t\ttgt_j->pfn_child_start_fl(file, line,\n+\t\t\t\t\t\t  us_elapsed_absolute, cmd);\n+}\n+\n+void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd,\n+\t\t\t  int child_exit_code)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_child;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tif (cmd->trace2_child_us_start)\n+\t\tus_elapsed_child = us_now - cmd->trace2_child_us_start;\n+\telse\n+\t\tus_elapsed_child = 0;\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_child_exit_fl)\n+\t\t\ttgt_j->pfn_child_exit_fl(file, line,\n+\t\t\t\t\t\t us_elapsed_absolute,\n+\t\t\t\t\t\t cmd->trace2_child_id, cmd->pid,\n+\t\t\t\t\t\t child_exit_code,\n+\t\t\t\t\t\t us_elapsed_child);\n+}\n+\n+int trace2_exec_fl(const char *file, int line, const char *exe,\n+\t\t   const char **argv)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tint exec_id;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn -1;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\texec_id = tr2tls_locked_increment(&tr2_next_exec_id);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_exec_fl)\n+\t\t\ttgt_j->pfn_exec_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t   exec_id, exe, argv);\n+\n+\treturn exec_id;\n+}\n+\n+void trace2_exec_result_fl(const char *file, int line, int exec_id, int code)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_exec_result_fl)\n+\t\t\ttgt_j->pfn_exec_result_fl(\n+\t\t\t\tfile, line, us_elapsed_absolute, exec_id, code);\n+}\n+\n+void trace2_thread_start_fl(const char *file, int line, const char *thread_name)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tif (tr2tls_is_main_thread()) {\n+\t\t/*\n+\t\t * We should only be called from the new thread's thread-proc,\n+\t\t * so this is technically a bug.  But in those cases where the\n+\t\t * main thread also runs the thread-proc function (or when we\n+\t\t * are built with threading disabled), we need to allow it.\n+\t\t *\n+\t\t * Convert this call to a region-enter so the nesting looks\n+\t\t * correct.\n+\t\t */\n+\t\ttrace2_region_enter_printf_fl(file, line, NULL, NULL, NULL,\n+\t\t\t\t\t      \"thread-proc on main: %s\",\n+\t\t\t\t\t      thread_name);\n+\t\treturn;\n+\t}\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\ttr2tls_create_self(thread_name);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_thread_start_fl)\n+\t\t\ttgt_j->pfn_thread_start_fl(file, line,\n+\t\t\t\t\t\t   us_elapsed_absolute);\n+}\n+\n+void trace2_thread_exit_fl(const char *file, int line)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_thread;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tif (tr2tls_is_main_thread()) {\n+\t\t/*\n+\t\t * We should only be called from the exiting thread's\n+\t\t * thread-proc, so this is technically a bug.  But in\n+\t\t * those cases where the main thread also runs the\n+\t\t * thread-proc function (or when we are built with\n+\t\t * threading disabled), we need to allow it.\n+\t\t *\n+\t\t * Convert this call to a region-leave so the nesting\n+\t\t * looks correct.\n+\t\t */\n+\t\ttrace2_region_leave_printf_fl(file, line, NULL, NULL, NULL,\n+\t\t\t\t\t      \"thread-proc on main\");\n+\t\treturn;\n+\t}\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Clear any unbalanced regions and then get the relative time\n+\t * for the outer-most region (which we pushed when the thread\n+\t * started).  This gives us the run time of the thread.\n+\t */\n+\ttr2tls_pop_unwind_self();\n+\tus_elapsed_thread = tr2tls_region_elasped_self(us_now);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_thread_exit_fl)\n+\t\t\ttgt_j->pfn_thread_exit_fl(file, line,\n+\t\t\t\t\t\t  us_elapsed_absolute,\n+\t\t\t\t\t\t  us_elapsed_thread);\n+\n+\ttr2tls_unset_self();\n+}\n+\n+void trace2_def_param_fl(const char *file, int line, const char *param,\n+\t\t\t const char *value)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_param_fl)\n+\t\t\ttgt_j->pfn_param_fl(file, line, param, value);\n+}\n+\n+void trace2_def_repo_fl(const char *file, int line, struct repository *repo)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tif (repo->trace2_repo_id)\n+\t\treturn;\n+\n+\trepo->trace2_repo_id = tr2tls_locked_increment(&tr2_next_repo_id);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_repo_fl)\n+\t\t\ttgt_j->pfn_repo_fl(file, line, repo);\n+}\n+\n+void trace2_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category, const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Print the region-enter message at the current nesting\n+\t * (indentation) level and then push a new level.\n+\t *\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy.\n+\t */\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_region_enter_printf_va_fl)\n+\t\t\ttgt_j->pfn_region_enter_printf_va_fl(\n+\t\t\t\tfile, line, us_elapsed_absolute, category,\n+\t\t\t\tlabel, repo, fmt, ap);\n+\n+\ttr2tls_push_self(us_now);\n+}\n+\n+void trace2_region_enter_fl(const char *file, int line, const char *category,\n+\t\t\t    const char *label, const struct repository *repo)\n+{\n+\ttrace2_region_enter_printf_va_fl(file, line, category, label, repo,\n+\t\t\t\t\t NULL, NULL);\n+}\n+\n+void trace2_region_enter_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category, const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_enter_printf_va_fl(file, line, category, label, repo, fmt,\n+\t\t\t\t\t ap);\n+\tva_end(ap);\n+}\n+\n+#ifndef HAVE_VARIADIC_MACROS\n+void trace2_region_enter_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo, const char *fmt,\n+\t\t\t\t...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_enter_printf_va_fl(NULL, 0, category, label, repo, fmt,\n+\t\t\t\t\t ap);\n+\tva_end(ap);\n+}\n+#endif\n+\n+void trace2_region_leave_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category, const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_region;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * Get the elapsed time in the current region before we\n+\t * pop it off the stack.  Pop the stack.  And then print\n+\t * the perf message at the new (shallower) level so that\n+\t * it lines up with the corresponding push/enter.\n+\t */\n+\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n+\n+\ttr2tls_pop_self();\n+\n+\t/*\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy.\n+\t */\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_region_leave_printf_va_fl)\n+\t\t\ttgt_j->pfn_region_leave_printf_va_fl(\n+\t\t\t\tfile, line, us_elapsed_absolute,\n+\t\t\t\tus_elapsed_region, category, label, repo, fmt,\n+\t\t\t\tap);\n+}\n+\n+void trace2_region_leave_fl(const char *file, int line, const char *category,\n+\t\t\t    const char *label, const struct repository *repo)\n+{\n+\ttrace2_region_leave_printf_va_fl(file, line, category, label, repo,\n+\t\t\t\t\t NULL, NULL);\n+}\n+\n+void trace2_region_leave_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category, const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_leave_printf_va_fl(file, line, category, label, repo, fmt,\n+\t\t\t\t\t ap);\n+\tva_end(ap);\n+}\n+\n+#ifndef HAVE_VARIADIC_MACROS\n+void trace2_region_leave_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo, const char *fmt,\n+\t\t\t\t...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_region_leave_printf_va_fl(NULL, 0, category, label, repo, fmt,\n+\t\t\t\t\t ap);\n+\tva_end(ap);\n+}\n+#endif\n+\n+void trace2_data_string_fl(const char *file, int line, const char *category,\n+\t\t\t   const struct repository *repo, const char *key,\n+\t\t\t   const char *value)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_region;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_data_fl)\n+\t\t\ttgt_j->pfn_data_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t   us_elapsed_region, category, repo,\n+\t\t\t\t\t   key, value);\n+}\n+\n+void trace2_data_intmax_fl(const char *file, int line, const char *category,\n+\t\t\t   const struct repository *repo, const char *key,\n+\t\t\t   intmax_t value)\n+{\n+\tstruct strbuf buf_string = STRBUF_INIT;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tstrbuf_addf(&buf_string, \"%\" PRIdMAX, value);\n+\ttrace2_data_string_fl(file, line, category, repo, key, buf_string.buf);\n+\tstrbuf_release(&buf_string);\n+}\n+\n+void trace2_data_json_fl(const char *file, int line, const char *category,\n+\t\t\t const struct repository *repo, const char *key,\n+\t\t\t const struct json_writer *value)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\tuint64_t us_elapsed_region;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\tus_elapsed_region = tr2tls_region_elasped_self(us_now);\n+\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_data_fl)\n+\t\t\ttgt_j->pfn_data_json_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t\tus_elapsed_region, category,\n+\t\t\t\t\t\trepo, key, value);\n+}\n+\n+void trace2_printf_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t va_list ap)\n+{\n+\tstruct tr2_tgt *tgt_j;\n+\tint j;\n+\tuint64_t us_now;\n+\tuint64_t us_elapsed_absolute;\n+\n+\tif (!trace2_enabled)\n+\t\treturn;\n+\n+\tus_now = getnanotime() / 1000;\n+\tus_elapsed_absolute = tr2tls_absolute_elapsed(us_now);\n+\n+\t/*\n+\t * We expect each target function to treat 'ap' as constant\n+\t * and use va_copy.\n+\t */\n+\tfor_each_wanted_builtin (j, tgt_j)\n+\t\tif (tgt_j->pfn_printf_va_fl)\n+\t\t\ttgt_j->pfn_printf_va_fl(file, line, us_elapsed_absolute,\n+\t\t\t\t\t\tfmt, ap);\n+}\n+\n+void trace2_printf_fl(const char *file, int line, const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_printf_va_fl(file, line, fmt, ap);\n+\tva_end(ap);\n+}\n+\n+#ifndef HAVE_VARIADIC_MACROS\n+void trace2_printf(const char *fmt, ...)\n+{\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\ttrace2_printf_va_fl(NULL, 0, fmt, ap);\n+\tva_end(ap);\n+}\n+#endif\ndiff --git a/trace2.h b/trace2.h\nnew file mode 100644\nindex 0000000000..fce9891f53\n--- /dev/null\n+++ b/trace2.h\n@@ -0,0 +1,371 @@\n+#ifndef TRACE2_H\n+#define TRACE2_H\n+\n+struct child_process;\n+struct repository;\n+struct json_writer;\n+\n+/*\n+ * The public TRACE2 routines are grouped into the following groups:\n+ *\n+ * [] trace2_initialize -- initialization.\n+ * [] trace2_cmd_*      -- emit command/control messages.\n+ * [] trace2_child*     -- emit child start/stop messages.\n+ * [] trace2_exec*      -- emit exec start/stop messages.\n+ * [] trace2_thread*    -- emit thread start/stop messages.\n+ * [] trace2_def*       -- emit definition/parameter mesasges.\n+ * [] trace2_region*    -- emit region nesting messages.\n+ * [] trace2_data*      -- emit region/thread/repo data messages.\n+ * [] trace2_printf*    -- legacy trace[1] messages.\n+ */\n+\n+/*\n+ * Initialize TRACE2 tracing facility if any of the builtin TRACE2\n+ * targets are enabled in the environment.  Emits a 'version' event.\n+ *\n+ * Cleanup/Termination is handled automatically by a registered\n+ * atexit() routine.\n+ */\n+void trace2_initialize_fl(const char *file, int line);\n+\n+#define trace2_initialize() trace2_initialize_fl(__FILE__, __LINE__)\n+\n+/*\n+ * Return true if trace2 is enabled.\n+ */\n+int trace2_is_enabled(void);\n+\n+/*\n+ * Emit a 'start' event with the original (unmodified) argv.\n+ */\n+void trace2_cmd_start_fl(const char *file, int line, const char **argv);\n+\n+#define trace2_cmd_start(argv) trace2_cmd_start_fl(__FILE__, __LINE__, (argv))\n+\n+/*\n+ * Emit an 'exit' event.\n+ *\n+ * Write the exit-code that will be passed to exit() or returned\n+ * from main().\n+ *\n+ * Use this prior to actually calling exit().\n+ * See \"#define exit()\" in git-compat-util.h\n+ */\n+int trace2_cmd_exit_fl(const char *file, int line, int code);\n+\n+#define trace2_cmd_exit(code) (trace2_cmd_exit_fl(__FILE__, __LINE__, (code)))\n+\n+/*\n+ * Emit an 'error' event.\n+ *\n+ * Write an error message to the TRACE2 targets.\n+ */\n+void trace2_cmd_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t    va_list ap);\n+\n+#define trace2_cmd_error_va(fmt, ap) \\\n+\ttrace2_cmd_error_va_fl(__FILE__, __LINE__, (fmt), (ap))\n+\n+/*\n+ * Emit a 'pathname' event with the canonical pathname of the current process\n+ * This gives post-processors a simple field to identify the command without\n+ * having to parse the argv.  For example, to distinguish invocations from\n+ * installed versus debug executables.\n+ */\n+void trace2_cmd_path_fl(const char *file, int line, const char *pathname);\n+\n+#define trace2_cmd_path(p) trace2_cmd_path_fl(__FILE__, __LINE__, (p))\n+\n+/*\n+ * Emit a 'cmd_name' event with the canonical name of the command.\n+ * This gives post-processors a simple field to identify the command\n+ * without having to parse the argv.\n+ */\n+void trace2_cmd_name_fl(const char *file, int line, const char *name);\n+\n+#define trace2_cmd_name(v) trace2_cmd_name_fl(__FILE__, __LINE__, (v))\n+\n+/*\n+ * Emit a 'cmd_mode' event to further describe the command being run.\n+ * For example, \"checkout\" can checkout a single file or can checkout a\n+ * different branch.  This gives post-processors a simple field to compare\n+ * equivalent commands without having to parse the argv.\n+ */\n+void trace2_cmd_mode_fl(const char *file, int line, const char *mode);\n+\n+#define trace2_cmd_mode(sv) trace2_cmd_mode_fl(__FILE__, __LINE__, (sv))\n+\n+/*\n+ * Emit an 'alias' expansion event.\n+ */\n+void trace2_cmd_alias_fl(const char *file, int line, const char *alias,\n+\t\t\t const char **argv);\n+\n+#define trace2_cmd_alias(alias, argv) \\\n+\ttrace2_cmd_alias_fl(__FILE__, __LINE__, (alias), (argv))\n+\n+/*\n+ * Emit one or more 'def_param' events for \"interesting\" configuration\n+ * settings.\n+ *\n+ * The environment variable \"GIT_TR2_CONFIG_PARAMS\" can be set to a\n+ * list of patterns considered important.  For example:\n+ *\n+ *    GIT_TR2_CONFIG_PARAMS=\"core.*,remote.*.url\"\n+ *\n+ * Note: this routine does a read-only iteration on the config data\n+ * (using read_early_config()), so it must not be called until enough\n+ * of the process environment has been established.  This includes the\n+ * location of the git and worktree directories, expansion of any \"-c\"\n+ * and \"-C\" command line options, and etc.\n+ */\n+void trace2_cmd_list_config_fl(const char *file, int line);\n+\n+#define trace2_cmd_list_config() trace2_cmd_list_config_fl(__FILE__, __LINE__)\n+\n+/*\n+ * Emit a \"def_param\" event for the given config key/value pair IF\n+ * we consider the key to be \"interesting\".\n+ *\n+ * Use this for new/updated config settings created/updated after\n+ * trace2_cmd_list_config() is called.\n+ */\n+void trace2_cmd_set_config_fl(const char *file, int line, const char *key,\n+\t\t\t      const char *value);\n+\n+#define trace2_cmd_set_config(k, v) \\\n+\ttrace2_cmd_set_config_fl(__FILE__, __LINE__, (k), (v))\n+\n+/*\n+ * Emit a 'child_start' event prior to spawning a child process.\n+ *\n+ * Before calling optionally set \"cmd->trace2_child_class\" to a string\n+ * describing the type of the child process.  For example, \"editor\" or\n+ * \"pager\".\n+ */\n+void trace2_child_start_fl(const char *file, int line,\n+\t\t\t   struct child_process *cmd);\n+\n+#define trace2_child_start(cmd) trace2_child_start_fl(__FILE__, __LINE__, (cmd))\n+\n+/*\n+ * Emit a 'child_exit' event after the child process completes.\n+ */\n+void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd,\n+\t\t\t  int child_exit_code);\n+\n+#define trace2_child_exit(cmd, code) \\\n+\ttrace2_child_exit_fl(__FILE__, __LINE__, (cmd), (code))\n+\n+/*\n+ * Emit an 'exec' event prior to calling one of exec(), execv(),\n+ * execvp(), and etc.  On Unix-derived systems, this will be the\n+ * last event emitted for the current process, unless the exec\n+ * fails.  On Windows, exec() behaves like 'child_start' and a\n+ * waitpid(), so additional events may be emitted.\n+ *\n+ * Returns the \"exec_id\".\n+ */\n+int trace2_exec_fl(const char *file, int line, const char *exe,\n+\t\t   const char **argv);\n+\n+#define trace2_exec(exe, argv) trace2_exec_fl(__FILE__, __LINE__, (exe), (argv))\n+\n+/*\n+ * Emit an 'exec_result' when possible.  On Unix-derived systems,\n+ * this should be called after exec() returns (which only happens\n+ * when there is an error starting the new process).  On Windows,\n+ * this should be called after the waitpid().\n+ *\n+ * The \"exec_id\" should be the value returned from trace2_exec().\n+ */\n+void trace2_exec_result_fl(const char *file, int line, int exec_id, int code);\n+\n+#define trace2_exec_result(id, code) \\\n+\ttrace2_exec_result_fl(__FILE__, __LINE__, (id), (code))\n+\n+/*\n+ * Emit a 'thread_start' event.  This must be called from inside the\n+ * thread-proc to set up the trace2 TLS data for the thread.\n+ *\n+ * Thread names should be descriptive, like \"preload_index\".\n+ * Thread names will be decorated with an instance number automatically.\n+ */\n+void trace2_thread_start_fl(const char *file, int line,\n+\t\t\t    const char *thread_name);\n+\n+#define trace2_thread_start(thread_name) \\\n+\ttrace2_thread_start_fl(__FILE__, __LINE__, (thread_name))\n+\n+/*\n+ * Emit a 'thread_exit' event.  This must be called from inside the\n+ * thread-proc to report thread-specific data and cleanup TLS data\n+ * for the thread.\n+ */\n+void trace2_thread_exit_fl(const char *file, int line);\n+\n+#define trace2_thread_exit() trace2_thread_exit_fl(__FILE__, __LINE__)\n+\n+/*\n+ * Emit a 'param' event.\n+ *\n+ * Write a \"<param> = <value>\" pair describing some aspect of the\n+ * run such as an important configuration setting or command line\n+ * option that significantly changes command behavior.\n+ */\n+void trace2_def_param_fl(const char *file, int line, const char *param,\n+\t\t\t const char *value);\n+\n+#define trace2_def_param(param, value) \\\n+\ttrace2_def_param_fl(__FILE__, __LINE__, (param), (value))\n+\n+/*\n+ * Tell trace2 about a newly instantiated repo object and assign\n+ * a trace2-repo-id to be used in subsequent activity events.\n+ *\n+ * Emits a 'worktree' event for this repo instance.\n+ */\n+void trace2_def_repo_fl(const char *file, int line, struct repository *repo);\n+\n+#define trace2_def_repo(repo) trace2_def_repo_fl(__FILE__, __LINE__, repo)\n+\n+/*\n+ * Emit a 'region_enter' event for <category>.<label> with optional\n+ * repo-id and printf message.\n+ *\n+ * Enter a new nesting level on the current thread and remember the\n+ * current time.  This controls the indenting of all subsequent events\n+ * on this thread.\n+ */\n+void trace2_region_enter_fl(const char *file, int line, const char *category,\n+\t\t\t    const char *label, const struct repository *repo);\n+\n+#define trace2_region_enter(category, label, repo) \\\n+\ttrace2_region_enter_fl(__FILE__, __LINE__, (category), (label), (repo))\n+\n+void trace2_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category, const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap);\n+\n+#define trace2_region_enter_printf_va(category, label, repo, fmt, ap)    \\\n+\ttrace2_region_enter_printf_va_fl(__FILE__, __LINE__, (category), \\\n+\t\t\t\t\t (label), (repo), (fmt), (ap))\n+\n+void trace2_region_enter_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category, const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...);\n+\n+#ifdef HAVE_VARIADIC_MACROS\n+#define trace2_region_enter_printf(category, label, repo, ...)                 \\\n+\ttrace2_region_enter_printf_fl(__FILE__, __LINE__, (category), (label), \\\n+\t\t\t\t      (repo), __VA_ARGS__)\n+#else\n+/* clang-format off */\n+__attribute__((format (region_enter_printf, 4, 5)))\n+void trace2_region_enter_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo, const char *fmt,\n+\t\t\t\t...);\n+/* clang-format on */\n+#endif\n+\n+/*\n+ * Emit a 'region_leave' event for <category>.<label> with optional\n+ * repo-id and printf message.\n+ *\n+ * Leave current nesting level and report the elapsed time spent\n+ * in this nesting level.\n+ */\n+void trace2_region_leave_fl(const char *file, int line, const char *category,\n+\t\t\t    const char *label, const struct repository *repo);\n+\n+#define trace2_region_leave(category, label, repo) \\\n+\ttrace2_region_leave_fl(__FILE__, __LINE__, (category), (label), (repo))\n+\n+void trace2_region_leave_printf_va_fl(const char *file, int line,\n+\t\t\t\t      const char *category, const char *label,\n+\t\t\t\t      const struct repository *repo,\n+\t\t\t\t      const char *fmt, va_list ap);\n+\n+#define trace2_region_leave_printf_va(category, label, repo, fmt, ap)    \\\n+\ttrace2_region_leave_printf_va_fl(__FILE__, __LINE__, (category), \\\n+\t\t\t\t\t (label), (repo), (fmt), (ap))\n+\n+void trace2_region_leave_printf_fl(const char *file, int line,\n+\t\t\t\t   const char *category, const char *label,\n+\t\t\t\t   const struct repository *repo,\n+\t\t\t\t   const char *fmt, ...);\n+\n+#ifdef HAVE_VARIADIC_MACROS\n+#define trace2_region_leave_printf(category, label, repo, ...)                 \\\n+\ttrace2_region_leave_printf_fl(__FILE__, __LINE__, (category), (label), \\\n+\t\t\t\t      (repo), __VA_ARGS__)\n+#else\n+/* clang-format off */\n+__attribute__((format (region_leave_printf, 4, 5)))\n+void trace2_region_leave_printf(const char *category, const char *label,\n+\t\t\t\tconst struct repository *repo, const char *fmt,\n+\t\t\t\t...);\n+/* clang-format on */\n+#endif\n+\n+/*\n+ * Emit a key-value pair 'data' event of the form <category>.<key> = <value>.\n+ * This event implicitly contains information about thread, nesting region,\n+ * and optional repo-id.\n+ *\n+ * On event-based TRACE2 targets, this generates a 'data' event suitable\n+ * for post-processing.  On printf-based TRACE2 targets, this is converted\n+ * into a fixed-format printf message.\n+ */\n+void trace2_data_string_fl(const char *file, int line, const char *category,\n+\t\t\t   const struct repository *repo, const char *key,\n+\t\t\t   const char *value);\n+\n+#define trace2_data_string(category, repo, key, value)                       \\\n+\ttrace2_data_string_fl(__FILE__, __LINE__, (category), (repo), (key), \\\n+\t\t\t      (value))\n+\n+void trace2_data_intmax_fl(const char *file, int line, const char *category,\n+\t\t\t   const struct repository *repo, const char *key,\n+\t\t\t   intmax_t value);\n+\n+#define trace2_data_intmax(category, repo, key, value)                       \\\n+\ttrace2_data_intmax_fl(__FILE__, __LINE__, (category), (repo), (key), \\\n+\t\t\t      (value))\n+\n+void trace2_data_json_fl(const char *file, int line, const char *category,\n+\t\t\t const struct repository *repo, const char *key,\n+\t\t\t const struct json_writer *jw);\n+\n+#define trace2_data_json(category, repo, key, value)                       \\\n+\ttrace2_data_json_fl(__FILE__, __LINE__, (category), (repo), (key), \\\n+\t\t\t    (value))\n+\n+/*\n+ * Emit a 'printf' event.\n+ *\n+ * Write an arbitrary formatted message to the TRACE2 targets.  These\n+ * text messages should be considered as human-readable strings without\n+ * any formatting guidelines.  Post-processors may choose to ignore\n+ * them.\n+ */\n+void trace2_printf_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t va_list ap);\n+\n+#define trace2_printf_va(fmt, ap) \\\n+\ttrace2_printf_va_fl(__FILE__, __LINE__, (fmt), (ap))\n+\n+void trace2_printf_fl(const char *file, int line, const char *fmt, ...);\n+\n+#ifdef HAVE_VARIADIC_MACROS\n+#define trace2_printf(...) trace2_printf_fl(__FILE__, __LINE__, __VA_ARGS__)\n+#else\n+/* clang-format off */\n+__attribute__((format (printf, 1, 2)))\n+void trace2_printf(const char *fmt, ...);\n+/* clang-format on */\n+#endif\n+\n+#endif /* TRACE2_H */\ndiff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c\nnew file mode 100644\nindex 0000000000..b329921ac5\n--- /dev/null\n+++ b/trace2/tr2_cfg.c\n@@ -0,0 +1,90 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"tr2_cfg.h\"\n+\n+#define TR2_ENVVAR_CFG_PARAM \"GIT_TR2_CONFIG_PARAMS\"\n+\n+static struct strbuf **tr2_cfg_patterns;\n+static int tr2_cfg_count_patterns;\n+static int tr2_cfg_loaded;\n+\n+/*\n+ * Parse a string containing a comma-delimited list of config keys\n+ * or wildcard patterns into a list of strbufs.\n+ */\n+static int tr2_cfg_load_patterns(void)\n+{\n+\tstruct strbuf **s;\n+\tconst char *envvar;\n+\n+\tif (tr2_cfg_loaded)\n+\t\treturn tr2_cfg_count_patterns;\n+\ttr2_cfg_loaded = 1;\n+\n+\tenvvar = getenv(TR2_ENVVAR_CFG_PARAM);\n+\tif (!envvar || !*envvar)\n+\t\treturn tr2_cfg_count_patterns;\n+\n+\ttr2_cfg_patterns = strbuf_split_buf(envvar, strlen(envvar), ',', -1);\n+\tfor (s = tr2_cfg_patterns; *s; s++) {\n+\t\tstruct strbuf *buf = *s;\n+\n+\t\tif (buf->len && buf->buf[buf->len - 1] == ',')\n+\t\t\tstrbuf_setlen(buf, buf->len - 1);\n+\t\tstrbuf_trim_trailing_newline(*s);\n+\t\tstrbuf_trim(*s);\n+\t}\n+\n+\ttr2_cfg_count_patterns = s - tr2_cfg_patterns;\n+\treturn tr2_cfg_count_patterns;\n+}\n+\n+void tr2_cfg_free_patterns(void)\n+{\n+\tif (tr2_cfg_patterns)\n+\t\tstrbuf_list_free(tr2_cfg_patterns);\n+\ttr2_cfg_count_patterns = 0;\n+\ttr2_cfg_loaded = 0;\n+}\n+\n+struct tr2_cfg_data {\n+\tconst char *file;\n+\tint line;\n+};\n+\n+/*\n+ * See if the given config key matches any of our patterns of interest.\n+ */\n+static int tr2_cfg_cb(const char *key, const char *value, void *d)\n+{\n+\tstruct strbuf **s;\n+\tstruct tr2_cfg_data *data = (struct tr2_cfg_data *)d;\n+\n+\tfor (s = tr2_cfg_patterns; *s; s++) {\n+\t\tstruct strbuf *buf = *s;\n+\t\tint wm = wildmatch(buf->buf, key, WM_CASEFOLD);\n+\t\tif (wm == WM_MATCH) {\n+\t\t\ttrace2_def_param_fl(data->file, data->line, key, value);\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void tr2_cfg_list_config_fl(const char *file, int line)\n+{\n+\tstruct tr2_cfg_data data = { file, line };\n+\n+\tif (tr2_cfg_load_patterns() > 0)\n+\t\tread_early_config(tr2_cfg_cb, &data);\n+}\n+\n+void tr2_cfg_set_fl(const char *file, int line, const char *key,\n+\t\t    const char *value)\n+{\n+\tstruct tr2_cfg_data data = { file, line };\n+\n+\tif (tr2_cfg_load_patterns() > 0)\n+\t\ttr2_cfg_cb(key, value, &data);\n+}\ndiff --git a/trace2/tr2_cfg.h b/trace2/tr2_cfg.h\nnew file mode 100644\nindex 0000000000..d9c98f64dd\n--- /dev/null\n+++ b/trace2/tr2_cfg.h\n@@ -0,0 +1,19 @@\n+#ifndef TR2_CFG_H\n+#define TR2_CFG_H\n+\n+/*\n+ * Iterate over all config settings and emit 'def_param' events for the\n+ * \"interesting\" ones to TRACE2.\n+ */\n+void tr2_cfg_list_config_fl(const char *file, int line);\n+\n+/*\n+ * Emit a \"def_param\" event for the given key/value pair IF we consider\n+ * the key to be \"interesting\".\n+ */\n+void tr2_cfg_set_fl(const char *file, int line, const char *key,\n+\t\t    const char *value);\n+\n+void tr2_cfg_free_patterns(void);\n+\n+#endif /* TR2_CFG_H */\ndiff --git a/trace2/tr2_cmd_name.c b/trace2/tr2_cmd_name.c\nnew file mode 100644\nindex 0000000000..e999592b4c\n--- /dev/null\n+++ b/trace2/tr2_cmd_name.c\n@@ -0,0 +1,30 @@\n+#include \"cache.h\"\n+#include \"trace2/tr2_cmd_name.h\"\n+\n+#define TR2_ENVVAR_PARENT_NAME \"GIT_TR2_PARENT_NAME\"\n+\n+static struct strbuf tr2cmdname_hierarchy = STRBUF_INIT;\n+\n+void tr2_cmd_name_append_hierarchy(const char *name)\n+{\n+\tconst char *parent_name = getenv(TR2_ENVVAR_PARENT_NAME);\n+\n+\tstrbuf_reset(&tr2cmdname_hierarchy);\n+\tif (parent_name && *parent_name) {\n+\t\tstrbuf_addstr(&tr2cmdname_hierarchy, parent_name);\n+\t\tstrbuf_addch(&tr2cmdname_hierarchy, '/');\n+\t}\n+\tstrbuf_addstr(&tr2cmdname_hierarchy, name);\n+\n+\tsetenv(TR2_ENVVAR_PARENT_NAME, tr2cmdname_hierarchy.buf, 1);\n+}\n+\n+const char *tr2_cmd_name_get_hierarchy(void)\n+{\n+\treturn tr2cmdname_hierarchy.buf;\n+}\n+\n+void tr2_cmd_name_release(void)\n+{\n+\tstrbuf_release(&tr2cmdname_hierarchy);\n+}\ndiff --git a/trace2/tr2_cmd_name.h b/trace2/tr2_cmd_name.h\nnew file mode 100644\nindex 0000000000..ab70b67a8e\n--- /dev/null\n+++ b/trace2/tr2_cmd_name.h\n@@ -0,0 +1,24 @@\n+#ifndef TR2_CMD_NAME_H\n+#define TR2_CMD_NAME_H\n+\n+/*\n+ * Append the current command name to the list being maintained\n+ * in the environment.\n+ *\n+ * The hierarchy for a top-level git command is just the current\n+ * command name.  For a child git process, the hierarchy includes the\n+ * names of the parent processes.\n+ *\n+ * The hierarchy for the current process will be exported to the\n+ * environment and inherited by child processes.\n+ */\n+void tr2_cmd_name_append_hierarchy(const char *name);\n+\n+/*\n+ * Get the command name hierarchy for the current process.\n+ */\n+const char *tr2_cmd_name_get_hierarchy(void);\n+\n+void tr2_cmd_name_release(void);\n+\n+#endif /* TR2_CMD_NAME_H */\ndiff --git a/trace2/tr2_dst.c b/trace2/tr2_dst.c\nnew file mode 100644\nindex 0000000000..fd490a43ad\n--- /dev/null\n+++ b/trace2/tr2_dst.c\n@@ -0,0 +1,252 @@\n+#include \"cache.h\"\n+#include \"trace2/tr2_dst.h\"\n+\n+/*\n+ * If a Trace2 target cannot be opened for writing, we should issue a\n+ * warning to stderr, but this is very annoying if the target is a pipe\n+ * or socket and beyond the user's control -- especially since every\n+ * git command (and sub-command) will print the message.  So we silently\n+ * eat these warnings and just discard the trace data.\n+ *\n+ * Enable the following environment variable to see these warnings.\n+ */\n+#define TR2_ENVVAR_DST_DEBUG \"GIT_TR2_DST_DEBUG\"\n+\n+static int tr2_dst_want_warning(void)\n+{\n+\tstatic int tr2env_dst_debug = -1;\n+\n+\tif (tr2env_dst_debug == -1) {\n+\t\tconst char *env_value = getenv(TR2_ENVVAR_DST_DEBUG);\n+\t\tif (!env_value || !*env_value)\n+\t\t\ttr2env_dst_debug = 0;\n+\t\telse\n+\t\t\ttr2env_dst_debug = atoi(env_value) > 0;\n+\t}\n+\n+\treturn tr2env_dst_debug;\n+}\n+\n+void tr2_dst_trace_disable(struct tr2_dst *dst)\n+{\n+\tif (dst->need_close)\n+\t\tclose(dst->fd);\n+\tdst->fd = 0;\n+\tdst->initialized = 1;\n+\tdst->need_close = 0;\n+}\n+\n+static int tr2_dst_try_path(struct tr2_dst *dst, const char *tgt_value)\n+{\n+\tint fd = open(tgt_value, O_WRONLY | O_APPEND | O_CREAT, 0666);\n+\tif (fd == -1) {\n+\t\tif (tr2_dst_want_warning())\n+\t\t\twarning(\"trace2: could not open '%s' for '%s' tracing: %s\",\n+\t\t\t\ttgt_value, dst->env_var_name, strerror(errno));\n+\n+\t\ttr2_dst_trace_disable(dst);\n+\t\treturn 0;\n+\t}\n+\n+\tdst->fd = fd;\n+\tdst->need_close = 1;\n+\tdst->initialized = 1;\n+\n+\treturn dst->fd;\n+}\n+\n+#ifndef NO_UNIX_SOCKETS\n+#define PREFIX_AF_UNIX \"af_unix:\"\n+#define PREFIX_AF_UNIX_STREAM \"af_unix:stream:\"\n+#define PREFIX_AF_UNIX_DGRAM \"af_unix:dgram:\"\n+\n+static int tr2_dst_try_uds_connect(const char *path, int sock_type, int *out_fd)\n+{\n+\tint fd;\n+\tstruct sockaddr_un sa;\n+\n+\tfd = socket(AF_UNIX, sock_type, 0);\n+\tif (fd == -1)\n+\t\treturn errno;\n+\n+\tsa.sun_family = AF_UNIX;\n+\tstrlcpy(sa.sun_path, path, sizeof(sa.sun_path));\n+\n+\tif (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {\n+\t\tint e = errno;\n+\t\tclose(fd);\n+\t\treturn e;\n+\t}\n+\n+\t*out_fd = fd;\n+\treturn 0;\n+}\n+\n+#define TR2_DST_UDS_TRY_STREAM (1 << 0)\n+#define TR2_DST_UDS_TRY_DGRAM  (1 << 1)\n+\n+static int tr2_dst_try_unix_domain_socket(struct tr2_dst *dst,\n+\t\t\t\t\t  const char *tgt_value)\n+{\n+\tunsigned int uds_try = 0;\n+\tint fd;\n+\tint e;\n+\tconst char *path = NULL;\n+\n+\t/*\n+\t * Allow \"af_unix:[<type>:]<absolute_path>\"\n+\t *\n+\t * Trace2 always writes complete individual messages (without\n+\t * chunking), so we can talk to either DGRAM or STREAM type sockets.\n+\t *\n+\t * Allow the user to explicitly request the socket type.\n+\t *\n+\t * If they omit the socket type, try one and then the other.\n+\t */\n+\n+\tif (skip_prefix(tgt_value, PREFIX_AF_UNIX_STREAM, &path))\n+\t\tuds_try |= TR2_DST_UDS_TRY_STREAM;\n+\n+\telse if (skip_prefix(tgt_value, PREFIX_AF_UNIX_DGRAM, &path))\n+\t\tuds_try |= TR2_DST_UDS_TRY_DGRAM;\n+\n+\telse if (skip_prefix(tgt_value, PREFIX_AF_UNIX, &path))\n+\t\tuds_try |= TR2_DST_UDS_TRY_STREAM | TR2_DST_UDS_TRY_DGRAM;\n+\n+\tif (!path || !*path) {\n+\t\tif (tr2_dst_want_warning())\n+\t\t\twarning(\"trace2: invalid AF_UNIX value '%s' for '%s' tracing\",\n+\t\t\t\ttgt_value, dst->env_var_name);\n+\n+\t\ttr2_dst_trace_disable(dst);\n+\t\treturn 0;\n+\t}\n+\n+\tif (!is_absolute_path(path) ||\n+\t    strlen(path) >= sizeof(((struct sockaddr_un *)0)->sun_path)) {\n+\t\tif (tr2_dst_want_warning())\n+\t\t\twarning(\"trace2: invalid AF_UNIX path '%s' for '%s' tracing\",\n+\t\t\t\tpath, dst->env_var_name);\n+\n+\t\ttr2_dst_trace_disable(dst);\n+\t\treturn 0;\n+\t}\n+\n+\tif (uds_try & TR2_DST_UDS_TRY_STREAM) {\n+\t\te = tr2_dst_try_uds_connect(path, SOCK_STREAM, &fd);\n+\t\tif (!e)\n+\t\t\tgoto connected;\n+\t\tif (e != EPROTOTYPE)\n+\t\t\tgoto error;\n+\t}\n+\tif (uds_try & TR2_DST_UDS_TRY_DGRAM) {\n+\t\te = tr2_dst_try_uds_connect(path, SOCK_DGRAM, &fd);\n+\t\tif (!e)\n+\t\t\tgoto connected;\n+\t}\n+\n+error:\n+\tif (tr2_dst_want_warning())\n+\t\twarning(\"trace2: could not connect to socket '%s' for '%s' tracing: %s\",\n+\t\t\tpath, dst->env_var_name, strerror(e));\n+\n+\ttr2_dst_trace_disable(dst);\n+\treturn 0;\n+\n+connected:\n+\tdst->fd = fd;\n+\tdst->need_close = 1;\n+\tdst->initialized = 1;\n+\n+\treturn dst->fd;\n+}\n+#endif\n+\n+static void tr2_dst_malformed_warning(struct tr2_dst *dst,\n+\t\t\t\t      const char *tgt_value)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf, \"trace2: unknown value for '%s': '%s'\",\n+\t\t    dst->env_var_name, tgt_value);\n+\twarning(\"%s\", buf.buf);\n+\n+\tstrbuf_release(&buf);\n+}\n+\n+int tr2_dst_get_trace_fd(struct tr2_dst *dst)\n+{\n+\tconst char *tgt_value;\n+\n+\t/* don't open twice */\n+\tif (dst->initialized)\n+\t\treturn dst->fd;\n+\n+\tdst->initialized = 1;\n+\n+\ttgt_value = getenv(dst->env_var_name);\n+\n+\tif (!tgt_value || !strcmp(tgt_value, \"\") || !strcmp(tgt_value, \"0\") ||\n+\t    !strcasecmp(tgt_value, \"false\")) {\n+\t\tdst->fd = 0;\n+\t\treturn dst->fd;\n+\t}\n+\n+\tif (!strcmp(tgt_value, \"1\") || !strcasecmp(tgt_value, \"true\")) {\n+\t\tdst->fd = STDERR_FILENO;\n+\t\treturn dst->fd;\n+\t}\n+\n+\tif (strlen(tgt_value) == 1 && isdigit(*tgt_value)) {\n+\t\tdst->fd = atoi(tgt_value);\n+\t\treturn dst->fd;\n+\t}\n+\n+\tif (is_absolute_path(tgt_value))\n+\t\treturn tr2_dst_try_path(dst, tgt_value);\n+\n+#ifndef NO_UNIX_SOCKETS\n+\tif (starts_with(tgt_value, PREFIX_AF_UNIX))\n+\t\treturn tr2_dst_try_unix_domain_socket(dst, tgt_value);\n+#endif\n+\n+\t/* Always warn about malformed values. */\n+\ttr2_dst_malformed_warning(dst, tgt_value);\n+\ttr2_dst_trace_disable(dst);\n+\treturn 0;\n+}\n+\n+int tr2_dst_trace_want(struct tr2_dst *dst)\n+{\n+\treturn !!tr2_dst_get_trace_fd(dst);\n+}\n+\n+void tr2_dst_write_line(struct tr2_dst *dst, struct strbuf *buf_line)\n+{\n+\tint fd = tr2_dst_get_trace_fd(dst);\n+\n+\tstrbuf_complete_line(buf_line); /* ensure final NL on buffer */\n+\n+\t/*\n+\t * We do not use write_in_full() because we do not want\n+\t * a short-write to try again.  We are using O_APPEND mode\n+\t * files and the kernel handles the atomic seek+write. If\n+\t * another thread or git process is concurrently writing to\n+\t * this fd or file, our remainder-write may not be contiguous\n+\t * with our initial write of this message.  And that will\n+\t * confuse readers.  So just don't bother.\n+\t *\n+\t * It is assumed that TRACE2 messages are short enough that\n+\t * the system can write them in 1 attempt and we won't see\n+\t * a short-write.\n+\t *\n+\t * If we get an IO error, just close the trace dst.\n+\t */\n+\tif (write(fd, buf_line->buf, buf_line->len) >= 0)\n+\t\treturn;\n+\n+\tif (tr2_dst_want_warning())\n+\t\twarning(\"unable to write trace to '%s': %s\", dst->env_var_name,\n+\t\t\tstrerror(errno));\n+\ttr2_dst_trace_disable(dst);\n+}\ndiff --git a/trace2/tr2_dst.h b/trace2/tr2_dst.h\nnew file mode 100644\nindex 0000000000..9a64f05b02\n--- /dev/null\n+++ b/trace2/tr2_dst.h\n@@ -0,0 +1,36 @@\n+#ifndef TR2_DST_H\n+#define TR2_DST_H\n+\n+struct strbuf;\n+\n+struct tr2_dst {\n+\tconst char *const env_var_name;\n+\tint fd;\n+\tunsigned int initialized : 1;\n+\tunsigned int need_close : 1;\n+};\n+\n+/*\n+ * Disable TRACE2 on the destination.  In TRACE2 a destination (DST)\n+ * wraps a file descriptor; it is associated with a TARGET which\n+ * defines the formatting.\n+ */\n+void tr2_dst_trace_disable(struct tr2_dst *dst);\n+\n+/*\n+ * Return the file descriptor for the DST.\n+ * If 0, the dst is closed or disabled.\n+ */\n+int tr2_dst_get_trace_fd(struct tr2_dst *dst);\n+\n+/*\n+ * Return true if the DST is opened for writing.\n+ */\n+int tr2_dst_trace_want(struct tr2_dst *dst);\n+\n+/*\n+ * Write a single line/message to the trace file.\n+ */\n+void tr2_dst_write_line(struct tr2_dst *dst, struct strbuf *buf_line);\n+\n+#endif /* TR2_DST_H */\ndiff --git a/trace2/tr2_sid.c b/trace2/tr2_sid.c\nnew file mode 100644\nindex 0000000000..984524a43c\n--- /dev/null\n+++ b/trace2/tr2_sid.c\n@@ -0,0 +1,67 @@\n+#include \"cache.h\"\n+#include \"trace2/tr2_sid.h\"\n+\n+#define TR2_ENVVAR_PARENT_SID \"GIT_TR2_PARENT_SID\"\n+\n+static struct strbuf tr2sid_buf = STRBUF_INIT;\n+static int tr2sid_nr_git_parents;\n+\n+/*\n+ * Compute a \"unique\" session id (SID) for the current process.  This allows\n+ * all events from this process to have a single label (much like a PID).\n+ *\n+ * Export this into our environment so that all child processes inherit it.\n+ *\n+ * If we were started by another git instance, use our parent's SID as a\n+ * prefix.  (This lets us track parent/child relationships even if there\n+ * is an intermediate shell process.)\n+ *\n+ * Additionally, count the number of nested git processes.\n+ */\n+static void tr2_sid_compute(void)\n+{\n+\tuint64_t us_now;\n+\tconst char *parent_sid;\n+\n+\tif (tr2sid_buf.len)\n+\t\treturn;\n+\n+\tparent_sid = getenv(TR2_ENVVAR_PARENT_SID);\n+\tif (parent_sid && *parent_sid) {\n+\t\tconst char *p;\n+\t\tfor (p = parent_sid; *p; p++)\n+\t\t\tif (*p == '/')\n+\t\t\t\ttr2sid_nr_git_parents++;\n+\n+\t\tstrbuf_addstr(&tr2sid_buf, parent_sid);\n+\t\tstrbuf_addch(&tr2sid_buf, '/');\n+\t\ttr2sid_nr_git_parents++;\n+\t}\n+\n+\tus_now = getnanotime() / 1000;\n+\tstrbuf_addf(&tr2sid_buf, \"%\" PRIuMAX \"-%\" PRIdMAX, (uintmax_t)us_now,\n+\t\t    (intmax_t)getpid());\n+\n+\tsetenv(TR2_ENVVAR_PARENT_SID, tr2sid_buf.buf, 1);\n+}\n+\n+const char *tr2_sid_get(void)\n+{\n+\tif (!tr2sid_buf.len)\n+\t\ttr2_sid_compute();\n+\n+\treturn tr2sid_buf.buf;\n+}\n+\n+int tr2_sid_depth(void)\n+{\n+\tif (!tr2sid_buf.len)\n+\t\ttr2_sid_compute();\n+\n+\treturn tr2sid_nr_git_parents;\n+}\n+\n+void tr2_sid_release(void)\n+{\n+\tstrbuf_release(&tr2sid_buf);\n+}\ndiff --git a/trace2/tr2_sid.h b/trace2/tr2_sid.h\nnew file mode 100644\nindex 0000000000..9bef321708\n--- /dev/null\n+++ b/trace2/tr2_sid.h\n@@ -0,0 +1,18 @@\n+#ifndef TR2_SID_H\n+#define TR2_SID_H\n+\n+/*\n+ * Get our session id. Compute if necessary.\n+ */\n+const char *tr2_sid_get(void);\n+\n+/*\n+ * Get our process depth.  A top-level git process invoked from the\n+ * command line will have depth=0.  A child git process will have\n+ * depth=1 and so on.\n+ */\n+int tr2_sid_depth(void);\n+\n+void tr2_sid_release(void);\n+\n+#endif /* TR2_SID_H */\ndiff --git a/trace2/tr2_tbuf.c b/trace2/tr2_tbuf.c\nnew file mode 100644\nindex 0000000000..0844910423\n--- /dev/null\n+++ b/trace2/tr2_tbuf.c\n@@ -0,0 +1,32 @@\n+#include \"cache.h\"\n+#include \"tr2_tbuf.h\"\n+\n+void tr2_tbuf_local_time(struct tr2_tbuf *tb)\n+{\n+\tstruct timeval tv;\n+\tstruct tm tm;\n+\ttime_t secs;\n+\n+\tgettimeofday(&tv, NULL);\n+\tsecs = tv.tv_sec;\n+\tlocaltime_r(&secs, &tm);\n+\n+\txsnprintf(tb->buf, sizeof(tb->buf), \"%02d:%02d:%02d.%06ld\", tm.tm_hour,\n+\t\t  tm.tm_min, tm.tm_sec, (long)tv.tv_usec);\n+}\n+\n+void tr2_tbuf_utc_time(struct tr2_tbuf *tb)\n+{\n+\tstruct timeval tv;\n+\tstruct tm tm;\n+\ttime_t secs;\n+\n+\tgettimeofday(&tv, NULL);\n+\tsecs = tv.tv_sec;\n+\tgmtime_r(&secs, &tm);\n+\n+\txsnprintf(tb->buf, sizeof(tb->buf),\n+\t\t  \"%4d-%02d-%02d %02d:%02d:%02d.%06ld\", tm.tm_year + 1900,\n+\t\t  tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,\n+\t\t  (long)tv.tv_usec);\n+}\ndiff --git a/trace2/tr2_tbuf.h b/trace2/tr2_tbuf.h\nnew file mode 100644\nindex 0000000000..9cdefa3957\n--- /dev/null\n+++ b/trace2/tr2_tbuf.h\n@@ -0,0 +1,23 @@\n+#ifndef TR2_TBUF_H\n+#define TR2_TBUF_H\n+\n+/*\n+ * A simple wrapper around a fixed buffer to avoid C syntax\n+ * quirks and the need to pass around an additional size_t\n+ * argument.\n+ */\n+struct tr2_tbuf {\n+\tchar buf[32];\n+};\n+\n+/*\n+ * Fill buffer with formatted local time string.\n+ */\n+void tr2_tbuf_local_time(struct tr2_tbuf *tb);\n+\n+/*\n+ * Fill buffer with formatted UTC time string.\n+ */\n+void tr2_tbuf_utc_time(struct tr2_tbuf *tb);\n+\n+#endif /* TR2_TBUF_H */\ndiff --git a/trace2/tr2_tgt.h b/trace2/tr2_tgt.h\nnew file mode 100644\nindex 0000000000..297bb8ffbe\n--- /dev/null\n+++ b/trace2/tr2_tgt.h\n@@ -0,0 +1,133 @@\n+#ifndef TR2_TGT_H\n+#define TR2_TGT_H\n+\n+struct child_process;\n+struct repository;\n+struct json_writer;\n+\n+/*\n+ * Function prototypes for a TRACE2 \"target\" vtable.\n+ */\n+\n+typedef int(tr2_tgt_init_t)(void);\n+typedef void(tr2_tgt_term_t)(void);\n+\n+typedef void(tr2_tgt_evt_version_fl_t)(const char *file, int line);\n+\n+typedef void(tr2_tgt_evt_start_fl_t)(const char *file, int line,\n+\t\t\t\t     const char **argv);\n+typedef void(tr2_tgt_evt_exit_fl_t)(const char *file, int line,\n+\t\t\t\t    uint64_t us_elapsed_absolute, int code);\n+typedef void(tr2_tgt_evt_signal_t)(uint64_t us_elapsed_absolute, int signo);\n+typedef void(tr2_tgt_evt_atexit_t)(uint64_t us_elapsed_absolute, int code);\n+\n+typedef void(tr2_tgt_evt_error_va_fl_t)(const char *file, int line,\n+\t\t\t\t\tconst char *fmt, va_list ap);\n+\n+typedef void(tr2_tgt_evt_command_path_fl_t)(const char *file, int line,\n+\t\t\t\t\t    const char *command_path);\n+typedef void(tr2_tgt_evt_command_name_fl_t)(const char *file, int line,\n+\t\t\t\t\t    const char *name,\n+\t\t\t\t\t    const char *hierarchy);\n+typedef void(tr2_tgt_evt_command_mode_fl_t)(const char *file, int line,\n+\t\t\t\t\t    const char *mode);\n+\n+typedef void(tr2_tgt_evt_alias_fl_t)(const char *file, int line,\n+\t\t\t\t     const char *alias, const char **argv);\n+\n+typedef void(tr2_tgt_evt_child_start_fl_t)(const char *file, int line,\n+\t\t\t\t\t   uint64_t us_elapsed_absolute,\n+\t\t\t\t\t   const struct child_process *cmd);\n+typedef void(tr2_tgt_evt_child_exit_fl_t)(const char *file, int line,\n+\t\t\t\t\t  uint64_t us_elapsed_absolute, int cid,\n+\t\t\t\t\t  int pid, int code,\n+\t\t\t\t\t  uint64_t us_elapsed_child);\n+\n+typedef void(tr2_tgt_evt_thread_start_fl_t)(const char *file, int line,\n+\t\t\t\t\t    uint64_t us_elapsed_absolute);\n+typedef void(tr2_tgt_evt_thread_exit_fl_t)(const char *file, int line,\n+\t\t\t\t\t   uint64_t us_elapsed_absolute,\n+\t\t\t\t\t   uint64_t us_elapsed_thread);\n+\n+typedef void(tr2_tgt_evt_exec_fl_t)(const char *file, int line,\n+\t\t\t\t    uint64_t us_elapsed_absolute, int exec_id,\n+\t\t\t\t    const char *exe, const char **argv);\n+typedef void(tr2_tgt_evt_exec_result_fl_t)(const char *file, int line,\n+\t\t\t\t\t   uint64_t us_elapsed_absolute,\n+\t\t\t\t\t   int exec_id, int code);\n+\n+typedef void(tr2_tgt_evt_param_fl_t)(const char *file, int line,\n+\t\t\t\t     const char *param, const char *value);\n+\n+typedef void(tr2_tgt_evt_repo_fl_t)(const char *file, int line,\n+\t\t\t\t    const struct repository *repo);\n+\n+typedef void(tr2_tgt_evt_region_enter_printf_va_fl_t)(\n+\tconst char *file, int line, uint64_t us_elapsed_absolute,\n+\tconst char *category, const char *label, const struct repository *repo,\n+\tconst char *fmt, va_list ap);\n+typedef void(tr2_tgt_evt_region_leave_printf_va_fl_t)(\n+\tconst char *file, int line, uint64_t us_elapsed_absolute,\n+\tuint64_t us_elapsed_region, const char *category, const char *label,\n+\tconst struct repository *repo, const char *fmt, va_list ap);\n+\n+typedef void(tr2_tgt_evt_data_fl_t)(const char *file, int line,\n+\t\t\t\t    uint64_t us_elapsed_absolute,\n+\t\t\t\t    uint64_t us_elapsed_region,\n+\t\t\t\t    const char *category,\n+\t\t\t\t    const struct repository *repo,\n+\t\t\t\t    const char *key, const char *value);\n+typedef void(tr2_tgt_evt_data_json_fl_t)(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t uint64_t us_elapsed_region,\n+\t\t\t\t\t const char *category,\n+\t\t\t\t\t const struct repository *repo,\n+\t\t\t\t\t const char *key,\n+\t\t\t\t\t const struct json_writer *value);\n+\n+typedef void(tr2_tgt_evt_printf_va_fl_t)(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t const char *fmt, va_list ap);\n+\n+/*\n+ * \"vtable\" for a TRACE2 target.  Use NULL if a target does not want\n+ * to emit that message.\n+ */\n+/* clang-format off */\n+struct tr2_tgt {\n+\tstruct tr2_dst                          *pdst;\n+\n+\ttr2_tgt_init_t                          *pfn_init;\n+\ttr2_tgt_term_t                          *pfn_term;\n+\n+\ttr2_tgt_evt_version_fl_t                *pfn_version_fl;\n+\ttr2_tgt_evt_start_fl_t                  *pfn_start_fl;\n+\ttr2_tgt_evt_exit_fl_t                   *pfn_exit_fl;\n+\ttr2_tgt_evt_signal_t                    *pfn_signal;\n+\ttr2_tgt_evt_atexit_t                    *pfn_atexit;\n+\ttr2_tgt_evt_error_va_fl_t               *pfn_error_va_fl;\n+\ttr2_tgt_evt_command_path_fl_t           *pfn_command_path_fl;\n+\ttr2_tgt_evt_command_name_fl_t           *pfn_command_name_fl;\n+\ttr2_tgt_evt_command_mode_fl_t           *pfn_command_mode_fl;\n+\ttr2_tgt_evt_alias_fl_t                  *pfn_alias_fl;\n+\ttr2_tgt_evt_child_start_fl_t            *pfn_child_start_fl;\n+\ttr2_tgt_evt_child_exit_fl_t             *pfn_child_exit_fl;\n+\ttr2_tgt_evt_thread_start_fl_t           *pfn_thread_start_fl;\n+\ttr2_tgt_evt_thread_exit_fl_t            *pfn_thread_exit_fl;\n+\ttr2_tgt_evt_exec_fl_t                   *pfn_exec_fl;\n+\ttr2_tgt_evt_exec_result_fl_t            *pfn_exec_result_fl;\n+\ttr2_tgt_evt_param_fl_t                  *pfn_param_fl;\n+\ttr2_tgt_evt_repo_fl_t                   *pfn_repo_fl;\n+\ttr2_tgt_evt_region_enter_printf_va_fl_t *pfn_region_enter_printf_va_fl;\n+\ttr2_tgt_evt_region_leave_printf_va_fl_t *pfn_region_leave_printf_va_fl;\n+\ttr2_tgt_evt_data_fl_t                   *pfn_data_fl;\n+\ttr2_tgt_evt_data_json_fl_t              *pfn_data_json_fl;\n+\ttr2_tgt_evt_printf_va_fl_t              *pfn_printf_va_fl;\n+};\n+/* clang-format on */\n+\n+extern struct tr2_tgt tr2_tgt_event;\n+extern struct tr2_tgt tr2_tgt_normal;\n+extern struct tr2_tgt tr2_tgt_perf;\n+\n+#endif /* TR2_TGT_H */\ndiff --git a/trace2/tr2_tgt_event.c b/trace2/tr2_tgt_event.c\nnew file mode 100644\nindex 0000000000..107cb5317d\n--- /dev/null\n+++ b/trace2/tr2_tgt_event.c\n@@ -0,0 +1,588 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"json-writer.h\"\n+#include \"run-command.h\"\n+#include \"version.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_tbuf.h\"\n+#include \"trace2/tr2_sid.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+static struct tr2_dst tr2dst_event = { \"GIT_TR2_EVENT\", 0, 0, 0 };\n+\n+/*\n+ * The version number of the JSON data generated by the EVENT target\n+ * in this source file.  Update this if you make a significant change\n+ * to the JSON fields or message structure.  You probably do not need\n+ * to update this if you just add another call to one of the existing\n+ * TRACE2 API methods.\n+ */\n+#define TR2_EVENT_VERSION \"1\"\n+\n+/*\n+ * Region nesting limit for messages written to the event target.\n+ *\n+ * The \"region_enter\" and \"region_leave\" messages (especially recursive\n+ * messages such as those produced while diving the worktree or index)\n+ * are primarily intended for the performance target during debugging.\n+ *\n+ * Some of the outer-most messages, however, may be of interest to the\n+ * event target.  Set this environment variable to a larger integer for\n+ * more detail in the event target.\n+ */\n+#define TR2_ENVVAR_EVENT_NESTING \"GIT_TR2_EVENT_NESTING\"\n+static int tr2env_event_nesting_wanted = 2;\n+\n+/*\n+ * Set this environment variable to true to omit the <time>, <file>, and\n+ * <line> fields from most events.\n+ */\n+#define TR2_ENVVAR_EVENT_BRIEF \"GIT_TR2_EVENT_BRIEF\"\n+static int tr2env_event_brief;\n+\n+static int fn_init(void)\n+{\n+\tint want = tr2_dst_trace_want(&tr2dst_event);\n+\tint want_nesting;\n+\tint want_brief;\n+\tchar *nesting;\n+\tchar *brief;\n+\n+\tif (!want)\n+\t\treturn want;\n+\n+\tnesting = getenv(TR2_ENVVAR_EVENT_NESTING);\n+\tif (nesting && ((want_nesting = atoi(nesting)) > 0))\n+\t\ttr2env_event_nesting_wanted = want_nesting;\n+\n+\tbrief = getenv(TR2_ENVVAR_EVENT_BRIEF);\n+\tif (brief && ((want_brief = atoi(brief)) > 0))\n+\t\ttr2env_event_brief = want_brief;\n+\n+\treturn want;\n+}\n+\n+static void fn_term(void)\n+{\n+\ttr2_dst_trace_disable(&tr2dst_event);\n+}\n+\n+/*\n+ * Append common key-value pairs to the currently open JSON object.\n+ *     \"event:\"<event_name>\"\n+ *      \"sid\":\"<sid>\"\n+ *   \"thread\":\"<thread_name>\"\n+ *     \"time\":\"<time>\"\n+ *     \"file\":\"<filename>\"\n+ *     \"line\":<line_number>\n+ *     \"repo\":<repo_id>\n+ */\n+static void event_fmt_prepare(const char *event_name, const char *file,\n+\t\t\t      int line, const struct repository *repo,\n+\t\t\t      struct json_writer *jw)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tstruct tr2_tbuf tb_now;\n+\n+\tjw_object_string(jw, \"event\", event_name);\n+\tjw_object_string(jw, \"sid\", tr2_sid_get());\n+\tjw_object_string(jw, \"thread\", ctx->thread_name.buf);\n+\n+\t/*\n+\t * In brief mode, only emit <time> on these 2 event types.\n+\t */\n+\tif (!tr2env_event_brief || !strcmp(event_name, \"version\") ||\n+\t    !strcmp(event_name, \"atexit\")) {\n+\t\ttr2_tbuf_utc_time(&tb_now);\n+\t\tjw_object_string(jw, \"time\", tb_now.buf);\n+\t}\n+\n+\tif (!tr2env_event_brief && file && *file) {\n+\t\tjw_object_string(jw, \"file\", file);\n+\t\tjw_object_intmax(jw, \"line\", line);\n+\t}\n+\n+\tif (repo)\n+\t\tjw_object_intmax(jw, \"repo\", repo->trace2_repo_id);\n+}\n+\n+static void fn_version_fl(const char *file, int line)\n+{\n+\tconst char *event_name = \"version\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"evt\", TR2_EVENT_VERSION);\n+\tjw_object_string(&jw, \"exe\", git_version_string);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_start_fl(const char *file, int line, const char **argv)\n+{\n+\tconst char *event_name = \"start\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tjw_array_argv(&jw, argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_exit_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int code)\n+{\n+\tconst char *event_name = \"exit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_signal(uint64_t us_elapsed_absolute, int signo)\n+{\n+\tconst char *event_name = \"signal\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, __FILE__, __LINE__, NULL, &jw);\n+\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\tjw_object_intmax(&jw, \"signo\", signo);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_atexit(uint64_t us_elapsed_absolute, int code)\n+{\n+\tconst char *event_name = \"atexit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, __FILE__, __LINE__, NULL, &jw);\n+\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void maybe_add_string_va(struct json_writer *jw, const char *field_name,\n+\t\t\t\tconst char *fmt, va_list ap)\n+{\n+\tif (fmt && *fmt && ap) {\n+\t\tva_list copy_ap;\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\n+\t\tva_copy(copy_ap, ap);\n+\t\tstrbuf_vaddf(&buf, fmt, copy_ap);\n+\t\tva_end(copy_ap);\n+\n+\t\tjw_object_string(jw, field_name, buf.buf);\n+\t\tstrbuf_release(&buf);\n+\t\treturn;\n+\t}\n+\n+\tif (fmt && *fmt) {\n+\t\tjw_object_string(jw, field_name, fmt);\n+\t\treturn;\n+\t}\n+}\n+\n+static void fn_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t   va_list ap)\n+{\n+\tconst char *event_name = \"error\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tmaybe_add_string_va(&jw, \"msg\", fmt, ap);\n+\t/*\n+\t * Also emit the format string as a field in case\n+\t * post-processors want to aggregate common error\n+\t * messages by type without argument fields (such\n+\t * as pathnames or branch names) cluttering it up.\n+\t */\n+\tif (fmt && *fmt)\n+\t\tjw_object_string(&jw, \"fmt\", fmt);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_command_path_fl(const char *file, int line, const char *pathname)\n+{\n+\tconst char *event_name = \"cmd_path\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"path\", pathname);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_command_name_fl(const char *file, int line, const char *name,\n+\t\t\t       const char *hierarchy)\n+{\n+\tconst char *event_name = \"cmd_name\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"name\", name);\n+\tif (hierarchy && *hierarchy)\n+\t\tjw_object_string(&jw, \"hierarchy\", hierarchy);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_command_mode_fl(const char *file, int line, const char *mode)\n+{\n+\tconst char *event_name = \"cmd_mode\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"name\", mode);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_alias_fl(const char *file, int line, const char *alias,\n+\t\t\tconst char **argv)\n+{\n+\tconst char *event_name = \"alias\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"alias\", alias);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tjw_array_argv(&jw, argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_child_start_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      const struct child_process *cmd)\n+{\n+\tconst char *event_name = \"child_start\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"child_id\", cmd->trace2_child_id);\n+\tif (cmd->trace2_hook_name) {\n+\t\tjw_object_string(&jw, \"child_class\", \"hook\");\n+\t\tjw_object_string(&jw, \"hook_name\", cmd->trace2_hook_name);\n+\t} else {\n+\t\tconst char *child_class =\n+\t\t\tcmd->trace2_child_class ? cmd->trace2_child_class : \"?\";\n+\t\tjw_object_string(&jw, \"child_class\", child_class);\n+\t}\n+\tif (cmd->dir)\n+\t\tjw_object_string(&jw, \"cd\", cmd->dir);\n+\tjw_object_bool(&jw, \"use_shell\", cmd->use_shell);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tif (cmd->git_cmd)\n+\t\tjw_array_string(&jw, \"git\");\n+\tjw_array_argv(&jw, cmd->argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_child_exit_fl(const char *file, int line,\n+\t\t\t     uint64_t us_elapsed_absolute, int cid, int pid,\n+\t\t\t     int code, uint64_t us_elapsed_child)\n+{\n+\tconst char *event_name = \"child_exit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_rel = (double)us_elapsed_child / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"child_id\", cid);\n+\tjw_object_intmax(&jw, \"pid\", pid);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\n+\tjw_release(&jw);\n+}\n+\n+static void fn_thread_start_fl(const char *file, int line,\n+\t\t\t       uint64_t us_elapsed_absolute)\n+{\n+\tconst char *event_name = \"thread_start\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_thread_exit_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      uint64_t us_elapsed_thread)\n+{\n+\tconst char *event_name = \"thread_exit\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tdouble t_rel = (double)us_elapsed_thread / 1000000.0;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int exec_id, const char *exe, const char **argv)\n+{\n+\tconst char *event_name = \"exec\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"exec_id\", exec_id);\n+\tif (exe)\n+\t\tjw_object_string(&jw, \"exe\", exe);\n+\tjw_object_inline_begin_array(&jw, \"argv\");\n+\tjw_array_argv(&jw, argv);\n+\tjw_end(&jw);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_exec_result_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute, int exec_id,\n+\t\t\t      int code)\n+{\n+\tconst char *event_name = \"exec_result\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_intmax(&jw, \"exec_id\", exec_id);\n+\tjw_object_intmax(&jw, \"code\", code);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_param_fl(const char *file, int line, const char *param,\n+\t\t\tconst char *value)\n+{\n+\tconst char *event_name = \"def_param\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, NULL, &jw);\n+\tjw_object_string(&jw, \"param\", param);\n+\tjw_object_string(&jw, \"value\", value);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_repo_fl(const char *file, int line,\n+\t\t       const struct repository *repo)\n+{\n+\tconst char *event_name = \"def_repo\";\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\tjw_object_begin(&jw, 0);\n+\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\tjw_object_string(&jw, \"worktree\", repo->worktree);\n+\tjw_end(&jw);\n+\n+\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\tjw_release(&jw);\n+}\n+\n+static void fn_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t const char *category,\n+\t\t\t\t\t const char *label,\n+\t\t\t\t\t const struct repository *repo,\n+\t\t\t\t\t const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_enter\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tif (category)\n+\t\t\tjw_object_string(&jw, \"category\", category);\n+\t\tif (label)\n+\t\t\tjw_object_string(&jw, \"label\", label);\n+\t\tmaybe_add_string_va(&jw, \"msg\", fmt, ap);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+static void fn_region_leave_printf_va_fl(\n+\tconst char *file, int line, uint64_t us_elapsed_absolute,\n+\tuint64_t us_elapsed_region, const char *category, const char *label,\n+\tconst struct repository *repo, const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_leave\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\t\tdouble t_rel = (double)us_elapsed_region / 1000000.0;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tif (category)\n+\t\t\tjw_object_string(&jw, \"category\", category);\n+\t\tif (label)\n+\t\t\tjw_object_string(&jw, \"label\", label);\n+\t\tmaybe_add_string_va(&jw, \"msg\", fmt, ap);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+static void fn_data_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       uint64_t us_elapsed_region, const char *category,\n+\t\t       const struct repository *repo, const char *key,\n+\t\t       const char *value)\n+{\n+\tconst char *event_name = \"data\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\t\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\t\tdouble t_rel = (double)us_elapsed_region / 1000000.0;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\t\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tjw_object_string(&jw, \"category\", category);\n+\t\tjw_object_string(&jw, \"key\", key);\n+\t\tjw_object_string(&jw, \"value\", value);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+static void fn_data_json_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute,\n+\t\t\t    uint64_t us_elapsed_region, const char *category,\n+\t\t\t    const struct repository *repo, const char *key,\n+\t\t\t    const struct json_writer *value)\n+{\n+\tconst char *event_name = \"data_json\";\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tif (ctx->nr_open_regions <= tr2env_event_nesting_wanted) {\n+\t\tstruct json_writer jw = JSON_WRITER_INIT;\n+\t\tdouble t_abs = (double)us_elapsed_absolute / 1000000.0;\n+\t\tdouble t_rel = (double)us_elapsed_region / 1000000.0;\n+\n+\t\tjw_object_begin(&jw, 0);\n+\t\tevent_fmt_prepare(event_name, file, line, repo, &jw);\n+\t\tjw_object_double(&jw, \"t_abs\", 6, t_abs);\n+\t\tjw_object_double(&jw, \"t_rel\", 6, t_rel);\n+\t\tjw_object_intmax(&jw, \"nesting\", ctx->nr_open_regions);\n+\t\tjw_object_string(&jw, \"category\", category);\n+\t\tjw_object_string(&jw, \"key\", key);\n+\t\tjw_object_sub_jw(&jw, \"value\", value);\n+\t\tjw_end(&jw);\n+\n+\t\ttr2_dst_write_line(&tr2dst_event, &jw.json);\n+\t\tjw_release(&jw);\n+\t}\n+}\n+\n+struct tr2_tgt tr2_tgt_event = {\n+\t&tr2dst_event,\n+\n+\tfn_init,\n+\tfn_term,\n+\n+\tfn_version_fl,\n+\tfn_start_fl,\n+\tfn_exit_fl,\n+\tfn_signal,\n+\tfn_atexit,\n+\tfn_error_va_fl,\n+\tfn_command_path_fl,\n+\tfn_command_name_fl,\n+\tfn_command_mode_fl,\n+\tfn_alias_fl,\n+\tfn_child_start_fl,\n+\tfn_child_exit_fl,\n+\tfn_thread_start_fl,\n+\tfn_thread_exit_fl,\n+\tfn_exec_fl,\n+\tfn_exec_result_fl,\n+\tfn_param_fl,\n+\tfn_repo_fl,\n+\tfn_region_enter_printf_va_fl,\n+\tfn_region_leave_printf_va_fl,\n+\tfn_data_fl,\n+\tfn_data_json_fl,\n+\tNULL, /* printf */\n+};\ndiff --git a/trace2/tr2_tgt_normal.c b/trace2/tr2_tgt_normal.c\nnew file mode 100644\nindex 0000000000..547183d5b6\n--- /dev/null\n+++ b/trace2/tr2_tgt_normal.c\n@@ -0,0 +1,323 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"run-command.h\"\n+#include \"quote.h\"\n+#include \"version.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_tbuf.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+static struct tr2_dst tr2dst_normal = { \"GIT_TR2\", 0, 0, 0 };\n+\n+/*\n+ * Set this environment variable to true to omit the \"<time> <file>:<line>\"\n+ * fields from each line written to the builtin normal target.\n+ *\n+ * Unit tests may want to use this to help with testing.\n+ */\n+#define TR2_ENVVAR_NORMAL_BRIEF \"GIT_TR2_BRIEF\"\n+static int tr2env_normal_brief;\n+\n+#define TR2FMT_NORMAL_FL_WIDTH (50)\n+\n+static int fn_init(void)\n+{\n+\tint want = tr2_dst_trace_want(&tr2dst_normal);\n+\tint want_brief;\n+\tchar *brief;\n+\n+\tif (!want)\n+\t\treturn want;\n+\n+\tbrief = getenv(TR2_ENVVAR_NORMAL_BRIEF);\n+\tif (brief && *brief &&\n+\t    ((want_brief = git_parse_maybe_bool(brief)) != -1))\n+\t\ttr2env_normal_brief = want_brief;\n+\n+\treturn want;\n+}\n+\n+static void fn_term(void)\n+{\n+\ttr2_dst_trace_disable(&tr2dst_normal);\n+}\n+\n+static void normal_fmt_prepare(const char *file, int line, struct strbuf *buf)\n+{\n+\tstrbuf_setlen(buf, 0);\n+\n+\tif (!tr2env_normal_brief) {\n+\t\tstruct tr2_tbuf tb_now;\n+\n+\t\ttr2_tbuf_local_time(&tb_now);\n+\t\tstrbuf_addstr(buf, tb_now.buf);\n+\t\tstrbuf_addch(buf, ' ');\n+\n+\t\tif (file && *file)\n+\t\t\tstrbuf_addf(buf, \"%s:%d \", file, line);\n+\t\twhile (buf->len < TR2FMT_NORMAL_FL_WIDTH)\n+\t\t\tstrbuf_addch(buf, ' ');\n+\t}\n+}\n+\n+static void normal_io_write_fl(const char *file, int line,\n+\t\t\t       const struct strbuf *buf_payload)\n+{\n+\tstruct strbuf buf_line = STRBUF_INIT;\n+\n+\tnormal_fmt_prepare(file, line, &buf_line);\n+\tstrbuf_addbuf(&buf_line, buf_payload);\n+\ttr2_dst_write_line(&tr2dst_normal, &buf_line);\n+\tstrbuf_release(&buf_line);\n+}\n+\n+static void fn_version_fl(const char *file, int line)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"version %s\", git_version_string);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_start_fl(const char *file, int line, const char **argv)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"start \");\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exit_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int code)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"exit elapsed:%.6f code:%d\", elapsed, code);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_signal(uint64_t us_elapsed_absolute, int signo)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"signal elapsed:%.6f code:%d\", elapsed,\n+\t\t    signo);\n+\tnormal_io_write_fl(__FILE__, __LINE__, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_atexit(uint64_t us_elapsed_absolute, int code)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_absolute / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"atexit elapsed:%.6f code:%d\", elapsed, code);\n+\tnormal_io_write_fl(__FILE__, __LINE__, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void maybe_append_string_va(struct strbuf *buf, const char *fmt,\n+\t\t\t\t   va_list ap)\n+{\n+\tif (fmt && *fmt && ap) {\n+\t\tva_list copy_ap;\n+\n+\t\tva_copy(copy_ap, ap);\n+\t\tstrbuf_vaddf(buf, fmt, copy_ap);\n+\t\tva_end(copy_ap);\n+\t\treturn;\n+\t}\n+\n+\tif (fmt && *fmt) {\n+\t\tstrbuf_addstr(buf, fmt);\n+\t\treturn;\n+\t}\n+}\n+\n+static void fn_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t   va_list ap)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"error \");\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_path_fl(const char *file, int line, const char *pathname)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"cmd_path %s\", pathname);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_name_fl(const char *file, int line, const char *name,\n+\t\t\t       const char *hierarchy)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"cmd_name %s\", name);\n+\tif (hierarchy && *hierarchy)\n+\t\tstrbuf_addf(&buf_payload, \" (%s)\", hierarchy);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_mode_fl(const char *file, int line, const char *mode)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"cmd_mode %s\", mode);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_alias_fl(const char *file, int line, const char *alias,\n+\t\t\tconst char **argv)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"alias %s ->\", alias);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_start_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      const struct child_process *cmd)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"child_start[%d] \", cmd->trace2_child_id);\n+\n+\tif (cmd->dir) {\n+\t\tstrbuf_addstr(&buf_payload, \" cd\");\n+\t\tsq_quote_buf_pretty(&buf_payload, cmd->dir);\n+\t\tstrbuf_addstr(&buf_payload, \"; \");\n+\t}\n+\n+\t/*\n+\t * TODO if (cmd->env) { Consider dumping changes to environment. }\n+\t * See trace_add_env() in run-command.c as used by original trace.c\n+\t */\n+\n+\tif (cmd->git_cmd)\n+\t\tstrbuf_addstr(&buf_payload, \"git\");\n+\tsq_quote_argv_pretty(&buf_payload, cmd->argv);\n+\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_exit_fl(const char *file, int line,\n+\t\t\t     uint64_t us_elapsed_absolute, int cid, int pid,\n+\t\t\t     int code, uint64_t us_elapsed_child)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\tdouble elapsed = (double)us_elapsed_child / 1000000.0;\n+\n+\tstrbuf_addf(&buf_payload, \"child_exit[%d] pid:%d code:%d elapsed:%.6f\",\n+\t\t    cid, pid, code, elapsed);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int exec_id, const char *exe, const char **argv)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"exec[%d] \", exec_id);\n+\tif (exe)\n+\t\tstrbuf_addstr(&buf_payload, exe);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_result_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute, int exec_id,\n+\t\t\t      int code)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"exec_result[%d] code:%d\", exec_id, code);\n+\tif (code > 0)\n+\t\tstrbuf_addf(&buf_payload, \" err:%s\", strerror(code));\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_param_fl(const char *file, int line, const char *param,\n+\t\t\tconst char *value)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"def_param %s=%s\", param, value);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_repo_fl(const char *file, int line,\n+\t\t       const struct repository *repo)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"worktree \");\n+\tsq_quote_buf_pretty(&buf_payload, repo->worktree);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_printf_va_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute, const char *fmt,\n+\t\t\t    va_list ap)\n+{\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\tnormal_io_write_fl(file, line, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+struct tr2_tgt tr2_tgt_normal = {\n+\t&tr2dst_normal,\n+\n+\tfn_init,\n+\tfn_term,\n+\n+\tfn_version_fl,\n+\tfn_start_fl,\n+\tfn_exit_fl,\n+\tfn_signal,\n+\tfn_atexit,\n+\tfn_error_va_fl,\n+\tfn_command_path_fl,\n+\tfn_command_name_fl,\n+\tfn_command_mode_fl,\n+\tfn_alias_fl,\n+\tfn_child_start_fl,\n+\tfn_child_exit_fl,\n+\tNULL, /* thread_start */\n+\tNULL, /* thread_exit */\n+\tfn_exec_fl,\n+\tfn_exec_result_fl,\n+\tfn_param_fl,\n+\tfn_repo_fl,\n+\tNULL, /* region_enter */\n+\tNULL, /* region_leave */\n+\tNULL, /* data */\n+\tNULL, /* data_json */\n+\tfn_printf_va_fl,\n+};\ndiff --git a/trace2/tr2_tgt_perf.c b/trace2/tr2_tgt_perf.c\nnew file mode 100644\nindex 0000000000..f0746fcf86\n--- /dev/null\n+++ b/trace2/tr2_tgt_perf.c\n@@ -0,0 +1,534 @@\n+#include \"cache.h\"\n+#include \"config.h\"\n+#include \"run-command.h\"\n+#include \"quote.h\"\n+#include \"version.h\"\n+#include \"json-writer.h\"\n+#include \"trace2/tr2_dst.h\"\n+#include \"trace2/tr2_sid.h\"\n+#include \"trace2/tr2_tbuf.h\"\n+#include \"trace2/tr2_tgt.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+static struct tr2_dst tr2dst_perf = { \"GIT_TR2_PERF\", 0, 0, 0 };\n+\n+/*\n+ * Set this environment variable to true to omit the \"<time> <file>:<line>\"\n+ * fields from each line written to the builtin performance target.\n+ *\n+ * Unit tests may want to use this to help with testing.\n+ */\n+#define TR2_ENVVAR_PERF_BRIEF \"GIT_TR2_PERF_BRIEF\"\n+static int tr2env_perf_brief;\n+\n+#define TR2FMT_PERF_FL_WIDTH (50)\n+#define TR2FMT_PERF_MAX_EVENT_NAME (12)\n+#define TR2FMT_PERF_REPO_WIDTH (4)\n+#define TR2FMT_PERF_CATEGORY_WIDTH (10)\n+\n+#define TR2_DOTS_BUFFER_SIZE (100)\n+#define TR2_INDENT (2)\n+#define TR2_INDENT_LENGTH(ctx) (((ctx)->nr_open_regions - 1) * TR2_INDENT)\n+\n+static struct strbuf dots = STRBUF_INIT;\n+\n+static int fn_init(void)\n+{\n+\tint want = tr2_dst_trace_want(&tr2dst_perf);\n+\tint want_brief;\n+\tchar *brief;\n+\n+\tif (!want)\n+\t\treturn want;\n+\n+\tstrbuf_addchars(&dots, '.', TR2_DOTS_BUFFER_SIZE);\n+\n+\tbrief = getenv(TR2_ENVVAR_PERF_BRIEF);\n+\tif (brief && *brief &&\n+\t    ((want_brief = git_parse_maybe_bool(brief)) != -1))\n+\t\ttr2env_perf_brief = want_brief;\n+\n+\treturn want;\n+}\n+\n+static void fn_term(void)\n+{\n+\ttr2_dst_trace_disable(&tr2dst_perf);\n+\n+\tstrbuf_release(&dots);\n+}\n+\n+/*\n+ * Format trace line prefix in human-readable classic format for\n+ * the performance target:\n+ *     \"[<time> [<file>:<line>] <bar>] <nr_parents> <bar>\n+ *         <thread_name> <bar> <event_name> <bar> [<repo>] <bar>\n+ *         [<elapsed_absolute>] [<elapsed_relative>] <bar>\n+ *         [<category>] <bar> [<dots>] \"\n+ */\n+static void perf_fmt_prepare(const char *event_name,\n+\t\t\t     struct tr2tls_thread_ctx *ctx, const char *file,\n+\t\t\t     int line, const struct repository *repo,\n+\t\t\t     uint64_t *p_us_elapsed_absolute,\n+\t\t\t     uint64_t *p_us_elapsed_relative,\n+\t\t\t     const char *category, struct strbuf *buf)\n+{\n+\tint len;\n+\n+\tstrbuf_setlen(buf, 0);\n+\n+\tif (!tr2env_perf_brief) {\n+\t\tstruct tr2_tbuf tb_now;\n+\n+\t\ttr2_tbuf_local_time(&tb_now);\n+\t\tstrbuf_addstr(buf, tb_now.buf);\n+\t\tstrbuf_addch(buf, ' ');\n+\n+\t\tif (file && *file)\n+\t\t\tstrbuf_addf(buf, \"%s:%d \", file, line);\n+\t\twhile (buf->len < TR2FMT_PERF_FL_WIDTH)\n+\t\t\tstrbuf_addch(buf, ' ');\n+\n+\t\tstrbuf_addstr(buf, \"| \");\n+\t}\n+\n+\tstrbuf_addf(buf, \"d%d | \", tr2_sid_depth());\n+\tstrbuf_addf(buf, \"%-*s | %-*s | \", TR2_MAX_THREAD_NAME,\n+\t\t    ctx->thread_name.buf, TR2FMT_PERF_MAX_EVENT_NAME,\n+\t\t    event_name);\n+\n+\tlen = buf->len + TR2FMT_PERF_REPO_WIDTH;\n+\tif (repo)\n+\t\tstrbuf_addf(buf, \"r%d \", repo->trace2_repo_id);\n+\twhile (buf->len < len)\n+\t\tstrbuf_addch(buf, ' ');\n+\tstrbuf_addstr(buf, \"| \");\n+\n+\tif (p_us_elapsed_absolute)\n+\t\tstrbuf_addf(buf, \"%9.6f | \",\n+\t\t\t    ((double)(*p_us_elapsed_absolute)) / 1000000.0);\n+\telse\n+\t\tstrbuf_addf(buf, \"%9s | \", \" \");\n+\n+\tif (p_us_elapsed_relative)\n+\t\tstrbuf_addf(buf, \"%9.6f | \",\n+\t\t\t    ((double)(*p_us_elapsed_relative)) / 1000000.0);\n+\telse\n+\t\tstrbuf_addf(buf, \"%9s | \", \" \");\n+\n+\tstrbuf_addf(buf, \"%-*s | \", TR2FMT_PERF_CATEGORY_WIDTH,\n+\t\t    (category ? category : \"\"));\n+\n+\tif (ctx->nr_open_regions > 0) {\n+\t\tint len_indent = TR2_INDENT_LENGTH(ctx);\n+\t\twhile (len_indent > dots.len) {\n+\t\t\tstrbuf_addbuf(buf, &dots);\n+\t\t\tlen_indent -= dots.len;\n+\t\t}\n+\t\tstrbuf_addf(buf, \"%.*s\", len_indent, dots.buf);\n+\t}\n+}\n+\n+static void perf_io_write_fl(const char *file, int line, const char *event_name,\n+\t\t\t     const struct repository *repo,\n+\t\t\t     uint64_t *p_us_elapsed_absolute,\n+\t\t\t     uint64_t *p_us_elapsed_relative,\n+\t\t\t     const char *category,\n+\t\t\t     const struct strbuf *buf_payload)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\tstruct strbuf buf_line = STRBUF_INIT;\n+\n+\tperf_fmt_prepare(event_name, ctx, file, line, repo,\n+\t\t\t p_us_elapsed_absolute, p_us_elapsed_relative, category,\n+\t\t\t &buf_line);\n+\tstrbuf_addbuf(&buf_line, buf_payload);\n+\ttr2_dst_write_line(&tr2dst_perf, &buf_line);\n+\tstrbuf_release(&buf_line);\n+}\n+\n+static void fn_version_fl(const char *file, int line)\n+{\n+\tconst char *event_name = \"version\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, git_version_string);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_start_fl(const char *file, int line, const char **argv)\n+{\n+\tconst char *event_name = \"start\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exit_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int code)\n+{\n+\tconst char *event_name = \"exit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"code:%d\", code);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_signal(uint64_t us_elapsed_absolute, int signo)\n+{\n+\tconst char *event_name = \"signal\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"signo:%d\", signo);\n+\n+\tperf_io_write_fl(__FILE__, __LINE__, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_atexit(uint64_t us_elapsed_absolute, int code)\n+{\n+\tconst char *event_name = \"atexit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"code:%d\", code);\n+\n+\tperf_io_write_fl(__FILE__, __LINE__, event_name, NULL,\n+\t\t\t &us_elapsed_absolute, NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void maybe_append_string_va(struct strbuf *buf, const char *fmt,\n+\t\t\t\t   va_list ap)\n+{\n+\tif (fmt && *fmt && ap) {\n+\t\tva_list copy_ap;\n+\n+\t\tva_copy(copy_ap, ap);\n+\t\tstrbuf_vaddf(buf, fmt, copy_ap);\n+\t\tva_end(copy_ap);\n+\t\treturn;\n+\t}\n+\n+\tif (fmt && *fmt) {\n+\t\tstrbuf_addstr(buf, fmt);\n+\t\treturn;\n+\t}\n+}\n+\n+static void fn_error_va_fl(const char *file, int line, const char *fmt,\n+\t\t\t   va_list ap)\n+{\n+\tconst char *event_name = \"error\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_path_fl(const char *file, int line, const char *pathname)\n+{\n+\tconst char *event_name = \"cmd_path\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, pathname);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_name_fl(const char *file, int line, const char *name,\n+\t\t\t       const char *hierarchy)\n+{\n+\tconst char *event_name = \"cmd_name\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, name);\n+\tif (hierarchy && *hierarchy)\n+\t\tstrbuf_addf(&buf_payload, \" (%s)\", hierarchy);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_command_mode_fl(const char *file, int line, const char *mode)\n+{\n+\tconst char *event_name = \"cmd_mode\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, mode);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_alias_fl(const char *file, int line, const char *alias,\n+\t\t\tconst char **argv)\n+{\n+\tconst char *event_name = \"alias\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"alias:%s argv:\", alias);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_start_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      const struct child_process *cmd)\n+{\n+\tconst char *event_name = \"child_start\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tif (cmd->trace2_hook_name) {\n+\t\tstrbuf_addf(&buf_payload, \"[ch%d] class:hook hook:%s\",\n+\t\t\t    cmd->trace2_child_id, cmd->trace2_hook_name);\n+\t} else {\n+\t\tconst char *child_class =\n+\t\t\tcmd->trace2_child_class ? cmd->trace2_child_class : \"?\";\n+\t\tstrbuf_addf(&buf_payload, \"[ch%d] class:%s\",\n+\t\t\t    cmd->trace2_child_id, child_class);\n+\t}\n+\n+\tif (cmd->dir) {\n+\t\tstrbuf_addstr(&buf_payload, \" cd:\");\n+\t\tsq_quote_buf_pretty(&buf_payload, cmd->dir);\n+\t}\n+\n+\tstrbuf_addstr(&buf_payload, \" argv:\");\n+\tif (cmd->git_cmd)\n+\t\tstrbuf_addstr(&buf_payload, \" git\");\n+\tsq_quote_argv_pretty(&buf_payload, cmd->argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_child_exit_fl(const char *file, int line,\n+\t\t\t     uint64_t us_elapsed_absolute, int cid, int pid,\n+\t\t\t     int code, uint64_t us_elapsed_child)\n+{\n+\tconst char *event_name = \"child_exit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"[ch%d] pid:%d code:%d\", cid, pid, code);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_child, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_thread_start_fl(const char *file, int line,\n+\t\t\t       uint64_t us_elapsed_absolute)\n+{\n+\tconst char *event_name = \"thread_start\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_thread_exit_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute,\n+\t\t\t      uint64_t us_elapsed_thread)\n+{\n+\tconst char *event_name = \"thread_exit\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_thread, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       int exec_id, const char *exe, const char **argv)\n+{\n+\tconst char *event_name = \"exec\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"id:%d \", exec_id);\n+\tstrbuf_addstr(&buf_payload, \"argv:\");\n+\tif (exe)\n+\t\tstrbuf_addf(&buf_payload, \" %s\", exe);\n+\tsq_quote_argv_pretty(&buf_payload, argv);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_exec_result_fl(const char *file, int line,\n+\t\t\t      uint64_t us_elapsed_absolute, int exec_id,\n+\t\t\t      int code)\n+{\n+\tconst char *event_name = \"exec_result\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"id:%d code:%d\", exec_id, code);\n+\tif (code > 0)\n+\t\tstrbuf_addf(&buf_payload, \" err:%s\", strerror(code));\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_param_fl(const char *file, int line, const char *param,\n+\t\t\tconst char *value)\n+{\n+\tconst char *event_name = \"def_param\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"%s:%s\", param, value);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_repo_fl(const char *file, int line,\n+\t\t       const struct repository *repo)\n+{\n+\tconst char *event_name = \"def_repo\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&buf_payload, \"worktree:\");\n+\tsq_quote_buf_pretty(&buf_payload, repo->worktree);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, NULL, NULL, NULL,\n+\t\t\t &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_region_enter_printf_va_fl(const char *file, int line,\n+\t\t\t\t\t uint64_t us_elapsed_absolute,\n+\t\t\t\t\t const char *category,\n+\t\t\t\t\t const char *label,\n+\t\t\t\t\t const struct repository *repo,\n+\t\t\t\t\t const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_enter\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tif (label)\n+\t\tstrbuf_addf(&buf_payload, \"label:%s \", label);\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,\n+\t\t\t NULL, category, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_region_leave_printf_va_fl(\n+\tconst char *file, int line, uint64_t us_elapsed_absolute,\n+\tuint64_t us_elapsed_region, const char *category, const char *label,\n+\tconst struct repository *repo, const char *fmt, va_list ap)\n+{\n+\tconst char *event_name = \"region_leave\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tif (label)\n+\t\tstrbuf_addf(&buf_payload, \"label:%s \", label);\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_region, category, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_data_fl(const char *file, int line, uint64_t us_elapsed_absolute,\n+\t\t       uint64_t us_elapsed_region, const char *category,\n+\t\t       const struct repository *repo, const char *key,\n+\t\t       const char *value)\n+{\n+\tconst char *event_name = \"data\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"%s:%s\", key, value);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_region, category, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_data_json_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute,\n+\t\t\t    uint64_t us_elapsed_region, const char *category,\n+\t\t\t    const struct repository *repo, const char *key,\n+\t\t\t    const struct json_writer *value)\n+{\n+\tconst char *event_name = \"data_json\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tstrbuf_addf(&buf_payload, \"%s:%s\", key, value->json.buf);\n+\n+\tperf_io_write_fl(file, line, event_name, repo, &us_elapsed_absolute,\n+\t\t\t &us_elapsed_region, category, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+static void fn_printf_va_fl(const char *file, int line,\n+\t\t\t    uint64_t us_elapsed_absolute, const char *fmt,\n+\t\t\t    va_list ap)\n+{\n+\tconst char *event_name = \"printf\";\n+\tstruct strbuf buf_payload = STRBUF_INIT;\n+\n+\tmaybe_append_string_va(&buf_payload, fmt, ap);\n+\n+\tperf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,\n+\t\t\t NULL, NULL, &buf_payload);\n+\tstrbuf_release(&buf_payload);\n+}\n+\n+struct tr2_tgt tr2_tgt_perf = {\n+\t&tr2dst_perf,\n+\n+\tfn_init,\n+\tfn_term,\n+\n+\tfn_version_fl,\n+\tfn_start_fl,\n+\tfn_exit_fl,\n+\tfn_signal,\n+\tfn_atexit,\n+\tfn_error_va_fl,\n+\tfn_command_path_fl,\n+\tfn_command_name_fl,\n+\tfn_command_mode_fl,\n+\tfn_alias_fl,\n+\tfn_child_start_fl,\n+\tfn_child_exit_fl,\n+\tfn_thread_start_fl,\n+\tfn_thread_exit_fl,\n+\tfn_exec_fl,\n+\tfn_exec_result_fl,\n+\tfn_param_fl,\n+\tfn_repo_fl,\n+\tfn_region_enter_printf_va_fl,\n+\tfn_region_leave_printf_va_fl,\n+\tfn_data_fl,\n+\tfn_data_json_fl,\n+\tfn_printf_va_fl,\n+};\ndiff --git a/trace2/tr2_tls.c b/trace2/tr2_tls.c\nnew file mode 100644\nindex 0000000000..8e65b0361d\n--- /dev/null\n+++ b/trace2/tr2_tls.c\n@@ -0,0 +1,164 @@\n+#include \"cache.h\"\n+#include \"thread-utils.h\"\n+#include \"trace2/tr2_tls.h\"\n+\n+/*\n+ * Initialize size of the thread stack for nested regions.\n+ * This is used to store nested region start times.  Note that\n+ * this stack is per-thread and not per-trace-key.\n+ */\n+#define TR2_REGION_NESTING_INITIAL_SIZE (100)\n+\n+static struct tr2tls_thread_ctx *tr2tls_thread_main;\n+static uint64_t tr2tls_us_start_main;\n+\n+static pthread_mutex_t tr2tls_mutex;\n+static pthread_key_t tr2tls_key;\n+\n+static int tr2_next_thread_id; /* modify under lock */\n+\n+struct tr2tls_thread_ctx *tr2tls_create_self(const char *thread_name)\n+{\n+\tuint64_t us_now = getnanotime() / 1000;\n+\tstruct tr2tls_thread_ctx *ctx = xcalloc(1, sizeof(*ctx));\n+\n+\t/*\n+\t * Implicitly \"tr2tls_push_self()\" to capture the thread's start\n+\t * time in array_us_start[0].  For the main thread this gives us the\n+\t * application run time.\n+\t */\n+\tctx->alloc = TR2_REGION_NESTING_INITIAL_SIZE;\n+\tctx->array_us_start = (uint64_t *)xcalloc(ctx->alloc, sizeof(uint64_t));\n+\tctx->array_us_start[ctx->nr_open_regions++] = us_now;\n+\n+\tctx->thread_id = tr2tls_locked_increment(&tr2_next_thread_id);\n+\n+\tstrbuf_init(&ctx->thread_name, 0);\n+\tif (ctx->thread_id)\n+\t\tstrbuf_addf(&ctx->thread_name, \"th%02d:\", ctx->thread_id);\n+\tstrbuf_addstr(&ctx->thread_name, thread_name);\n+\tif (ctx->thread_name.len > TR2_MAX_THREAD_NAME)\n+\t\tstrbuf_setlen(&ctx->thread_name, TR2_MAX_THREAD_NAME);\n+\n+\tpthread_setspecific(tr2tls_key, ctx);\n+\n+\treturn ctx;\n+}\n+\n+struct tr2tls_thread_ctx *tr2tls_get_self(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = pthread_getspecific(tr2tls_key);\n+\n+\t/*\n+\t * If the thread-proc did not call trace2_thread_start(), we won't\n+\t * have any TLS data associated with the current thread.  Fix it\n+\t * here and silently continue.\n+\t */\n+\tif (!ctx)\n+\t\tctx = tr2tls_create_self(\"unknown\");\n+\n+\treturn ctx;\n+}\n+\n+int tr2tls_is_main_thread(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = pthread_getspecific(tr2tls_key);\n+\n+\treturn ctx == tr2tls_thread_main;\n+}\n+\n+void tr2tls_unset_self(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx;\n+\n+\tctx = tr2tls_get_self();\n+\n+\tpthread_setspecific(tr2tls_key, NULL);\n+\n+\tfree(ctx->array_us_start);\n+\tfree(ctx);\n+}\n+\n+void tr2tls_push_self(uint64_t us_now)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\n+\tALLOC_GROW(ctx->array_us_start, ctx->nr_open_regions + 1, ctx->alloc);\n+\tctx->array_us_start[ctx->nr_open_regions++] = us_now;\n+}\n+\n+void tr2tls_pop_self(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\n+\tif (!ctx->nr_open_regions)\n+\t\tBUG(\"no open regions in thread '%s'\", ctx->thread_name.buf);\n+\n+\tctx->nr_open_regions--;\n+}\n+\n+void tr2tls_pop_unwind_self(void)\n+{\n+\tstruct tr2tls_thread_ctx *ctx = tr2tls_get_self();\n+\n+\twhile (ctx->nr_open_regions > 1)\n+\t\ttr2tls_pop_self();\n+}\n+\n+uint64_t tr2tls_region_elasped_self(uint64_t us)\n+{\n+\tstruct tr2tls_thread_ctx *ctx;\n+\tuint64_t us_start;\n+\n+\tctx = tr2tls_get_self();\n+\tif (!ctx->nr_open_regions)\n+\t\treturn 0;\n+\n+\tus_start = ctx->array_us_start[ctx->nr_open_regions - 1];\n+\n+\treturn us - us_start;\n+}\n+\n+uint64_t tr2tls_absolute_elapsed(uint64_t us)\n+{\n+\tif (!tr2tls_thread_main)\n+\t\treturn 0;\n+\n+\treturn us - tr2tls_us_start_main;\n+}\n+\n+void tr2tls_init(void)\n+{\n+\tpthread_key_create(&tr2tls_key, NULL);\n+\tinit_recursive_mutex(&tr2tls_mutex);\n+\n+\ttr2tls_thread_main = tr2tls_create_self(\"main\");\n+\t/*\n+\t * Keep a copy of the absolute start time of the main thread\n+\t * in a fixed variable since other threads need to access it.\n+\t * This also eliminates the need to lock accesses to the main\n+\t * thread's array (because of reallocs).\n+\t */\n+\ttr2tls_us_start_main = tr2tls_thread_main->array_us_start[0];\n+}\n+\n+void tr2tls_release(void)\n+{\n+\ttr2tls_unset_self();\n+\ttr2tls_thread_main = NULL;\n+\n+\tpthread_mutex_destroy(&tr2tls_mutex);\n+\tpthread_key_delete(tr2tls_key);\n+}\n+\n+int tr2tls_locked_increment(int *p)\n+{\n+\tint current_value;\n+\n+\tpthread_mutex_lock(&tr2tls_mutex);\n+\tcurrent_value = *p;\n+\t*p = current_value + 1;\n+\tpthread_mutex_unlock(&tr2tls_mutex);\n+\n+\treturn current_value;\n+}\ndiff --git a/trace2/tr2_tls.h b/trace2/tr2_tls.h\nnew file mode 100644\nindex 0000000000..bb80e3f8e7\n--- /dev/null\n+++ b/trace2/tr2_tls.h\n@@ -0,0 +1,97 @@\n+#ifndef TR2_TLS_H\n+#define TR2_TLS_H\n+\n+#include \"strbuf.h\"\n+\n+/*\n+ * Arbitry limit for thread names for column alignment.\n+ */\n+#define TR2_MAX_THREAD_NAME (24)\n+\n+struct tr2tls_thread_ctx {\n+\tstruct strbuf thread_name;\n+\tuint64_t *array_us_start;\n+\tint alloc;\n+\tint nr_open_regions; /* plays role of \"nr\" in ALLOC_GROW */\n+\tint thread_id;\n+};\n+\n+/*\n+ * Create TLS data for the current thread.  This gives us a place to\n+ * put per-thread data, such as thread start time, function nesting\n+ * and a per-thread label for our messages.\n+ *\n+ * We assume the first thread is \"main\".  Other threads are given\n+ * non-zero thread-ids to help distinguish messages from concurrent\n+ * threads.\n+ *\n+ * Truncate the thread name if necessary to help with column alignment\n+ * in printf-style messages.\n+ *\n+ * In this and all following functions the term \"self\" refers to the\n+ * current thread.\n+ */\n+struct tr2tls_thread_ctx *tr2tls_create_self(const char *thread_name);\n+\n+/*\n+ * Get our TLS data.\n+ */\n+struct tr2tls_thread_ctx *tr2tls_get_self(void);\n+\n+/*\n+ * return true if the current thread is the main thread.\n+ */\n+int tr2tls_is_main_thread(void);\n+\n+/*\n+ * Free our TLS data.\n+ */\n+void tr2tls_unset_self(void);\n+\n+/*\n+ * Begin a new nested region and remember the start time.\n+ */\n+void tr2tls_push_self(uint64_t us_now);\n+\n+/*\n+ * End the innermost nested region.\n+ */\n+void tr2tls_pop_self(void);\n+\n+/*\n+ * Pop any extra (above the first) open regions on the current\n+ * thread and discard.  During a thread-exit, we should only\n+ * have region[0] that was pushed in trace2_thread_start() if\n+ * the thread exits normally.\n+ */\n+void tr2tls_pop_unwind_self(void);\n+\n+/*\n+ * Compute the elapsed time since the innermost region in the\n+ * current thread started and the given time (usually now).\n+ */\n+uint64_t tr2tls_region_elasped_self(uint64_t us);\n+\n+/*\n+ * Compute the elapsed time since the main thread started\n+ * and the given time (usually now).  This is assumed to\n+ * be the absolute run time of the process.\n+ */\n+uint64_t tr2tls_absolute_elapsed(uint64_t us);\n+\n+/*\n+ * Initialize the tr2 TLS system.\n+ */\n+void tr2tls_init(void);\n+\n+/*\n+ * Free all tr2 TLS resources.\n+ */\n+void tr2tls_release(void);\n+\n+/*\n+ * Protected increment of an integer.\n+ */\n+int tr2tls_locked_increment(int *p);\n+\n+#endif /* TR2_TLS_H */\ndiff --git a/usage.c b/usage.c\nindex cc803336bd..2fdb20086b 100644\n--- a/usage.c\n+++ b/usage.c\n@@ -22,17 +22,48 @@ void vreportf(const char *prefix, const char *err, va_list params)\n static NORETURN void usage_builtin(const char *err, va_list params)\n {\n \tvreportf(\"usage: \", err, params);\n+\n+\t/*\n+\t * When we detect a usage error *before* the command dispatch in\n+\t * cmd_main(), we don't know what verb to report.  Force it to this\n+\t * to facilitate post-processing.\n+\t */\n+\ttrace2_cmd_name(\"_usage_\");\n+\n+\t/*\n+\t * Currently, the (err, params) are usually just the static usage\n+\t * string which isn't very useful here.  Usually, the call site\n+\t * manually calls fprintf(stderr,...) with the actual detailed\n+\t * syntax error before calling usage().\n+\t *\n+\t * TODO It would be nice to update the call sites to pass both\n+\t * the static usage string and the detailed error message.\n+\t */\n+\n \texit(129);\n }\n \n static NORETURN void die_builtin(const char *err, va_list params)\n {\n+\t/*\n+\t * We call this trace2 function first and expect it to va_copy 'params'\n+\t * before using it (because an 'ap' can only be walked once).\n+\t */\n+\ttrace2_cmd_error_va(err, params);\n+\n \tvreportf(\"fatal: \", err, params);\n+\n \texit(128);\n }\n \n static void error_builtin(const char *err, va_list params)\n {\n+\t/*\n+\t * We call this trace2 function first and expect it to va_copy 'params'\n+\t * before using it (because an 'ap' can only be walked once).\n+\t */\n+\ttrace2_cmd_error_va(err, params);\n+\n \tvreportf(\"error: \", err, params);\n }\n \n-- \ngitgitgadget\n\n"},{"id":"369996","messageId":"ec7d7cd4c773eeb29dc8c371c5d9eb223c70e509.1550874298.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v7.git.gitgitgadget@gmail.com","subject":"[PATCH v7 06/15] trace2:data: add trace2 sub-process classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-22T22:25:05Z","receivedAt":"2019-02-22T22:25:23Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 classification for long-running processes\nstarted in sub-process.c\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n sub-process.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/sub-process.c b/sub-process.c\nindex 8d2a1707cf..3f4af93555 100644\n--- a/sub-process.c\n+++ b/sub-process.c\n@@ -88,6 +88,7 @@ int subprocess_start(struct hashmap *hashmap, struct subprocess_entry *entry, co\n \tprocess->out = -1;\n \tprocess->clean_on_exit = 1;\n \tprocess->clean_on_exit_handler = subprocess_exit_handler;\n+\tprocess->trace2_child_class = \"subprocess\";\n \n \terr = start_command(process);\n \tif (err) {\n-- \ngitgitgadget\n\n"},{"id":"369998","messageId":"18ff59d6fec8caeb0844fe8c3130daa6f60fed4e.1550874298.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v7.git.gitgitgadget@gmail.com","subject":"[PATCH v7 05/15] trace2:data: add editor/pager child classification","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-22T22:25:04Z","receivedAt":"2019-02-22T22:25:24Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2 process classification for editor and pager\nchild processes.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n editor.c | 1 +\n pager.c  | 1 +\n 2 files changed, 2 insertions(+)\n\ndiff --git a/editor.c b/editor.c\nindex c985eee1f9..71547674ab 100644\n--- a/editor.c\n+++ b/editor.c\n@@ -78,6 +78,7 @@ static int launch_specified_editor(const char *editor, const char *path,\n \t\tp.argv = args;\n \t\tp.env = env;\n \t\tp.use_shell = 1;\n+\t\tp.trace2_child_class = \"editor\";\n \t\tif (start_command(&p) < 0)\n \t\t\treturn error(\"unable to start editor '%s'\", editor);\n \ndiff --git a/pager.c b/pager.c\nindex a768797fcf..4168460ae9 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -100,6 +100,7 @@ void prepare_pager_args(struct child_process *pager_process, const char *pager)\n \targv_array_push(&pager_process->args, pager);\n \tpager_process->use_shell = 1;\n \tsetup_pager_env(&pager_process->env_array);\n+\tpager_process->trace2_child_class = \"pager\";\n }\n \n void setup_pager(void)\n-- \ngitgitgadget\n\n"},{"id":"369997","messageId":"40411100c3dfe142646fe0eac66e138d0ce062d1.1550874298.git.gitgitgadget@gmail.com","threadId":"50293","inReplyTo":"pull.108.v7.git.gitgitgadget@gmail.com","subject":"[PATCH v7 04/15] trace2:data: add trace2 regions to wt-status","fromName":"Jeff Hostetler via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2019-02-22T22:25:03Z","receivedAt":"2019-02-22T22:25:25Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd trace2_region_enter() and trace2_region_leave() calls around the\nvarious phases of a status scan.  This gives elapsed time for each\nphase in the GIT_TR2_PERF and GIT_TR2_EVENT trace target.\n\nAlso, these Trace2 calls now use s->repo rather than the_repository.\n\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n wt-status.c | 24 ++++++++++++++++++++++--\n 1 file changed, 22 insertions(+), 2 deletions(-)\n\ndiff --git a/wt-status.c b/wt-status.c\nindex 0fe3bcd4cd..434636850e 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -748,12 +748,23 @@ static int has_unmerged(struct wt_status *s)\n \n void wt_status_collect(struct wt_status *s)\n {\n+\ttrace2_region_enter(\"status\", \"worktrees\", s->repo);\n \twt_status_collect_changes_worktree(s);\n-\tif (s->is_initial)\n+\ttrace2_region_leave(\"status\", \"worktrees\", s->repo);\n+\n+\tif (s->is_initial) {\n+\t\ttrace2_region_enter(\"status\", \"initial\", s->repo);\n \t\twt_status_collect_changes_initial(s);\n-\telse\n+\t\ttrace2_region_leave(\"status\", \"initial\", s->repo);\n+\t} else {\n+\t\ttrace2_region_enter(\"status\", \"index\", s->repo);\n \t\twt_status_collect_changes_index(s);\n+\t\ttrace2_region_leave(\"status\", \"index\", s->repo);\n+\t}\n+\n+\ttrace2_region_enter(\"status\", \"untracked\", s->repo);\n \twt_status_collect_untracked(s);\n+\ttrace2_region_leave(\"status\", \"untracked\", s->repo);\n \n \twt_status_get_state(s->repo, &s->state, s->branch && !strcmp(s->branch, \"HEAD\"));\n \tif (s->state.merge_in_progress && !has_unmerged(s))\n@@ -2291,6 +2302,13 @@ static void wt_porcelain_v2_print(struct wt_status *s)\n \n void wt_status_print(struct wt_status *s)\n {\n+\ttrace2_data_intmax(\"status\", s->repo, \"count/changed\", s->change.nr);\n+\ttrace2_data_intmax(\"status\", s->repo, \"count/untracked\",\n+\t\t\t   s->untracked.nr);\n+\ttrace2_data_intmax(\"status\", s->repo, \"count/ignored\", s->ignored.nr);\n+\n+\ttrace2_region_enter(\"status\", \"print\", s->repo);\n+\n \tswitch (s->status_format) {\n \tcase STATUS_FORMAT_SHORT:\n \t\twt_shortstatus_print(s);\n@@ -2309,6 +2327,8 @@ void wt_status_print(struct wt_status *s)\n \t\twt_longstatus_print(s);\n \t\tbreak;\n \t}\n+\n+\ttrace2_region_leave(\"status\", \"print\", s->repo);\n }\n \n /**\n-- \ngitgitgadget\n\n"},{"id":"371728","messageId":"87k1gxa8cm.fsf@evledraar.gmail.com","threadId":"50293","inReplyTo":"0aff8302-371e-cb5e-fe5e-f08e45456559@jeffhostetler.com","subject":"Re: [PATCH v6 00/15] Trace2 tracing facility","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-03-17T14:22:49Z","receivedAt":"2019-03-17T14:22:53Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Feb 15 2019, Jeff Hostetler wrote:\n\n> On 2/14/2019 7:33 AM, Ævar Arnfjörð Bjarmason wrote:\n>>\n>> On Wed, Feb 06 2019, Jeff Hostetler via GitGitGadget wrote:\n>>\n>>> V6 addresses: [] The remaining hdr-check warning in trace2/tr2_tls.h\n>>>\n>>> There are no other outstanding comments that I'm aware of.\n>>\n>> Not a comment on this, just a follow-up question. I started looking into\n>> whether this could be driven by config instead of getenv(). A lot easier\n>> to set up in some cases than injecting env variables, especialy if the\n>> log target supported a strftime() string, is any of that something\n>> you've looked into already (so I don't do dupe work...).\n>>\n>> There's the chicken & egg problem with wanting to do traces way before\n>> we get to reading config, so I expect that such a facility would need to\n>> work by always trace record at the beginning until we get far enough to\n>> write the config, and then either stop and throw away the buffer, or\n>> write out the existing trace to the configured target, and continue.\n>>\n>\n> Yes, I beat my head against the config settings for quite a while\n> before settling on using an env var.\n>\n> I wanted to get the:\n> () full process elapsed time,\n> () the full original argv,\n> () all of the command dispatch, run-dashed, and alias expansion,\n> () and for my atexit() to run last.\n> So I hooked into main() before the config is loaded.\n>\n> In most commands, the config is processed about the same time as\n> parse_options() is called.  And we have to insert code in\n> git_default_config() to load my settings.  This happens after all\n> of the .git dir discovery, \"-c\" and \"-C\" processing, alias expansion,\n> command dispatch and etc.  And the argv received in the cmd_*()\n> function has been modified.  So we lose some information.  (I tried\n> this for a while and didn't like the results.)\n>\n> I also tried using read_early_config() various places, but there\n> were problems here too.  Too early and the \"-c\" settings weren't\n> parsed yet.  And there was an issue about when .git dir was discovered,\n> so local config settings weren't ready yet.\n>\n> I also recall having a problem when doing an early iteration with\n> side effects (or rather the early iteration caused something to be\n> set that caused the real iteration (in cmd_*()) to short-cut), but\n> I don't remember the details.\n>\n> So we have a custom installer that also sets the environment variable\n> after git is installed and haven't had any problems.\n>\n>\n> I hesitate to say we should always start allocating a bunch of data\n> and spinning up the TLS data and etc. before we know if tracing is\n> wanted.  Just seems expensive for most users.\n>\n>\n> I could see having a \"~/.git_tr2_config\" or something similar in\n> some place like \"/etc\" that only contained the Trace2 settings.\n> It would be safe to read very early inside main() and we would not\n> consider it to be part of the real config.  For example, \"git config\"\n> would not know about it.  Then you could enforce a system-wide\n> setting without any of the env var issues.\n\nI haven't written a patch for this, but it seems to me that we could\njust start reading /etc/gitconfig via some custom config callback code\nearly as e.g. 58b284a2e91 (\"worktree: add per-worktree config files\",\n2018-10-21) does for the worktree config.\n\nIt would ignore everything except trace.* or wherever namespace we'll\nput this in, and \"-c\" etc. wouldn't work. We could just document that as\na limitation for now which could be fixed later.\n\nIt wouldn't make things worse, and would mean you could easily set\nlogging system-wide without needing to inject environment variables in\nlots of custom (and sometimes hard-to-do) places, which I expect is the\nmost common use-case, and is the one I have.\n\n> WRT the strftime() question, we could either add syntax to the\n> env var value (or the tr2 config setting) to have some tokens\n> for that.  I just stuck with absolute pathnames since I started\n> by copying what was done for GIT_TRACE.\n>\n> Jeff\n"},{"id":"372132","messageId":"nycvar.QRO.7.76.6.1903211528090.41@tvgsbejvaqbjf.bet","threadId":"50293","inReplyTo":"87k1gxa8cm.fsf@evledraar.gmail.com","subject":"Re: [PATCH v6 00/15] Trace2 tracing facility","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-03-21T14:30:00Z","receivedAt":"2019-03-21T14:30:29Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Ævar,\n\nOn Sun, 17 Mar 2019, Ævar Arnfjörð Bjarmason wrote:\n\n>\n> On Fri, Feb 15 2019, Jeff Hostetler wrote:\n>\n> > On 2/14/2019 7:33 AM, Ævar Arnfjörð Bjarmason wrote:\n> >>\n> >> On Wed, Feb 06 2019, Jeff Hostetler via GitGitGadget wrote:\n> >>\n> >>> V6 addresses: [] The remaining hdr-check warning in trace2/tr2_tls.h\n> >>>\n> >>> There are no other outstanding comments that I'm aware of.\n> >>\n> >> Not a comment on this, just a follow-up question. I started looking into\n> >> whether this could be driven by config instead of getenv(). A lot easier\n> >> to set up in some cases than injecting env variables, especialy if the\n> >> log target supported a strftime() string, is any of that something\n> >> you've looked into already (so I don't do dupe work...).\n> >>\n> >> There's the chicken & egg problem with wanting to do traces way before\n> >> we get to reading config, so I expect that such a facility would need to\n> >> work by always trace record at the beginning until we get far enough to\n> >> write the config, and then either stop and throw away the buffer, or\n> >> write out the existing trace to the configured target, and continue.\n> >>\n> >\n> > Yes, I beat my head against the config settings for quite a while\n> > before settling on using an env var.\n> >\n> > I wanted to get the:\n> > () full process elapsed time,\n> > () the full original argv,\n> > () all of the command dispatch, run-dashed, and alias expansion,\n> > () and for my atexit() to run last.\n> > So I hooked into main() before the config is loaded.\n> >\n> > In most commands, the config is processed about the same time as\n> > parse_options() is called.  And we have to insert code in\n> > git_default_config() to load my settings.  This happens after all\n> > of the .git dir discovery, \"-c\" and \"-C\" processing, alias expansion,\n> > command dispatch and etc.  And the argv received in the cmd_*()\n> > function has been modified.  So we lose some information.  (I tried\n> > this for a while and didn't like the results.)\n> >\n> > I also tried using read_early_config() various places, but there\n> > were problems here too.  Too early and the \"-c\" settings weren't\n> > parsed yet.  And there was an issue about when .git dir was discovered,\n> > so local config settings weren't ready yet.\n> >\n> > I also recall having a problem when doing an early iteration with\n> > side effects (or rather the early iteration caused something to be\n> > set that caused the real iteration (in cmd_*()) to short-cut), but\n> > I don't remember the details.\n> >\n> > So we have a custom installer that also sets the environment variable\n> > after git is installed and haven't had any problems.\n> >\n> >\n> > I hesitate to say we should always start allocating a bunch of data\n> > and spinning up the TLS data and etc. before we know if tracing is\n> > wanted.  Just seems expensive for most users.\n> >\n> >\n> > I could see having a \"~/.git_tr2_config\" or something similar in\n> > some place like \"/etc\" that only contained the Trace2 settings.\n> > It would be safe to read very early inside main() and we would not\n> > consider it to be part of the real config.  For example, \"git config\"\n> > would not know about it.  Then you could enforce a system-wide\n> > setting without any of the env var issues.\n>\n> I haven't written a patch for this, but it seems to me that we could\n> just start reading /etc/gitconfig via some custom config callback code\n> early as e.g. 58b284a2e91 (\"worktree: add per-worktree config files\",\n> 2018-10-21) does for the worktree config.\n\nOy. Oy, oy, oy.\n\nMaybe use `read_early_config()` instead? That would be *a lot* cleaner.\nYou could maybe use a9bcf6586d1a (alias: use the early config machinery to\nexpand aliases, 2017-06-14) as an inspiration.\n\n> It would ignore everything except trace.* or wherever namespace we'll\n> put this in, and \"-c\" etc. wouldn't work. We could just document that as\n> a limitation for now which could be fixed later.\n>\n> It wouldn't make things worse, and would mean you could easily set\n> logging system-wide without needing to inject environment variables in\n> lots of custom (and sometimes hard-to-do) places, which I expect is the\n> most common use-case, and is the one I have.\n\nYes, I agree, those are good goals to address.\n\nCiao,\nDscho\n\n> > WRT the strftime() question, we could either add syntax to the\n> > env var value (or the tr2 config setting) to have some tokens\n> > for that.  I just stuck with absolute pathnames since I started\n> > by copying what was done for GIT_TRACE.\n> >\n> > Jeff\n>\n"},{"id":"372138","messageId":"87mulocmgz.fsf@evledraar.gmail.com","threadId":"50293","inReplyTo":"nycvar.QRO.7.76.6.1903211528090.41@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v6 00/15] Trace2 tracing facility","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-03-21T14:48:28Z","receivedAt":"2019-03-21T14:48:54Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Mar 21 2019, Johannes Schindelin wrote:\n\n> Hi Ævar,\n>\n> On Sun, 17 Mar 2019, Ævar Arnfjörð Bjarmason wrote:\n>\n>>\n>> On Fri, Feb 15 2019, Jeff Hostetler wrote:\n>>\n>> > On 2/14/2019 7:33 AM, Ævar Arnfjörð Bjarmason wrote:\n>> >>\n>> >> On Wed, Feb 06 2019, Jeff Hostetler via GitGitGadget wrote:\n>> >>\n>> >>> V6 addresses: [] The remaining hdr-check warning in trace2/tr2_tls.h\n>> >>>\n>> >>> There are no other outstanding comments that I'm aware of.\n>> >>\n>> >> Not a comment on this, just a follow-up question. I started looking into\n>> >> whether this could be driven by config instead of getenv(). A lot easier\n>> >> to set up in some cases than injecting env variables, especialy if the\n>> >> log target supported a strftime() string, is any of that something\n>> >> you've looked into already (so I don't do dupe work...).\n>> >>\n>> >> There's the chicken & egg problem with wanting to do traces way before\n>> >> we get to reading config, so I expect that such a facility would need to\n>> >> work by always trace record at the beginning until we get far enough to\n>> >> write the config, and then either stop and throw away the buffer, or\n>> >> write out the existing trace to the configured target, and continue.\n>> >>\n>> >\n>> > Yes, I beat my head against the config settings for quite a while\n>> > before settling on using an env var.\n>> >\n>> > I wanted to get the:\n>> > () full process elapsed time,\n>> > () the full original argv,\n>> > () all of the command dispatch, run-dashed, and alias expansion,\n>> > () and for my atexit() to run last.\n>> > So I hooked into main() before the config is loaded.\n>> >\n>> > In most commands, the config is processed about the same time as\n>> > parse_options() is called.  And we have to insert code in\n>> > git_default_config() to load my settings.  This happens after all\n>> > of the .git dir discovery, \"-c\" and \"-C\" processing, alias expansion,\n>> > command dispatch and etc.  And the argv received in the cmd_*()\n>> > function has been modified.  So we lose some information.  (I tried\n>> > this for a while and didn't like the results.)\n>> >\n>> > I also tried using read_early_config() various places, but there\n>> > were problems here too.  Too early and the \"-c\" settings weren't\n>> > parsed yet.  And there was an issue about when .git dir was discovered,\n>> > so local config settings weren't ready yet.\n>> >\n>> > I also recall having a problem when doing an early iteration with\n>> > side effects (or rather the early iteration caused something to be\n>> > set that caused the real iteration (in cmd_*()) to short-cut), but\n>> > I don't remember the details.\n>> >\n>> > So we have a custom installer that also sets the environment variable\n>> > after git is installed and haven't had any problems.\n>> >\n>> >\n>> > I hesitate to say we should always start allocating a bunch of data\n>> > and spinning up the TLS data and etc. before we know if tracing is\n>> > wanted.  Just seems expensive for most users.\n>> >\n>> >\n>> > I could see having a \"~/.git_tr2_config\" or something similar in\n>> > some place like \"/etc\" that only contained the Trace2 settings.\n>> > It would be safe to read very early inside main() and we would not\n>> > consider it to be part of the real config.  For example, \"git config\"\n>> > would not know about it.  Then you could enforce a system-wide\n>> > setting without any of the env var issues.\n>>\n>> I haven't written a patch for this, but it seems to me that we could\n>> just start reading /etc/gitconfig via some custom config callback code\n>> early as e.g. 58b284a2e91 (\"worktree: add per-worktree config files\",\n>> 2018-10-21) does for the worktree config.\n>\n> Oy. Oy, oy, oy.\n>\n> Maybe use `read_early_config()` instead? That would be *a lot* cleaner.\n> You could maybe use a9bcf6586d1a (alias: use the early config machinery to\n> expand aliases, 2017-06-14) as an inspiration.\n\nThanks. I was thinking *only* to do /etc/gitconfig and not the whole\n.git/config -> ~/.gitconfig etc. sequence just in terms of saving\ncritical time (this is the performance trace path, after all...).\n\nBut on a second reading I see that read_early_config() can do that if\nyou set config_source->file, opts->respect_includes etc. I.e. it just\n(depending on options) resolves to git_config_from_file() which\n58b284a2e91 used directly.\n\n>> It would ignore everything except trace.* or wherever namespace we'll\n>> put this in, and \"-c\" etc. wouldn't work. We could just document that as\n>> a limitation for now which could be fixed later.\n>>\n>> It wouldn't make things worse, and would mean you could easily set\n>> logging system-wide without needing to inject environment variables in\n>> lots of custom (and sometimes hard-to-do) places, which I expect is the\n>> most common use-case, and is the one I have.\n>\n> Yes, I agree, those are good goals to address.\n>\n> Ciao,\n> Dscho\n>\n>> > WRT the strftime() question, we could either add syntax to the\n>> > env var value (or the tr2 config setting) to have some tokens\n>> > for that.  I just stuck with absolute pathnames since I started\n>> > by copying what was done for GIT_TRACE.\n>> >\n>> > Jeff\n>>\n"},{"id":"372238","messageId":"nycvar.QRO.7.76.6.1903221416230.41@tvgsbejvaqbjf.bet","threadId":"50293","inReplyTo":"87mulocmgz.fsf@evledraar.gmail.com","subject":"Re: [PATCH v6 00/15] Trace2 tracing facility","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-03-22T13:17:22Z","receivedAt":"2019-03-22T13:17:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Ævar,\n\nOn Thu, 21 Mar 2019, Ævar Arnfjörð Bjarmason wrote:\n\n> On Thu, Mar 21 2019, Johannes Schindelin wrote:\n>\n> > On Sun, 17 Mar 2019, Ævar Arnfjörð Bjarmason wrote:\n> >\n> >>\n> >> On Fri, Feb 15 2019, Jeff Hostetler wrote:\n> >>\n> >> > I could see having a \"~/.git_tr2_config\" or something similar in\n> >> > some place like \"/etc\" that only contained the Trace2 settings. It\n> >> > would be safe to read very early inside main() and we would not\n> >> > consider it to be part of the real config.  For example, \"git\n> >> > config\" would not know about it.  Then you could enforce a\n> >> > system-wide setting without any of the env var issues.\n> >>\n> >> I haven't written a patch for this, but it seems to me that we could\n> >> just start reading /etc/gitconfig via some custom config callback\n> >> code early as e.g. 58b284a2e91 (\"worktree: add per-worktree config\n> >> files\", 2018-10-21) does for the worktree config.\n> >\n> > Oy. Oy, oy, oy.\n> >\n> > Maybe use `read_early_config()` instead? That would be *a lot*\n> > cleaner. You could maybe use a9bcf6586d1a (alias: use the early config\n> > machinery to expand aliases, 2017-06-14) as an inspiration.\n>\n> Thanks. I was thinking *only* to do /etc/gitconfig and not the whole\n> .git/config -> ~/.gitconfig etc. sequence just in terms of saving\n> critical time (this is the performance trace path, after all...).\n>\n> But on a second reading I see that read_early_config() can do that if\n> you set config_source->file, opts->respect_includes etc. I.e. it just\n> (depending on options) resolves to git_config_from_file() which\n> 58b284a2e91 used directly.\n\nSure, it can exclude the repo and user config, but would that not be\nrather confusing?\n\nCiao,\nDscho\n"},{"id":"372241","messageId":"37cffd65-58bd-3fae-a4f1-44038ed52f60@jeffhostetler.com","threadId":"50293","inReplyTo":"nycvar.QRO.7.76.6.1903221416230.41@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v6 00/15] Trace2 tracing facility","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2019-03-22T14:01:56Z","receivedAt":"2019-03-22T14:01:59Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 3/22/2019 9:17 AM, Johannes Schindelin wrote:\n> Hi Ævar,\n> \n> On Thu, 21 Mar 2019, Ævar Arnfjörð Bjarmason wrote:\n> \n>> On Thu, Mar 21 2019, Johannes Schindelin wrote:\n>>\n>>> On Sun, 17 Mar 2019, Ævar Arnfjörð Bjarmason wrote:\n>>>\n>>>>\n>>>> On Fri, Feb 15 2019, Jeff Hostetler wrote:\n>>>>\n>>>>> I could see having a \"~/.git_tr2_config\" or something similar in\n>>>>> some place like \"/etc\" that only contained the Trace2 settings. It\n>>>>> would be safe to read very early inside main() and we would not\n>>>>> consider it to be part of the real config.  For example, \"git\n>>>>> config\" would not know about it.  Then you could enforce a\n>>>>> system-wide setting without any of the env var issues.\n>>>>\n>>>> I haven't written a patch for this, but it seems to me that we could\n>>>> just start reading /etc/gitconfig via some custom config callback\n>>>> code early as e.g. 58b284a2e91 (\"worktree: add per-worktree config\n>>>> files\", 2018-10-21) does for the worktree config.\n>>>\n>>> Oy. Oy, oy, oy.\n>>>\n>>> Maybe use `read_early_config()` instead? That would be *a lot*\n>>> cleaner. You could maybe use a9bcf6586d1a (alias: use the early config\n>>> machinery to expand aliases, 2017-06-14) as an inspiration.\n>>\n>> Thanks. I was thinking *only* to do /etc/gitconfig and not the whole\n>> .git/config -> ~/.gitconfig etc. sequence just in terms of saving\n>> critical time (this is the performance trace path, after all...).\n>>\n>> But on a second reading I see that read_early_config() can do that if\n>> you set config_source->file, opts->respect_includes etc. I.e. it just\n>> (depending on options) resolves to git_config_from_file() which\n>> 58b284a2e91 used directly.\n> \n> Sure, it can exclude the repo and user config, but would that not be\n> rather confusing?\n\nThis was hidden in my earlier message.\n\nThere's a lot a config machinery here with lots of chicken-n-egg\nproblems.  I want to know at the top of main() as quickly as possible\nwhether trace2 should be enabled.\n\nI don't want to slow down git by spinning up a bunch of trace2 state\nand wait until the git-dir is discovered, the \"-c\" args are processed,\nand we dispatch into the builtin layer and the config is enumerated\nto know if it should really be on or not.\n\nI also didn't want to introduce another full iteration of the full\nconfig system for startup performance reasons.\n\nI played with read_early_config() at one point and it always seemed\nto introduce side-effects.  Perhaps I was calling it earlier than it\nwas expecting and that triggered some of the git-dir discovery or\nsomething. I don't remember all the details, I just remember that it\nchanged some behaviors in subtle ways.\n\nPerhaps I could call something like git_config_from_file() with the\nright set of magic bits to get it to parse exactly 1 system config\nfile that would contain my trace2 settings.  Hopefully, this will\nnot have any side-effects.\n\nBut if we lump them in with the main /etc/gitconfig settings, we\nwould have to explain that these trace2 config settings are\nsystem-only and ARE NOT overridden by \"-c\", global, local, ...\nconfig settings.  This would get confusing if the user tried to\nset local values and did:\n\tgit config --list --show-origin\nand it showed system and local values but yet \"stubbornly\" refused\nto use the local values over the system values. (I think this was\nJohannes' point.)\n\nThat's why I was suggesting a system trace2 config file that is a\npeer of /etc/gitconfig (maybe /etc/gittrace2) that would have these\nvalues and not be expected to interact with the main config system.\nThat is, we just use the git_config_ routines to parse the file format,\nrather than inventing another file format, but not change the\nexpectation of the established config value inheritance system.\n\nIf there's no objections, I'll take a look at doing this.\n\nThanks\nJeff\n\n\n"},{"id":"372249","messageId":"87h8bvc65j.fsf@evledraar.gmail.com","threadId":"50293","inReplyTo":"37cffd65-58bd-3fae-a4f1-44038ed52f60@jeffhostetler.com","subject":"Re: [PATCH v6 00/15] Trace2 tracing facility","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-03-22T14:53:12Z","receivedAt":"2019-03-22T14:53:18Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Mar 22 2019, Jeff Hostetler wrote:\n\n> On 3/22/2019 9:17 AM, Johannes Schindelin wrote:\n>> Hi Ævar,\n>>\n>> On Thu, 21 Mar 2019, Ævar Arnfjörð Bjarmason wrote:\n>>\n>>> On Thu, Mar 21 2019, Johannes Schindelin wrote:\n>>>\n>>>> On Sun, 17 Mar 2019, Ævar Arnfjörð Bjarmason wrote:\n>>>>\n>>>>>\n>>>>> On Fri, Feb 15 2019, Jeff Hostetler wrote:\n>>>>>\n>>>>>> I could see having a \"~/.git_tr2_config\" or something similar in\n>>>>>> some place like \"/etc\" that only contained the Trace2 settings. It\n>>>>>> would be safe to read very early inside main() and we would not\n>>>>>> consider it to be part of the real config.  For example, \"git\n>>>>>> config\" would not know about it.  Then you could enforce a\n>>>>>> system-wide setting without any of the env var issues.\n>>>>>\n>>>>> I haven't written a patch for this, but it seems to me that we could\n>>>>> just start reading /etc/gitconfig via some custom config callback\n>>>>> code early as e.g. 58b284a2e91 (\"worktree: add per-worktree config\n>>>>> files\", 2018-10-21) does for the worktree config.\n>>>>\n>>>> Oy. Oy, oy, oy.\n>>>>\n>>>> Maybe use `read_early_config()` instead? That would be *a lot*\n>>>> cleaner. You could maybe use a9bcf6586d1a (alias: use the early config\n>>>> machinery to expand aliases, 2017-06-14) as an inspiration.\n>>>\n>>> Thanks. I was thinking *only* to do /etc/gitconfig and not the whole\n>>> .git/config -> ~/.gitconfig etc. sequence just in terms of saving\n>>> critical time (this is the performance trace path, after all...).\n>>>\n>>> But on a second reading I see that read_early_config() can do that if\n>>> you set config_source->file, opts->respect_includes etc. I.e. it just\n>>> (depending on options) resolves to git_config_from_file() which\n>>> 58b284a2e91 used directly.\n>>\n>> Sure, it can exclude the repo and user config, but would that not be\n>> rather confusing?\n>\n> This was hidden in my earlier message.\n>\n> There's a lot a config machinery here with lots of chicken-n-egg\n> problems.  I want to know at the top of main() as quickly as possible\n> whether trace2 should be enabled.\n>\n> I don't want to slow down git by spinning up a bunch of trace2 state\n> and wait until the git-dir is discovered, the \"-c\" args are processed,\n> and we dispatch into the builtin layer and the config is enumerated\n> to know if it should really be on or not.\n>\n> I also didn't want to introduce another full iteration of the full\n> config system for startup performance reasons.\n>\n> I played with read_early_config() at one point and it always seemed\n> to introduce side-effects.  Perhaps I was calling it earlier than it\n> was expecting and that triggered some of the git-dir discovery or\n> something. I don't remember all the details, I just remember that it\n> changed some behaviors in subtle ways.\n>\n> Perhaps I could call something like git_config_from_file() with the\n> right set of magic bits to get it to parse exactly 1 system config\n> file that would contain my trace2 settings.  Hopefully, this will\n> not have any side-effects.\n\nRight, it also occurred to me that e.g. /home tends to be on NFS on some\nsystems, but very rarely /etc. I'd hate for trace reporting for git to\nstall because NFS slows to a halt, so aside from temporary\nimplementation details (e.g. -c on the CLI not working) there's a good\ncase to be made for \"read this from /etc/gitconfig only\".\n\n> But if we lump them in with the main /etc/gitconfig settings, we\n> would have to explain that these trace2 config settings are\n> system-only and ARE NOT overridden by \"-c\", global, local, ...\n> config settings.  This would get confusing if the user tried to\n> set local values and did:\n> \tgit config --list --show-origin\n> and it showed system and local values but yet \"stubbornly\" refused\n> to use the local values over the system values. (I think this was\n> Johannes' point.)\n>\n> That's why I was suggesting a system trace2 config file that is a\n> peer of /etc/gitconfig (maybe /etc/gittrace2) that would have these\n> values and not be expected to interact with the main config system.\n> That is, we just use the git_config_ routines to parse the file format,\n> rather than inventing another file format, but not change the\n> expectation of the established config value inheritance system.\n>\n> If there's no objections, I'll take a look at doing this.\n\nI'd much rather just drop it in /etc/gitconfig with documented caveats\nthan introduce a new and permanent thing like /etc/gittrace2 due to a\ncurrent implementation detail.\n\nUnlike something in core.* or whatever this facility is specialized\nenough that I think it's fine to make it a bit of a special snowflake\ngiven what it does and the target audience.\n\nBut even with those caveats it's still useful to see it in 'git config\n-l --show-origin' for inspecting, and e.g. have it just work out of the\nbox with say the default puppetry for the likes of GitLab that now knows\nhow to set stuff in its /etc/gitconfig, but would need a special case\njust for this.\n"},{"id":"372253","messageId":"4c5cef43-2f35-124a-b88c-397866b6111f@jeffhostetler.com","threadId":"50293","inReplyTo":"87h8bvc65j.fsf@evledraar.gmail.com","subject":"Re: [PATCH v6 00/15] Trace2 tracing facility","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2019-03-22T15:21:19Z","receivedAt":"2019-03-22T15:21:23Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 3/22/2019 10:53 AM, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Fri, Mar 22 2019, Jeff Hostetler wrote:\n> \n>> On 3/22/2019 9:17 AM, Johannes Schindelin wrote:\n>>> Hi Ævar,\n>>>\n>>> On Thu, 21 Mar 2019, Ævar Arnfjörð Bjarmason wrote:\n>>>\n>>>> On Thu, Mar 21 2019, Johannes Schindelin wrote:\n>>>>\n>>>>> On Sun, 17 Mar 2019, Ævar Arnfjörð Bjarmason wrote:\n>>>>>\n>>>>>>\n>>>>>> On Fri, Feb 15 2019, Jeff Hostetler wrote:\n>>>>>>\n>>>>>>> I could see having a \"~/.git_tr2_config\" or something similar in\n>>>>>>> some place like \"/etc\" that only contained the Trace2 settings. It\n>>>>>>> would be safe to read very early inside main() and we would not\n>>>>>>> consider it to be part of the real config.  For example, \"git\n>>>>>>> config\" would not know about it.  Then you could enforce a\n>>>>>>> system-wide setting without any of the env var issues.\n>>>>>>\n>>>>>> I haven't written a patch for this, but it seems to me that we could\n>>>>>> just start reading /etc/gitconfig via some custom config callback\n>>>>>> code early as e.g. 58b284a2e91 (\"worktree: add per-worktree config\n>>>>>> files\", 2018-10-21) does for the worktree config.\n>>>>>\n>>>>> Oy. Oy, oy, oy.\n>>>>>\n>>>>> Maybe use `read_early_config()` instead? That would be *a lot*\n>>>>> cleaner. You could maybe use a9bcf6586d1a (alias: use the early config\n>>>>> machinery to expand aliases, 2017-06-14) as an inspiration.\n>>>>\n>>>> Thanks. I was thinking *only* to do /etc/gitconfig and not the whole\n>>>> .git/config -> ~/.gitconfig etc. sequence just in terms of saving\n>>>> critical time (this is the performance trace path, after all...).\n>>>>\n>>>> But on a second reading I see that read_early_config() can do that if\n>>>> you set config_source->file, opts->respect_includes etc. I.e. it just\n>>>> (depending on options) resolves to git_config_from_file() which\n>>>> 58b284a2e91 used directly.\n>>>\n>>> Sure, it can exclude the repo and user config, but would that not be\n>>> rather confusing?\n>>\n>> This was hidden in my earlier message.\n>>\n>> There's a lot a config machinery here with lots of chicken-n-egg\n>> problems.  I want to know at the top of main() as quickly as possible\n>> whether trace2 should be enabled.\n>>\n>> I don't want to slow down git by spinning up a bunch of trace2 state\n>> and wait until the git-dir is discovered, the \"-c\" args are processed,\n>> and we dispatch into the builtin layer and the config is enumerated\n>> to know if it should really be on or not.\n>>\n>> I also didn't want to introduce another full iteration of the full\n>> config system for startup performance reasons.\n>>\n>> I played with read_early_config() at one point and it always seemed\n>> to introduce side-effects.  Perhaps I was calling it earlier than it\n>> was expecting and that triggered some of the git-dir discovery or\n>> something. I don't remember all the details, I just remember that it\n>> changed some behaviors in subtle ways.\n>>\n>> Perhaps I could call something like git_config_from_file() with the\n>> right set of magic bits to get it to parse exactly 1 system config\n>> file that would contain my trace2 settings.  Hopefully, this will\n>> not have any side-effects.\n> \n> Right, it also occurred to me that e.g. /home tends to be on NFS on some\n> systems, but very rarely /etc. I'd hate for trace reporting for git to\n> stall because NFS slows to a halt, so aside from temporary\n> implementation details (e.g. -c on the CLI not working) there's a good\n> case to be made for \"read this from /etc/gitconfig only\".\n> \n>> But if we lump them in with the main /etc/gitconfig settings, we\n>> would have to explain that these trace2 config settings are\n>> system-only and ARE NOT overridden by \"-c\", global, local, ...\n>> config settings.  This would get confusing if the user tried to\n>> set local values and did:\n>> \tgit config --list --show-origin\n>> and it showed system and local values but yet \"stubbornly\" refused\n>> to use the local values over the system values. (I think this was\n>> Johannes' point.)\n>>\n>> That's why I was suggesting a system trace2 config file that is a\n>> peer of /etc/gitconfig (maybe /etc/gittrace2) that would have these\n>> values and not be expected to interact with the main config system.\n>> That is, we just use the git_config_ routines to parse the file format,\n>> rather than inventing another file format, but not change the\n>> expectation of the established config value inheritance system.\n>>\n>> If there's no objections, I'll take a look at doing this.\n> \n> I'd much rather just drop it in /etc/gitconfig with documented caveats\n> than introduce a new and permanent thing like /etc/gittrace2 due to a\n> current implementation detail.\n> \n> Unlike something in core.* or whatever this facility is specialized\n> enough that I think it's fine to make it a bit of a special snowflake\n> given what it does and the target audience.\n> \n> But even with those caveats it's still useful to see it in 'git config\n> -l --show-origin' for inspecting, and e.g. have it just work out of the\n> box with say the default puppetry for the likes of GitLab that now knows\n> how to set stuff in its /etc/gitconfig, but would need a special case\n> just for this.\n> \n\nok, if we're comfortable with those caveats, i'll proceed as\nyou suggested. thanks for the quick response.\n\nJeff\n"},{"id":"375292","messageId":"20190510133738.4511-1-avarab@gmail.com","threadId":"50293","inReplyTo":"08be5aadbef7f6b404d2e1c47cf3657ced585ba1.1550874298.git.gitgitgadget@gmail.com","subject":"[PATCH] trace2: fix up a missing \"leave\" entry point","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-05-10T13:37:38Z","receivedAt":"2019-05-10T13:38:09Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Fix a trivial bug that's been here since the shared/do_write_index\ntracing was added in 42fee7a388 (\"trace2:data: add trace2\ninstrumentation to index read/write\", 2019-02-22). We should have\nenter/leave points, not two enter/enter points. This resulted in an\n\"enter\" event without a corresponding \"leave\" event.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n read-cache.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex 61b043bac3..4fad4e3f9a 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -3131,7 +3131,7 @@ static int write_shared_index(struct index_state *istate,\n \ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n \t\t\t\t   the_repository, \"%s\", (*temp)->filename.buf);\n \tret = do_write_index(si->base, *temp, 1);\n-\ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n+\ttrace2_region_leave_printf(\"index\", \"shared/do_write_index\",\n \t\t\t\t   the_repository, \"%s\", (*temp)->filename.buf);\n \n \tif (ret)\n-- \n2.21.0.1020.gf2820cf01a\n\n"},{"id":"375293","messageId":"1242af07-aa42-140b-0866-6227df52a886@gmail.com","threadId":"50293","inReplyTo":"20190510133738.4511-1-avarab@gmail.com","subject":"Re: [PATCH] trace2: fix up a missing \"leave\" entry point","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-05-10T13:45:23Z","receivedAt":"2019-05-10T13:45:27Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 5/10/2019 9:37 AM, Ævar Arnfjörð Bjarmason wrote:\n> Fix a trivial bug that's been here since the shared/do_write_index\n> tracing was added in 42fee7a388 (\"trace2:data: add trace2\n> instrumentation to index read/write\", 2019-02-22). We should have\n> enter/leave points, not two enter/enter points. This resulted in an\n> \"enter\" event without a corresponding \"leave\" event.\n> \n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> ---\n>  read-cache.c | 2 +-\n>  1 file changed, 1 insertion(+), 1 deletion(-)\n> \n> diff --git a/read-cache.c b/read-cache.c\n> index 61b043bac3..4fad4e3f9a 100644\n> --- a/read-cache.c\n> +++ b/read-cache.c\n> @@ -3131,7 +3131,7 @@ static int write_shared_index(struct index_state *istate,\n>  \ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n>  \t\t\t\t   the_repository, \"%s\", (*temp)->filename.buf);\n>  \tret = do_write_index(si->base, *temp, 1);\n> -\ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n> +\ttrace2_region_leave_printf(\"index\", \"shared/do_write_index\",\n>  \t\t\t\t   the_repository, \"%s\", (*temp)->filename.buf);\n>  \n>  \tif (ret)\n> \n\nThanks! Obviously correct.\n\n-Stolee\n"},{"id":"375307","messageId":"20190510172824.GR14763@szeder.dev","threadId":"50293","inReplyTo":"90e4e6af9300751fd2e7e98601ed0e60ca048e05.1550874298.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v7 02/15] trace2: create new combined trace facility","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2019-05-10T17:28:24Z","receivedAt":"2019-05-10T17:28:31Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Fri, Feb 22, 2019 at 02:25:01PM -0800, Jeff Hostetler via GitGitGadget wrote:\n> From: Jeff Hostetler <jeffhost@microsoft.com>\n> \n> Create a new unified tracing facility for git.  The eventual intent is to\n> replace the current trace_printf* and trace_performance* routines with a\n> unified set of git_trace2* routines.\n> \n> In addition to the usual printf-style API, trace2 provides higer-level\n> event verbs with fixed-fields allowing structured data to be written.\n> This makes post-processing and analysis easier for external tools.\n> \n> Trace2 defines 3 output targets.  These are set using the environment\n> variables \"GIT_TR2\", \"GIT_TR2_PERF\", and \"GIT_TR2_EVENT\".  These may be\n> set to \"1\" or to an absolute pathname (just like the current GIT_TRACE).\n> \n> * GIT_TR2 is intended to be a replacement for GIT_TRACE and logs command\n>   summary data.\n> \n> * GIT_TR2_PERF is intended as a replacement for GIT_TRACE_PERFORMANCE.\n>   It extends the output with columns for the command process, thread,\n>   repo, absolute and relative elapsed times.  It reports events for\n>   child process start/stop, thread start/stop, and per-thread function\n>   nesting.\n> \n> * GIT_TR2_EVENT is a new structured format. It writes event data as a\n>   series of JSON records.\n\nPlease document these new environment variables in\n'Documentation/git.txt', where the other environment variables,\nincluding GIT_TRACE_*, are already documented.\n\nWhile doing so, please note that the description about the possible\nvalues of these variables and of GIT_TRACE above is incomplete,\nbecause it's not just \"1\" or an absolute pathname.  Quoting the\ndescription of GIT_TRACE:\n\n  If this variable is set to \"1\", \"2\" or \"true\" (comparison is case\n  insensitive), trace messages will be printed to stderr.\n  \n  If the variable is set to an integer value greater than 2 and lower\n  than 10 (strictly) then Git will interpret this value as an open\n  file descriptor and will try to write the trace messages into this\n  file descriptor.\n\nThe way I see it in tr2_dst_get_trace_fd() below, this applies to\nGIT_TRACE2* as well, and it even offers the possibility to specify a\nUnix Domain Socket, too.\n\n\nAnd sorry for barging in with a big bucket of paint this late, but,\nreally...  why GIT_TR2 instead of GIT_TRACE2?\n\n\n> +int tr2_dst_get_trace_fd(struct tr2_dst *dst)\n> +{\n> +\tconst char *tgt_value;\n> +\n> +\t/* don't open twice */\n> +\tif (dst->initialized)\n> +\t\treturn dst->fd;\n> +\n> +\tdst->initialized = 1;\n> +\n> +\ttgt_value = getenv(dst->env_var_name);\n> +\n> +\tif (!tgt_value || !strcmp(tgt_value, \"\") || !strcmp(tgt_value, \"0\") ||\n> +\t    !strcasecmp(tgt_value, \"false\")) {\n> +\t\tdst->fd = 0;\n> +\t\treturn dst->fd;\n> +\t}\n> +\n> +\tif (!strcmp(tgt_value, \"1\") || !strcasecmp(tgt_value, \"true\")) {\n> +\t\tdst->fd = STDERR_FILENO;\n> +\t\treturn dst->fd;\n> +\t}\n> +\n> +\tif (strlen(tgt_value) == 1 && isdigit(*tgt_value)) {\n> +\t\tdst->fd = atoi(tgt_value);\n> +\t\treturn dst->fd;\n> +\t}\n> +\n> +\tif (is_absolute_path(tgt_value))\n> +\t\treturn tr2_dst_try_path(dst, tgt_value);\n> +\n> +#ifndef NO_UNIX_SOCKETS\n> +\tif (starts_with(tgt_value, PREFIX_AF_UNIX))\n> +\t\treturn tr2_dst_try_unix_domain_socket(dst, tgt_value);\n> +#endif\n> +\n> +\t/* Always warn about malformed values. */\n> +\ttr2_dst_malformed_warning(dst, tgt_value);\n> +\ttr2_dst_trace_disable(dst);\n> +\treturn 0;\n> +}\n"},{"id":"375313","messageId":"2cbc8e41-fe0e-ea2e-5550-0df30f15b293@gmail.com","threadId":"50293","inReplyTo":"20190510172824.GR14763@szeder.dev","subject":"Re: [PATCH v7 02/15] trace2: create new combined trace facility","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-05-10T19:54:11Z","receivedAt":"2019-05-10T19:54:18Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 5/10/2019 1:28 PM, SZEDER Gábor wrote:\n> On Fri, Feb 22, 2019 at 02:25:01PM -0800, Jeff Hostetler via GitGitGadget wrote:\n>> From: Jeff Hostetler <jeffhost@microsoft.com>\n>>\n>> * GIT_TR2_EVENT is a new structured format. It writes event data as a\n>>   series of JSON records.\n> \n> Please document these new environment variables in\n> 'Documentation/git.txt', where the other environment variables,\n> including GIT_TRACE_*, are already documented.\n\nThanks for the report!\n \n> While doing so, please note that the description about the possible\n> values of these variables and of GIT_TRACE above is incomplete,\n> because it's not just \"1\" or an absolute pathname.  Quoting the\n> description of GIT_TRACE:\n> \n>   If this variable is set to \"1\", \"2\" or \"true\" (comparison is case\n>   insensitive), trace messages will be printed to stderr.\n>   \n>   If the variable is set to an integer value greater than 2 and lower\n>   than 10 (strictly) then Git will interpret this value as an open\n>   file descriptor and will try to write the trace messages into this\n>   file descriptor.\n> \n> The way I see it in tr2_dst_get_trace_fd() below, this applies to\n> GIT_TRACE2* as well, and it even offers the possibility to specify a\n> Unix Domain Socket, too.\n\n\nPlease see [1] for a possible direction here.\n\n[1] https://public-inbox.org/git/pull.189.git.gitgitgadget@gmail.com/\n\n\n> And sorry for barging in with a big bucket of paint this late, but,\n> really...  why GIT_TR2 instead of GIT_TRACE2?\n\nI did not do anything in response to this. I'll leave that discussion\nto others.\n\nThanks,\n-Stolee\n"},{"id":"375936","messageId":"1ef6ca23-8046-e0a1-16b8-14f14d4a4680@jeffhostetler.com","threadId":"50293","inReplyTo":"20190510133738.4511-1-avarab@gmail.com","subject":"Re: [PATCH] trace2: fix up a missing \"leave\" entry point","fromName":"Jeff Hostetler","fromEmail":"git@jeffhostetler.com","sentAt":"2019-05-20T16:50:38Z","receivedAt":"2019-05-20T16:50:41Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"\n\nOn 5/10/2019 9:37 AM, Ævar Arnfjörð Bjarmason wrote:\n> Fix a trivial bug that's been here since the shared/do_write_index\n> tracing was added in 42fee7a388 (\"trace2:data: add trace2\n> instrumentation to index read/write\", 2019-02-22). We should have\n> enter/leave points, not two enter/enter points. This resulted in an\n> \"enter\" event without a corresponding \"leave\" event.\n> \n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> ---\n>   read-cache.c | 2 +-\n>   1 file changed, 1 insertion(+), 1 deletion(-)\n> \n> diff --git a/read-cache.c b/read-cache.c\n> index 61b043bac3..4fad4e3f9a 100644\n> --- a/read-cache.c\n> +++ b/read-cache.c\n> @@ -3131,7 +3131,7 @@ static int write_shared_index(struct index_state *istate,\n>   \ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n>   \t\t\t\t   the_repository, \"%s\", (*temp)->filename.buf);\n>   \tret = do_write_index(si->base, *temp, 1);\n> -\ttrace2_region_enter_printf(\"index\", \"shared/do_write_index\",\n> +\ttrace2_region_leave_printf(\"index\", \"shared/do_write_index\",\n>   \t\t\t\t   the_repository, \"%s\", (*temp)->filename.buf);\n>   \n>   \tif (ret)\n> \n\nGood catch!\n\nThanks,\nJeff\n"}]}