OLD | NEW |
1 # Copyright 2016 The Chromium Authors. All rights reserved. | 1 # Copyright 2016 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 import multiprocessing | |
6 import os | 5 import os |
7 import re | 6 import re |
8 import resource | |
9 import sys | 7 import sys |
10 import traceback | 8 import traceback |
11 | 9 |
12 import psutil | |
13 | |
14 import common.clovis_paths | 10 import common.clovis_paths |
15 from common.clovis_task import ClovisTask | 11 from common.clovis_task import ClovisTask |
16 from common.loading_trace_database import LoadingTraceDatabase | 12 from common.loading_trace_database import LoadingTraceDatabase |
17 import controller | 13 import controller |
18 from failure_database import FailureDatabase | 14 from failure_database import FailureDatabase |
19 import loading_trace | 15 import loading_trace |
| 16 import multiprocessing_helper |
20 import options | 17 import options |
21 import xvfb_helper | 18 import xvfb_helper |
22 | 19 |
23 | 20 |
24 def LimitMemory(memory_share): | |
25 """Limits the memory available to this process, to avoid OOM issues. | |
26 | |
27 Args: | |
28 memory_share: (float) Share coefficient of the total physical memory that | |
29 the process can use. | |
30 """ | |
31 total_memory = psutil.virtual_memory().total | |
32 memory_limit = memory_share * total_memory | |
33 resource.setrlimit(resource.RLIMIT_AS, (memory_limit, -1L)) | |
34 | |
35 | |
36 def GenerateTrace(url, emulate_device, emulate_network, filename, log_filename): | 21 def GenerateTrace(url, emulate_device, emulate_network, filename, log_filename): |
37 """ Generates a trace. | 22 """ Generates a trace. |
38 | 23 |
39 Args: | 24 Args: |
40 url: URL as a string. | 25 url: URL as a string. |
41 emulate_device: Name of the device to emulate. Empty for no emulation. | 26 emulate_device: Name of the device to emulate. Empty for no emulation. |
42 emulate_network: Type of network emulation. Empty for no emulation. | 27 emulate_network: Type of network emulation. Empty for no emulation. |
43 filename: Name of the file where the trace is saved. | 28 filename: Name of the file where the trace is saved. |
44 log_filename: Name of the file where standard output and errors are | 29 log_filename: Name of the file where standard output and errors are |
45 logged. | 30 logged. |
(...skipping 11 matching lines...) Expand all Loading... |
57 old_stderr = sys.stderr | 42 old_stderr = sys.stderr |
58 | 43 |
59 trace_metadata = { 'succeeded' : False, 'url' : url } | 44 trace_metadata = { 'succeeded' : False, 'url' : url } |
60 trace = None | 45 trace = None |
61 if not url.startswith('http') and not url.startswith('file'): | 46 if not url.startswith('http') and not url.startswith('file'): |
62 url = 'http://' + url | 47 url = 'http://' + url |
63 with open(log_filename, 'w') as sys.stdout: | 48 with open(log_filename, 'w') as sys.stdout: |
64 try: | 49 try: |
65 sys.stderr = sys.stdout | 50 sys.stderr = sys.stdout |
66 | 51 |
| 52 sys.stdout.write('Starting trace generation for: %s.\n' % url) |
| 53 |
67 # Set up the controller. | 54 # Set up the controller. |
68 chrome_ctl = controller.LocalChromeController() | 55 chrome_ctl = controller.LocalChromeController() |
69 chrome_ctl.SetChromeEnvOverride(xvfb_helper.GetChromeEnvironment()) | 56 chrome_ctl.SetChromeEnvOverride(xvfb_helper.GetChromeEnvironment()) |
70 if emulate_device: | 57 if emulate_device: |
71 chrome_ctl.SetDeviceEmulation(emulate_device) | 58 chrome_ctl.SetDeviceEmulation(emulate_device) |
72 if emulate_network: | 59 if emulate_network: |
73 chrome_ctl.SetNetworkEmulation(emulate_network) | 60 chrome_ctl.SetNetworkEmulation(emulate_network) |
74 | 61 |
75 # Record and write the trace. | 62 # Record and write the trace. |
76 with chrome_ctl.Open() as connection: | 63 with chrome_ctl.Open() as connection: |
77 connection.ClearCache() | 64 connection.ClearCache() |
78 trace = loading_trace.LoadingTrace.RecordUrlNavigation( | 65 trace = loading_trace.LoadingTrace.RecordUrlNavigation( |
79 url, connection, chrome_ctl.ChromeMetadata()) | 66 url, connection, chrome_ctl.ChromeMetadata()) |
80 trace_metadata['succeeded'] = True | 67 trace_metadata['succeeded'] = True |
81 trace_metadata.update(trace.ToJsonDict()[trace._METADATA_KEY]) | 68 trace_metadata.update(trace.ToJsonDict()[trace._METADATA_KEY]) |
| 69 sys.stdout.write('Trace generation success.\n') |
82 except controller.ChromeControllerError as e: | 70 except controller.ChromeControllerError as e: |
83 e.Dump(sys.stderr) | 71 e.Dump(sys.stderr) |
84 except Exception as e: | 72 except Exception as e: |
85 sys.stderr.write('Unknown exception:\n' + str(e)) | 73 sys.stderr.write('Unknown exception:\n' + str(e)) |
86 traceback.print_exc(file=sys.stderr) | 74 traceback.print_exc(file=sys.stderr) |
87 | 75 |
88 if trace: | 76 if trace: |
| 77 sys.stdout.write('Dumping trace to file.\n') |
89 trace.ToJsonFile(filename) | 78 trace.ToJsonFile(filename) |
| 79 else: |
| 80 sys.stderr.write('No trace generated.\n') |
| 81 |
| 82 sys.stdout.write('Trace generation finished.\n') |
90 | 83 |
91 sys.stdout = old_stdout | 84 sys.stdout = old_stdout |
92 sys.stderr = old_stderr | 85 sys.stderr = old_stderr |
93 | 86 |
94 return trace_metadata | 87 return trace_metadata |
95 | 88 |
96 | 89 |
97 class TraceTaskHandler(object): | 90 class TraceTaskHandler(object): |
98 """Handles 'trace' tasks.""" | 91 """Handles 'trace' tasks.""" |
99 | 92 |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
159 """ Generates a trace in a separate process by calling GenerateTrace(). | 152 """ Generates a trace in a separate process by calling GenerateTrace(). |
160 | 153 |
161 The generation is done out of process to avoid issues where the system would | 154 The generation is done out of process to avoid issues where the system would |
162 run out of memory when the trace is very large. This ensures that the system | 155 run out of memory when the trace is very large. This ensures that the system |
163 can reclaim all the memory when the trace generation is done. | 156 can reclaim all the memory when the trace generation is done. |
164 | 157 |
165 See the GenerateTrace() documentation for a description of the parameters | 158 See the GenerateTrace() documentation for a description of the parameters |
166 and return values. | 159 and return values. |
167 """ | 160 """ |
168 self._logger.info('Starting external process for trace generation.') | 161 self._logger.info('Starting external process for trace generation.') |
169 failed_metadata = {'succeeded':False, 'url':url} | 162 result = multiprocessing_helper.RunInSeparateProcess( |
170 failed = False | 163 GenerateTrace, |
171 pool = multiprocessing.Pool(1, initializer=LimitMemory, initargs=(0.9,)) | 164 (url, emulate_device, emulate_network, filename, log_filename), |
| 165 self._logger, timeout_seconds=180, memory_share=0.9) |
172 | 166 |
173 apply_result = pool.apply_async( | 167 self._logger.info('Cleaning up Chrome processes.') |
174 GenerateTrace, | 168 controller.LocalChromeController.KillChromeProcesses() |
175 (url, emulate_device, emulate_network, filename, log_filename)) | |
176 pool.close() | |
177 apply_result.wait(timeout=180) | |
178 | 169 |
179 if not apply_result.ready(): | 170 if not result: |
180 self._logger.error('Process timeout for trace generation of URL: ' + url) | |
181 self._failure_database.AddFailure('trace_process_timeout', url) | 171 self._failure_database.AddFailure('trace_process_timeout', url) |
182 # Explicitly kill Chrome now, or pool.terminate() will hang. | 172 return {'succeeded':False, 'url':url} |
183 controller.LocalChromeController.KillChromeProcesses() | 173 return result |
184 pool.terminate() | |
185 failed = True | |
186 elif not apply_result.successful(): | |
187 # Try to reraise the exception that killed the subprocess and add it to | |
188 # the error log. | |
189 try: | |
190 apply_result.get() | |
191 except Exception as e: | |
192 with file(log_filename, 'w+') as error_log: | |
193 error_log.write('Unhandled exception caught by apply_result: {}' | |
194 .format(e)) | |
195 traceback.print_exc(file=error_log) | |
196 else: | |
197 with file(log_filename, 'w+') as error_log: | |
198 error_log.write('No exception found for unsuccessful apply_result') | |
199 self._logger.error('Process failure for trace generation of URL: ' + url) | |
200 self._failure_database.AddFailure('trace_process_error', url) | |
201 failed = True | |
202 | 174 |
203 self._logger.info('Cleaning up external process.') | |
204 pool.join() | |
205 | |
206 if failed: | |
207 return failed_metadata | |
208 return apply_result.get() | |
209 | 175 |
210 def _HandleTraceGenerationResults(self, local_filename, log_filename, | 176 def _HandleTraceGenerationResults(self, local_filename, log_filename, |
211 remote_filename, trace_metadata): | 177 remote_filename, trace_metadata): |
212 """Updates the trace database and the failure database after a trace | 178 """Updates the trace database and the failure database after a trace |
213 generation. Uploads the trace and the log. | 179 generation. Uploads the trace and the log. |
214 Results related to successful traces are uploaded in the 'traces' directory, | 180 Results related to successful traces are uploaded in the 'traces' directory, |
215 and failures are uploaded in the 'failures' directory. | 181 and failures are uploaded in the 'failures' directory. |
216 | 182 |
217 Args: | 183 Args: |
218 local_filename (str): Path to the local file containing the trace. | 184 local_filename (str): Path to the local file containing the trace. |
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
295 trace_metadata = self._GenerateTraceOutOfProcess( | 261 trace_metadata = self._GenerateTraceOutOfProcess( |
296 url, emulate_device, emulate_network, local_filename, log_filename) | 262 url, emulate_device, emulate_network, local_filename, log_filename) |
297 if trace_metadata['succeeded']: | 263 if trace_metadata['succeeded']: |
298 success_happened = True | 264 success_happened = True |
299 remote_filename = os.path.join(local_filename, str(repeat)) | 265 remote_filename = os.path.join(local_filename, str(repeat)) |
300 self._HandleTraceGenerationResults( | 266 self._HandleTraceGenerationResults( |
301 local_filename, log_filename, remote_filename, trace_metadata) | 267 local_filename, log_filename, remote_filename, trace_metadata) |
302 | 268 |
303 if success_happened: | 269 if success_happened: |
304 self._UploadTraceDatabase() | 270 self._UploadTraceDatabase() |
OLD | NEW |