Skip to content

Commit edff92c

Browse files
jeffhostetlergitster
authored andcommitted
structured-logging: add detail-events for child processes
Teach git to emit "detail" events with category "child" before a child process is started and after it finishes. These events can be used to infer time spent by git waiting for child processes to complete. These events are controlled by the slog.detail config setting. Set to true or add the token "child" to it. Signed-off-by: Jeff Hostetler <[email protected]>
1 parent ef0e808 commit edff92c

File tree

4 files changed

+181
-3
lines changed

4 files changed

+181
-3
lines changed

run-command.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,8 @@ int start_command(struct child_process *cmd)
710710

711711
fflush(NULL);
712712

713+
cmd->slog_child_id = slog_child_starting(cmd);
714+
713715
#ifndef GIT_WINDOWS_NATIVE
714716
{
715717
int notify_pipe[2];
@@ -923,6 +925,9 @@ int start_command(struct child_process *cmd)
923925
close_pair(fderr);
924926
else if (cmd->err)
925927
close(cmd->err);
928+
929+
slog_child_ended(cmd->slog_child_id, cmd->pid, failed_errno);
930+
926931
child_process_clear(cmd);
927932
errno = failed_errno;
928933
return -1;
@@ -949,13 +954,20 @@ int start_command(struct child_process *cmd)
949954
int finish_command(struct child_process *cmd)
950955
{
951956
int ret = wait_or_whine(cmd->pid, cmd->argv[0], 0);
957+
958+
slog_child_ended(cmd->slog_child_id, cmd->pid, ret);
959+
952960
child_process_clear(cmd);
953961
return ret;
954962
}
955963

956964
int finish_command_in_signal(struct child_process *cmd)
957965
{
958-
return wait_or_whine(cmd->pid, cmd->argv[0], 1);
966+
int ret = wait_or_whine(cmd->pid, cmd->argv[0], 1);
967+
968+
slog_child_ended(cmd->slog_child_id, cmd->pid, ret);
969+
970+
return ret;
959971
}
960972

961973

run-command.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ struct child_process {
1212
struct argv_array args;
1313
struct argv_array env_array;
1414
pid_t pid;
15+
int slog_child_id;
1516
/*
1617
* Using .in, .out, .err:
1718
* - Specify 0 for no redirections (child inherits stdin, stdout,

structured-logging.c

Lines changed: 152 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "json-writer.h"
55
#include "sigchain.h"
66
#include "argv-array.h"
7+
#include "run-command.h"
78

89
#if !defined(STRUCTURED_LOGGING)
910
/*
@@ -48,6 +49,23 @@ struct aux_data_array {
4849
static struct aux_data_array my__aux_data;
4950
static void format_and_free_aux_data(struct json_writer *jw);
5051

52+
struct child_data {
53+
uint64_t start_ns;
54+
uint64_t end_ns;
55+
struct json_writer jw_argv;
56+
unsigned int is_running:1;
57+
unsigned int is_git_cmd:1;
58+
unsigned int use_shell:1;
59+
};
60+
61+
struct child_data_array {
62+
struct child_data **array;
63+
size_t nr, alloc;
64+
};
65+
66+
static struct child_data_array my__child_data;
67+
static void free_children(void);
68+
5169
static uint64_t my__start_time;
5270
static uint64_t my__exit_time;
5371
static int my__is_config_loaded;
@@ -283,10 +301,11 @@ static void emit_exit_event(void)
283301
}
284302

285303
static void emit_detail_event(const char *category, const char *label,
304+
uint64_t clock_ns,
286305
const struct json_writer *data)
287306
{
288307
struct json_writer jw = JSON_WRITER_INIT;
289-
uint64_t clock_us = getnanotime() / 1000;
308+
uint64_t clock_us = clock_ns / 1000;
290309

291310
/* build "detail" event */
292311
jw_object_begin(&jw, my__is_pretty);
@@ -435,6 +454,7 @@ static void do_final_steps(int in_signal)
435454
jw_release(&my__errors);
436455
strbuf_release(&my__session_id);
437456
free_timers();
457+
free_children();
438458
}
439459

440460
static void slog_atexit(void)
@@ -580,7 +600,7 @@ void slog_emit_detail_event(const char *category, const char *label,
580600
BUG("unterminated slog.detail data: '%s' '%s' '%s'",
581601
category, label, data->json.buf);
582602

583-
emit_detail_event(category, label, data);
603+
emit_detail_event(category, label, getnanotime(), data);
584604
}
585605

586606
int slog_start_timer(const char *category, const char *name)
@@ -815,4 +835,134 @@ static void format_and_free_aux_data(struct json_writer *jw)
815835
my__aux_data.alloc = 0;
816836
}
817837

838+
static struct child_data *alloc_child_data(const struct child_process *cmd)
839+
{
840+
struct child_data *cd = xcalloc(1, sizeof(struct child_data));
841+
842+
cd->start_ns = getnanotime();
843+
cd->is_running = 1;
844+
cd->is_git_cmd = cmd->git_cmd;
845+
cd->use_shell = cmd->use_shell;
846+
847+
jw_init(&cd->jw_argv);
848+
849+
jw_array_begin(&cd->jw_argv, my__is_pretty);
850+
{
851+
jw_array_argv(&cd->jw_argv, cmd->argv);
852+
}
853+
jw_end(&cd->jw_argv);
854+
855+
return cd;
856+
}
857+
858+
static int insert_child_data(struct child_data *cd)
859+
{
860+
int child_id = my__child_data.nr;
861+
862+
ALLOC_GROW(my__child_data.array, my__child_data.nr + 1,
863+
my__child_data.alloc);
864+
my__child_data.array[my__child_data.nr++] = cd;
865+
866+
return child_id;
867+
}
868+
869+
int slog_child_starting(const struct child_process *cmd)
870+
{
871+
struct child_data *cd;
872+
int child_id;
873+
874+
if (!slog_is_enabled())
875+
return SLOG_UNDEFINED_CHILD_ID;
876+
877+
/*
878+
* If we have not yet written a cmd_start event (and even if
879+
* we do not emit this child_start event), force the cmd_start
880+
* event now so that it appears in the log before any events
881+
* that the child process itself emits.
882+
*/
883+
if (!my__wrote_start_event)
884+
emit_start_event();
885+
886+
cd = alloc_child_data(cmd);
887+
child_id = insert_child_data(cd);
888+
889+
/* build data portion for a "detail" event */
890+
if (slog_want_detail_event("child")) {
891+
struct json_writer jw_data = JSON_WRITER_INIT;
892+
893+
jw_object_begin(&jw_data, my__is_pretty);
894+
{
895+
jw_object_intmax(&jw_data, "child_id", child_id);
896+
jw_object_bool(&jw_data, "git_cmd", cd->is_git_cmd);
897+
jw_object_bool(&jw_data, "use_shell", cd->use_shell);
898+
jw_object_sub_jw(&jw_data, "child_argv", &cd->jw_argv);
899+
}
900+
jw_end(&jw_data);
901+
902+
emit_detail_event("child", "child_starting", cd->start_ns,
903+
&jw_data);
904+
jw_release(&jw_data);
905+
}
906+
907+
return child_id;
908+
}
909+
910+
void slog_child_ended(int child_id, int child_pid, int child_exit_code)
911+
{
912+
struct child_data *cd;
913+
914+
if (!slog_is_enabled())
915+
return;
916+
if (child_id == SLOG_UNDEFINED_CHILD_ID)
917+
return;
918+
if (child_id >= my__child_data.nr || child_id < 0)
919+
BUG("Invalid slog.child id '%d'", child_id);
920+
921+
cd = my__child_data.array[child_id];
922+
if (!cd->is_running)
923+
BUG("slog.child '%d' already stopped", child_id);
924+
925+
cd->end_ns = getnanotime();
926+
cd->is_running = 0;
927+
928+
/* build data portion for a "detail" event */
929+
if (slog_want_detail_event("child")) {
930+
struct json_writer jw_data = JSON_WRITER_INIT;
931+
932+
jw_object_begin(&jw_data, my__is_pretty);
933+
{
934+
jw_object_intmax(&jw_data, "child_id", child_id);
935+
jw_object_bool(&jw_data, "git_cmd", cd->is_git_cmd);
936+
jw_object_bool(&jw_data, "use_shell", cd->use_shell);
937+
jw_object_sub_jw(&jw_data, "child_argv", &cd->jw_argv);
938+
939+
jw_object_intmax(&jw_data, "child_pid", child_pid);
940+
jw_object_intmax(&jw_data, "child_exit_code",
941+
child_exit_code);
942+
jw_object_intmax(&jw_data, "child_elapsed_us",
943+
(cd->end_ns - cd->start_ns) / 1000);
944+
}
945+
jw_end(&jw_data);
946+
947+
emit_detail_event("child", "child_ended", cd->end_ns, &jw_data);
948+
jw_release(&jw_data);
949+
}
950+
}
951+
952+
static void free_children(void)
953+
{
954+
int k;
955+
956+
for (k = 0; k < my__child_data.nr; k++) {
957+
struct child_data *cd = my__child_data.array[k];
958+
959+
jw_release(&cd->jw_argv);
960+
free(cd);
961+
}
962+
963+
FREE_AND_NULL(my__child_data.array);
964+
my__child_data.nr = 0;
965+
my__child_data.alloc = 0;
966+
}
967+
818968
#endif

structured-logging.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
#define STRUCTURED_LOGGING_H
33

44
struct json_writer;
5+
struct child_process;
56

67
typedef int (*slog_fn_main_t)(int, const char **);
78

89
#define SLOG_UNDEFINED_TIMER_ID (-1)
10+
#define SLOG_UNDEFINED_CHILD_ID (-1)
911

1012
#if !defined(STRUCTURED_LOGGING)
1113
/*
@@ -30,6 +32,8 @@ static inline void slog_stop_timer(int tid) { };
3032
#define slog_aux_intmax(c, k, v) do { } while (0)
3133
#define slog_aux_bool(c, k, v) do { } while (0)
3234
#define slog_aux_jw(c, k, v) do { } while (0)
35+
#define slog_child_starting(cmd) (SLOG_UNDEFINED_CHILD_ID)
36+
#define slog_child_ended(i, p, ec) do { } while (0)
3337

3438
#else
3539

@@ -147,5 +151,16 @@ void slog_aux_bool(const char *category, const char *key, int value);
147151
void slog_aux_jw(const char *category, const char *key,
148152
const struct json_writer *value);
149153

154+
/*
155+
* Emit a detail event of category "child" and label "child_starting"
156+
* or "child_ending" with information about the child process. Note
157+
* that this is in addition to any events that the child process itself
158+
* generates.
159+
*
160+
* Set "slog.detail" to true or contain "child" to get these events.
161+
*/
162+
int slog_child_starting(const struct child_process *cmd);
163+
void slog_child_ended(int child_id, int child_pid, int child_exit_code);
164+
150165
#endif /* STRUCTURED_LOGGING */
151166
#endif /* STRUCTURED_LOGGING_H */

0 commit comments

Comments
 (0)