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

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

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