Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(247)

Side by Side Diff: tools/win/link_limiter/limiter.cc

Issue 10826067: Implement a tool called link_limiter to impose a global restriction on how many (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Incorporate feedback from thestig Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « tools/win/link_limiter/build_link_limiter.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <stdio.h>
6 #include <stdlib.h>
7
8 #define NOMINMAX
9 #include <windows.h>
10
11 #include <algorithm>
12 #include <iterator>
13 #include <sstream>
14 #include <string>
15 #include <vector>
16
17 typedef std::basic_string<TCHAR> tstring;
18
19 namespace {
20 const bool g_is_debug = (_wgetenv(L"LIMITER_DEBUG") != NULL);
21 const int g_wait_time_in_ms = 30 * 1000; // 30 seconds
22 }
23
24 // Don't use stderr for errors because VS has large buffers on them, leading
25 // to confusing error output.
26 static void Error(const wchar_t* msg, ...) {
27 tstring new_msg = tstring(L"limiter fatal error: ") + msg + L"\n";
28 va_list args;
29 va_start(args, msg);
30 vwprintf(new_msg.c_str(), args);
31 va_end(args);
32 }
33
34 static void Warn(const wchar_t* msg, ...) {
35 if (!g_is_debug)
36 return;
37 tstring new_msg = tstring(L"limiter warning: ") + msg + L"\n";
38 va_list args;
39 va_start(args, msg);
40 vwprintf(new_msg.c_str(), args);
41 va_end(args);
42 }
43
44 static tstring ErrorMessageToString(DWORD err) {
45 TCHAR* msg_buf = NULL;
46 DWORD rc = FormatMessage(
47 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
48 NULL,
49 err,
50 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
51 reinterpret_cast<LPTSTR>(&msg_buf),
52 0,
53 NULL);
54 if (!rc)
55 return L"unknown error";
56 tstring ret(msg_buf);
57 LocalFree(msg_buf);
58 return ret;
59 }
60
61 static DWORD RunExe(const tstring& exe_name) {
62 STARTUPINFO startup_info = { sizeof(STARTUPINFO) };
63 PROCESS_INFORMATION process_info;
64 DWORD exit_code;
65
66 GetStartupInfo(&startup_info);
67 tstring cmdline = tstring(GetCommandLine());
68
69 size_t first_space = cmdline.find(' ');
70 if (first_space == -1) {
71 // I'm not sure why this would ever happen, but just in case...
72 cmdline = exe_name;
73 } else {
74 cmdline = exe_name + cmdline.substr(first_space);
75 }
76
77 if (!CreateProcess(NULL,
78 const_cast<TCHAR*>(cmdline.c_str()),
79 NULL,
80 NULL,
81 TRUE,
82 0,
83 NULL,
84 NULL,
85 &startup_info, &process_info)) {
86 Error(L"Error in CreateProcess[%s]: %s",
87 cmdline.c_str(), ErrorMessageToString(GetLastError()).c_str());
88 return MAXDWORD;
89 }
90 CloseHandle(process_info.hThread);
91 WaitForSingleObject(process_info.hProcess, INFINITE);
92 GetExitCodeProcess(process_info.hProcess, &exit_code);
93 CloseHandle(process_info.hProcess);
94 return exit_code;
95 }
96
97 // Returns 0 if there was an error
98 static int CpuConcurrencyMetric(const tstring& envvar_name) {
99 int max_concurrent = 0;
100 std::vector<char> buffer(1);
101 BOOL ok = false;
102 DWORD last_error = 0;
103 do {
104 DWORD bufsize = buffer.size();
105 ok = GetLogicalProcessorInformation(
106 reinterpret_cast<PSYSTEM_LOGICAL_PROCESSOR_INFORMATION>(&buffer[0]),
Lei Zhang 2012/08/03 05:03:23 4 spaces here.
107 &bufsize);
108 last_error = GetLastError();
109 if (!ok && last_error == ERROR_INSUFFICIENT_BUFFER &&
110 bufsize > buffer.size()) {
111 buffer.resize(bufsize);
112 }
113 } while (!ok && last_error == ERROR_INSUFFICIENT_BUFFER);
114
115 if (!ok) {
116 Warn(L"Error while getting number of cores. Try setting the "
117 L" environment variable '%s' to (num_cores-1): %s",
Lei Zhang 2012/08/03 05:03:23 foo - bar instead of foo-bar here and below.
118 envvar_name.c_str(), ErrorMessageToString(last_error).c_str());
119 return 0;
120 }
121
122 PSYSTEM_LOGICAL_PROCESSOR_INFORMATION pproc_info =
123 reinterpret_cast<PSYSTEM_LOGICAL_PROCESSOR_INFORMATION>(&buffer[0]);
124 int num_entries = buffer.size() /
125 sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION);
126
127 for (int i = 0; i < num_entries; ++i) {
128 SYSTEM_LOGICAL_PROCESSOR_INFORMATION& info = pproc_info[i];
129 if (info.Relationship == RelationProcessorCore) {
130 ++max_concurrent;
131 }
132 }
133
134 // Leave one core for other tasks
135 return max_concurrent-1;
136 }
137
138 // TODO(defaults): Create a better heuristic than # of CPUs. It seems likely
139 // that the right value will, in fact, be based on the memory capacity of the
140 // machine, not on the number of CPUs.
141 enum ConcurrencyMetricEnum {
142 CONCURRENCY_METRIC_ONE,
143 CONCURRENCY_METRIC_CPU,
144 CONCURRENCY_METRIC_DEFAULT = CONCURRENCY_METRIC_CPU
145 };
146
147 static int GetMaxConcurrency(const tstring& base_pipename,
148 ConcurrencyMetricEnum metric) {
149 static int max_concurrent = -1;
150
151 if (max_concurrent == -1) {
152 tstring envvar_name = base_pipename + L"_MAXCONCURRENCY";
153
154 const LPTSTR max_concurrent_str = _wgetenv(envvar_name.c_str());
155 max_concurrent = _wtoi(max_concurrent_str ? max_concurrent_str : L"0");
Lei Zhang 2012/08/03 05:03:23 = max_concurrent_str ? _wtoi(max_concurrent_str) :
iannucci 2012/08/03 05:50:34 Not sure about this one. I was attempting to preve
Lei Zhang 2012/08/03 06:09:54 Yep, I know, but my suggestion also avoids calling
156
157 if (max_concurrent == 0) {
158 switch (metric) {
159 case CONCURRENCY_METRIC_CPU:
160 max_concurrent = CpuConcurrencyMetric(envvar_name);
161 if (max_concurrent)
162 break;
163 // else fall through
164 case CONCURRENCY_METRIC_ONE:
165 max_concurrent = 1;
166 break;
167 }
168 }
169
170 max_concurrent = std::min(std::max(max_concurrent, 1),
171 PIPE_UNLIMITED_INSTANCES);
172 }
173
174 return max_concurrent;
175 }
176
177 static HANDLE WaitForPipe(const tstring& pipename,
178 HANDLE event,
179 int max_concurrency) {
180 // We're using a named pipe instead of a semaphore so the Kernel can clean up
181 // after us if we crash while holding onto the pipe (A real semaphore will
182 // not release on process termination).
183 HANDLE pipe = INVALID_HANDLE_VALUE;
184 for (;;) {
185 pipe = CreateNamedPipe(
186 pipename.c_str(),
Lei Zhang 2012/08/03 05:03:23 4 spaces
187 PIPE_ACCESS_DUPLEX,
188 PIPE_TYPE_BYTE,
189 max_concurrency,
190 1, // nOutBufferSize
191 1, // nInBufferSize
192 0, // nDefaultTimeOut
193 NULL); // Default security attributes (noinherit)
194 if (pipe != INVALID_HANDLE_VALUE)
195 break;
196
197 DWORD error = GetLastError();
198 if (error == ERROR_PIPE_BUSY) {
199 if (event) {
200 WaitForSingleObject(event, g_wait_time_in_ms);
201 } else {
202 Sleep(g_wait_time_in_ms);
203 }
204 } else {
205 Warn(L"Got error %d while waiting for pipe: %s", error,
206 ErrorMessageToString(error).c_str());
207 return INVALID_HANDLE_VALUE;
208 }
209 }
210
211 return pipe;
212 }
213
214 static int WaitAndRun(const tstring& shimmed_exe,
215 const tstring& base_pipename) {
216 ULONGLONG start_time = 0, end_time = 0;
217 tstring pipename = L"\\\\.\\pipe\\" + base_pipename;
218 tstring event_name = L"Local\\EVENT_" + base_pipename;
219
220 // This event lets us do better than strict polling, but we don't rely on it
221 // (in case a process crashes before signalling the event).
222 HANDLE event = CreateEvent(
223 NULL, // Default security attributes
Lei Zhang 2012/08/03 05:03:23 put this on the previous line or 4 space indent.
224 FALSE, // Manual reset
225 FALSE, // Initial state
226 event_name.c_str());
227
228 if (g_is_debug)
229 start_time = GetTickCount64();
230
231 HANDLE pipe =
232 WaitForPipe(pipename, event,
233 GetMaxConcurrency(base_pipename, CONCURRENCY_METRIC_DEFAULT));
234
235 if (g_is_debug) {
236 end_time = GetTickCount64();
237 wprintf(L" took %.2fs to acquire semaphore.\n",
238 (end_time - start_time) / 1000.0);
Lei Zhang 2012/08/03 05:03:23 indent
239 }
240
241 DWORD ret = RunExe(shimmed_exe);
242
243 if (pipe != INVALID_HANDLE_VALUE)
244 CloseHandle(pipe);
245 if (event != NULL)
246 SetEvent(event);
247
248 return ret;
249 }
250
251 void Usage(const tstring& msg) {
252 tstring usage(msg);
253 usage += L"\n"
254 L"Usage: SHIMED_NAME__SEMAPHORE_NAME\n"
255 L"\n"
256 L" SHIMMED_NAME - ex. 'link.exe' or 'lib.exe'\n"
257 L" - can be exe, bat, or com\n"
258 L" - must exist in PATH\n"
259 L"\n"
260 L" SEMAPHORE_NAME - ex. 'SOME_NAME' or 'GROOVY_SEMAPHORE'\n"
261 L"\n"
262 L" Example:\n"
263 L" link.exe__LINK_LIMITER.exe\n"
264 L" lib.exe__LINK_LIMITER.exe\n"
265 L" * Both will limit on the same semaphore\n"
266 L"\n"
267 L" link.exe__LINK_LIMITER.exe\n"
268 L" lib.exe__LIB_LIMITER.exe\n"
269 L" * Both will limit on independent semaphores\n"
270 L"\n"
271 L" This program is meant to be run after renaming it into the\n"
272 L" above format. Once you have done so, executing it will block\n"
273 L" on the availability of the semaphore SEMAPHORE_NAME. Once\n"
274 L" the semaphore is obtained, it will execute SHIMMED_NAME, \n"
275 L" passing through all arguments as-is.\n"
276 L"\n"
277 L" The maximum concurrency can be manually set by setting the\n"
278 L" environment variable <SEMAPHORE_NAME>_MAXCONCURRENCY to an\n"
279 L" integer value (1, 254).\n"
280 L" * This value must be set the same for ALL invocations.\n"
281 L" * If the value is not set, it defaults to (num_cores-1).\n"
282 L"\n"
283 L" The semaphore is automatically released when the program\n"
284 L" completes normally, OR if the program crashes (or even if\n"
285 L" limiter itself crashes).\n";
286 Error(usage.c_str());
287 exit(-1);
288 }
289
290 // Input command line is assumed to be of the form:
291 //
292 // thing.exe__PIPE_NAME.exe ...
293 //
294 // Specifically, wait for a semaphore (whose concurrency is specified by
295 // LIMITER_MAXCONCURRENT), and then pass through everything once we have
296 // acquired the semaphore.
297 //
298 // argv[0] is parsed for:
299 // * exe_to_shim_including_extension.exe
300 // * This could also be a bat or com. Anything that CreateProcess will
301 // accept.
302 // * "__"
303 // * We search for this separator from the end of argv[0], so the exe name
304 // could contain a double underscore if necessary.
305 // * PIPE_NAME
306 // * Can only contain single underscores, not a double underscore.
307 // * i.e. HELLO_WORLD_PIPE will work, but HELLO__WORLD_PIPE will not.
308 // * This would allow the shimmed exe to contain arbitrary numbers of
309 // underscores. We control the pipe name, but not necessarily the thing
310 // we're shimming.
311 //
312 int wmain(int, wchar_t** argv) {
313 tstring shimmed_plus_pipename = argv[0];
314 size_t last_slash = shimmed_plus_pipename.find_last_of(L"/\\");
315 if (last_slash != tstring::npos) {
316 shimmed_plus_pipename = shimmed_plus_pipename.substr(last_slash + 1);
317 }
318
319 size_t separator = shimmed_plus_pipename.rfind(L"__");
320 if (separator == tstring::npos) {
321 Usage(L"Cannot parse argv[0]. No '__' found. "
322 L"Should be like '[...(\\|/)]link.exe__PIPE_NAME.exe'");
323 }
324 tstring shimmed_exe = shimmed_plus_pipename.substr(0, separator);
325 tstring base_pipename = shimmed_plus_pipename.substr(separator + 2);
326
327 size_t dot = base_pipename.find(L'.');
328 if (dot == tstring::npos) {
329 Usage(L"Expected an executable extension in argv[0]. No '.' found.");
330 }
331 base_pipename = base_pipename.substr(0, dot);
332
333 return WaitAndRun(shimmed_exe, base_pipename);
334 }
335
OLDNEW
« no previous file with comments | « tools/win/link_limiter/build_link_limiter.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698