Raw
1 #define USE_THE_REPOSITORY_VARIABLE
2
3 #include "../../git-compat-util.h"
4 #include "../../json-writer.h"
5 #include "../../repository.h"
6 #include "../../strvec.h"
7 #include "../../trace2.h"
8 #include "lazyload.h"
9 #include <psapi.h>
10 #include <tlhelp32.h>
11
12 /*
13 * An arbitrarily chosen value to limit the size of the ancestor
14 * array built in git_processes().
15 */
16 #define NR_PIDS_LIMIT 10
17
18 /*
19 * Find the process data for the given PID in the given snapshot
20 * and update the PROCESSENTRY32 data.
21 */
22 static int find_pid(DWORD pid, HANDLE hSnapshot, PROCESSENTRY32 *pe32)
23 {
24 pe32->dwSize = sizeof(PROCESSENTRY32);
25
26 if (Process32First(hSnapshot, pe32)) {
27 do {
28 if (pe32->th32ProcessID == pid)
29 return 1;
30 } while (Process32Next(hSnapshot, pe32));
31 }
32 return 0;
33 }
34
35 /*
36 * Accumulate array of our parent process names.
37 *
38 * Note: we only report the filename of the process executable; the
39 * only way to get its full pathname is to use OpenProcess()
40 * and GetModuleFileNameEx() or QueryfullProcessImageName()
41 * and that seems rather expensive (on top of the cost of
42 * getting the snapshot).
43 *
44 * Note: we compute the set of parent processes by walking the PPID
45 * link in each visited PROCESSENTRY32 record. This search
46 * stops when an ancestor process is not found in the snapshot
47 * (because it exited before the current or intermediate parent
48 * process exited).
49 *
50 * This search may compute an incorrect result if the PPID link
51 * refers to the PID of an exited parent and that PID has been
52 * recycled and given to a new unrelated process.
53 *
54 * Worse, it is possible for a child or descendant of the
55 * current process to be given the recycled PID and cause a
56 * PPID-cycle. This would cause an infinite loop building our
57 * parent process array.
58 *
59 * Note: for completeness, the "System Idle" process has PID=0 and
60 * PPID=0 and could cause another PPID-cycle. We don't expect
61 * Git to be a descendant of the idle process, but because of
62 * PID recycling, it might be possible to get a PPID link value
63 * of 0. This too would cause an infinite loop.
64 *
65 * Therefore, we keep an array of the visited PPIDs to guard against
66 * cycles.
67 *
68 * We use a fixed-size array rather than ALLOC_GROW to keep things
69 * simple and avoid the alloc/realloc overhead. It is OK if we
70 * truncate the search and return a partial answer.
71 */
72 static void get_processes(struct strvec *names, HANDLE hSnapshot)
73 {
74 PROCESSENTRY32 pe32;
75 DWORD pid;
76 DWORD pid_list[NR_PIDS_LIMIT];
77 int k, nr_pids = 0;
78
79 pid = GetCurrentProcessId();
80 while (find_pid(pid, hSnapshot, &pe32)) {
81 /* Only report parents. Omit self from the output. */
82 if (nr_pids)
83 strvec_push(names, pe32.szExeFile);
84
85 /* Check for cycle in snapshot. (Yes, it happened.) */
86 for (k = 0; k < nr_pids; k++)
87 if (pid == pid_list[k]) {
88 strvec_push(names, "(cycle)");
89 return;
90 }
91
92 if (nr_pids == NR_PIDS_LIMIT) {
93 strvec_push(names, "(truncated)");
94 return;
95 }
96
97 pid_list[nr_pids++] = pid;
98
99 pid = pe32.th32ParentProcessID;
100 }
101 }
102
103 /*
104 * Collect the list of parent process names.
105 */
106 static void get_ancestry(struct strvec *names)
107 {
108 HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
109
110 if (hSnapshot != INVALID_HANDLE_VALUE) {
111 get_processes(names, hSnapshot);
112 CloseHandle(hSnapshot);
113 }
114 }
115
116 /*
117 * Is a debugger attached to the current process?
118 *
119 * This will catch debug runs (where the debugger started the process).
120 * This is the normal case. Since this code is called during our startup,
121 * it will not report instances where a debugger is attached dynamically
122 * to a running git process, but that is relatively rare.
123 */
124 static void get_is_being_debugged(void)
125 {
126 if (IsDebuggerPresent())
127 trace2_data_intmax("process", the_repository,
128 "windows/debugger_present", 1);
129 }
130
131 /*
132 * Emit JSON data with the peak memory usage of the current process.
133 */
134 static void get_peak_memory_info(void)
135 {
136 DECLARE_PROC_ADDR(psapi.dll, BOOL, WINAPI, GetProcessMemoryInfo,
137 HANDLE, PPROCESS_MEMORY_COUNTERS, DWORD);
138
139 if (INIT_PROC_ADDR(GetProcessMemoryInfo)) {
140 PROCESS_MEMORY_COUNTERS pmc;
141
142 if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc,
143 sizeof(pmc))) {
144 struct json_writer jw = JSON_WRITER_INIT;
145
146 jw_object_begin(&jw, 0);
147
148 #define KV(kv) #kv, (intmax_t)pmc.kv
149
150 jw_object_intmax(&jw, KV(PageFaultCount));
151 jw_object_intmax(&jw, KV(PeakWorkingSetSize));
152 jw_object_intmax(&jw, KV(PeakPagefileUsage));
153
154 jw_end(&jw);
155
156 trace2_data_json("process", the_repository,
157 "windows/memory", &jw);
158 jw_release(&jw);
159 }
160 }
161 }
162
163 void trace2_collect_process_info(enum trace2_process_info_reason reason)
164 {
165 struct strvec names = STRVEC_INIT;
166
167 if (!trace2_is_enabled())
168 return;
169
170 switch (reason) {
171 case TRACE2_PROCESS_INFO_STARTUP:
172 get_is_being_debugged();
173 get_ancestry(&names);
174 if (names.nr) {
175 /*
176 Emit the ancestry data as a data_json event to
177 maintain compatibility for consumers of the older
178 "windows/ancestry" event.
179 */
180 struct json_writer jw = JSON_WRITER_INIT;
181 jw_array_begin(&jw, 0);
182 for (size_t i = 0; i < names.nr; i++)
183 jw_array_string(&jw, names.v[i]);
184 jw_end(&jw);
185 trace2_data_json("process", the_repository,
186 "windows/ancestry", &jw);
187 jw_release(&jw);
188
189 /* Emit the ancestry data with the new event. */
190 trace2_cmd_ancestry(names.v);
191 }
192
193 strvec_clear(&names);
194 return;
195
196 case TRACE2_PROCESS_INFO_EXIT:
197 get_peak_memory_info();
198 return;
199
200 default:
201 BUG("trace2_collect_process_info: unknown reason '%d'", reason);
202 }
203 }