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

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