trace2: add trace2_child_ready() to report on background children

Create "child_ready" event to capture the state of a child process
created in the background.

When a child command is started a "child_start" event is generated in
the Trace2 log.  For normal synchronous children, a "child_exit" event
is later generated when the child exits or is terminated.  The two events
include information, such as the "child_id" and "pid", to allow post
analysis to match-up the command line and exit status.

When a child is started in the background (and may outlive the parent
process), it is not possible for the parent to emit a "child_exit"
event.  Create a new "child_ready" event to indicate whether the
child was successfully started.  Also include the "child_id" and "pid"
to allow similar post processing.

This will be used in a later commit with the new "start_bg_command()".

Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Jeff Hostetler 2021-09-20 15:36:12 +00:00 committed by Junio C Hamano
parent 4c719308ce
commit 64bc75244b
7 changed files with 152 additions and 0 deletions

View File

@ -613,6 +613,46 @@ stopping after the waitpid() and includes OS process creation overhead).
So this time will be slightly larger than the atexit time reported by
the child process itself.
`"child_ready"`::
This event is generated after the current process has started
a background process and released all handles to it.
+
------------
{
"event":"child_ready",
...
"child_id":2,
"pid":14708, # child PID
"ready":"ready", # child ready state
"t_rel":0.110605 # observed run-time of child process
}
------------
+
Note that the session-id of the child process is not available to
the current/spawning process, so the child's PID is reported here as
a hint for post-processing. (But it is only a hint because the child
process may be a shell script which doesn't have a session-id.)
+
This event is generated after the child is started in the background
and given a little time to boot up and start working. If the child
startups normally and while the parent is still waiting, the "ready"
field will have the value "ready".
If the child is too slow to start and the parent times out, the field
will have the value "timeout".
If the child starts but the parent is unable to probe it, the field
will have the value "error".
+
After the parent process emits this event, it will release all of its
handles to the child process and treat the child as a background
daemon. So even if the child does eventually finish booting up,
the parent will not emit an updated event.
+
Note that the `t_rel` field contains the observed run time in seconds
when the parent released the child process into the background.
The child is assumed to be a long-running daemon process and may
outlive the parent process. So the parent's child event times should
not be compared to the child's atexit times.
`"exec"`::
This event is generated before git attempts to `exec()`
another command rather than starting a child process.

View File

@ -394,6 +394,37 @@ void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd,
us_elapsed_child);
}
void trace2_child_ready_fl(const char *file, int line,
struct child_process *cmd,
const char *ready)
{
struct tr2_tgt *tgt_j;
int j;
uint64_t us_now;
uint64_t us_elapsed_absolute;
uint64_t us_elapsed_child;
if (!trace2_enabled)
return;
us_now = getnanotime() / 1000;
us_elapsed_absolute = tr2tls_absolute_elapsed(us_now);
if (cmd->trace2_child_us_start)
us_elapsed_child = us_now - cmd->trace2_child_us_start;
else
us_elapsed_child = 0;
for_each_wanted_builtin (j, tgt_j)
if (tgt_j->pfn_child_ready_fl)
tgt_j->pfn_child_ready_fl(file, line,
us_elapsed_absolute,
cmd->trace2_child_id,
cmd->pid,
ready,
us_elapsed_child);
}
int trace2_exec_fl(const char *file, int line, const char *exe,
const char **argv)
{

View File

@ -253,6 +253,31 @@ void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd,
#define trace2_child_exit(cmd, code) \
trace2_child_exit_fl(__FILE__, __LINE__, (cmd), (code))
/**
* Emits a "child_ready" message containing the "child-id" and a flag
* indicating whether the child was considered "ready" when we
* released it.
*
* This function should be called after starting a daemon process in
* the background (and after giving it sufficient time to boot
* up) to indicate that we no longer control or own it.
*
* The "ready" argument should contain one of { "ready", "timeout",
* "error" } to indicate the state of the running daemon when we
* released it.
*
* If the daemon process fails to start or it exits or is terminated
* while we are still waiting for it, the caller should emit a
* regular "child_exit" to report the normal process exit information.
*
*/
void trace2_child_ready_fl(const char *file, int line,
struct child_process *cmd,
const char *ready);
#define trace2_child_ready(cmd, ready) \
trace2_child_ready_fl(__FILE__, __LINE__, (cmd), (ready))
/**
* Emit an 'exec' event prior to calling one of exec(), execv(),
* execvp(), and etc. On Unix-derived systems, this will be the

View File

@ -45,6 +45,10 @@ typedef void(tr2_tgt_evt_child_exit_fl_t)(const char *file, int line,
uint64_t us_elapsed_absolute, int cid,
int pid, int code,
uint64_t us_elapsed_child);
typedef void(tr2_tgt_evt_child_ready_fl_t)(const char *file, int line,
uint64_t us_elapsed_absolute,
int cid, int pid, const char *ready,
uint64_t us_elapsed_child);
typedef void(tr2_tgt_evt_thread_start_fl_t)(const char *file, int line,
uint64_t us_elapsed_absolute);
@ -116,6 +120,7 @@ struct tr2_tgt {
tr2_tgt_evt_alias_fl_t *pfn_alias_fl;
tr2_tgt_evt_child_start_fl_t *pfn_child_start_fl;
tr2_tgt_evt_child_exit_fl_t *pfn_child_exit_fl;
tr2_tgt_evt_child_ready_fl_t *pfn_child_ready_fl;
tr2_tgt_evt_thread_start_fl_t *pfn_thread_start_fl;
tr2_tgt_evt_thread_exit_fl_t *pfn_thread_exit_fl;
tr2_tgt_evt_exec_fl_t *pfn_exec_fl;

View File

@ -383,6 +383,27 @@ static void fn_child_exit_fl(const char *file, int line,
jw_release(&jw);
}
static void fn_child_ready_fl(const char *file, int line,
uint64_t us_elapsed_absolute, int cid, int pid,
const char *ready, uint64_t us_elapsed_child)
{
const char *event_name = "child_ready";
struct json_writer jw = JSON_WRITER_INIT;
double t_rel = (double)us_elapsed_child / 1000000.0;
jw_object_begin(&jw, 0);
event_fmt_prepare(event_name, file, line, NULL, &jw);
jw_object_intmax(&jw, "child_id", cid);
jw_object_intmax(&jw, "pid", pid);
jw_object_string(&jw, "ready", ready);
jw_object_double(&jw, "t_rel", 6, t_rel);
jw_end(&jw);
tr2_dst_write_line(&tr2dst_event, &jw.json);
jw_release(&jw);
}
static void fn_thread_start_fl(const char *file, int line,
uint64_t us_elapsed_absolute)
{
@ -610,6 +631,7 @@ struct tr2_tgt tr2_tgt_event = {
fn_alias_fl,
fn_child_start_fl,
fn_child_exit_fl,
fn_child_ready_fl,
fn_thread_start_fl,
fn_thread_exit_fl,
fn_exec_fl,

View File

@ -251,6 +251,19 @@ static void fn_child_exit_fl(const char *file, int line,
strbuf_release(&buf_payload);
}
static void fn_child_ready_fl(const char *file, int line,
uint64_t us_elapsed_absolute, int cid, int pid,
const char *ready, uint64_t us_elapsed_child)
{
struct strbuf buf_payload = STRBUF_INIT;
double elapsed = (double)us_elapsed_child / 1000000.0;
strbuf_addf(&buf_payload, "child_ready[%d] pid:%d ready:%s elapsed:%.6f",
cid, pid, ready, elapsed);
normal_io_write_fl(file, line, &buf_payload);
strbuf_release(&buf_payload);
}
static void fn_exec_fl(const char *file, int line, uint64_t us_elapsed_absolute,
int exec_id, const char *exe, const char **argv)
{
@ -330,6 +343,7 @@ struct tr2_tgt tr2_tgt_normal = {
fn_alias_fl,
fn_child_start_fl,
fn_child_exit_fl,
fn_child_ready_fl,
NULL, /* thread_start */
NULL, /* thread_exit */
fn_exec_fl,

View File

@ -360,6 +360,20 @@ static void fn_child_exit_fl(const char *file, int line,
strbuf_release(&buf_payload);
}
static void fn_child_ready_fl(const char *file, int line,
uint64_t us_elapsed_absolute, int cid, int pid,
const char *ready, uint64_t us_elapsed_child)
{
const char *event_name = "child_ready";
struct strbuf buf_payload = STRBUF_INIT;
strbuf_addf(&buf_payload, "[ch%d] pid:%d ready:%s", cid, pid, ready);
perf_io_write_fl(file, line, event_name, NULL, &us_elapsed_absolute,
&us_elapsed_child, NULL, &buf_payload);
strbuf_release(&buf_payload);
}
static void fn_thread_start_fl(const char *file, int line,
uint64_t us_elapsed_absolute)
{
@ -553,6 +567,7 @@ struct tr2_tgt tr2_tgt_perf = {
fn_alias_fl,
fn_child_start_fl,
fn_child_exit_fl,
fn_child_ready_fl,
fn_thread_start_fl,
fn_thread_exit_fl,
fn_exec_fl,