Title: [204659] trunk/Tools
Revision
204659
Author
[email protected]
Date
2016-08-19 15:13:15 -0700 (Fri, 19 Aug 2016)

Log Message

REGRESSION (r204477): Running LayoutTests on ios-simulator became ~15 minutes slower
https://bugs.webkit.org/show_bug.cgi?id=160985

Reviewed by Alexey Proskuryakov.

r204477 removed @memoized on a couple of ios.py functions, causing them to instantiate
a Simulator() on every call, which causes 'xcrun simctl list' to run. The functions
must not be @memoized, because their return value depends on the value of simulator_device_type().

Fix by adding some global state in simulator.py that tracks the created devices
in a worker number -> Device dictionary. Explicitly create devices in _create_simulators(),
and delete them in clean_up_test_run().

Also explicitly called 'xcrun simctl shutdown' to shut down devices, since it seems
that killing the Simulator apps isn't enough.

Simulator tracks the devices in a global dictionary, since state needs to persist
across different instances of IOSSimulatorPort.

Annoyingly, the "Command line:" dumping tried to access a device before we'd done
any setup. Rather than implicitly creating a device here (which the old code did),
override the more clearly named driver_cmd_line_for_logging() in IOSSimulatorPort
and set flag to say that device_id_for_worker_number() doesn't need to return a real
device id.

* Scripts/webkitpy/layout_tests/views/printing.py:
(print_options):
(Printer.print_config):
* Scripts/webkitpy/port/base.py:
(Port.driver_cmd_line_for_logging):
(Port.driver_cmd_line): Deleted.
* Scripts/webkitpy/port/driver.py:
(IOSSimulatorDriver.cmd_line):
* Scripts/webkitpy/port/ios.py:
(IOSSimulatorPort.__init__):
(IOSSimulatorPort.driver_cmd_line_for_logging):
(IOSSimulatorPort._create_simulators):
(IOSSimulatorPort.setup_test_run):
(IOSSimulatorPort.clean_up_test_run):
(IOSSimulatorPort._create_device):
(IOSSimulatorPort):
(IOSSimulatorPort._remove_device):
(IOSSimulatorPort._testing_device):
(IOSSimulatorPort.device_id_for_worker_number):
(IOSSimulatorPort._set_device_class): Deleted.
(IOSSimulatorPort.testing_device): Deleted.
* Scripts/webkitpy/port/port_testcase.py:
(PortTestCase.test_driver_cmd_line):
* Scripts/webkitpy/xcode/simulator.py:
(Device.shutdown):
(Device.delete):
(Device.reset):
(Simulator.create_device):
(Simulator.remove_device):
(Simulator.device_number):
(Simulator.device_state_description):
(Simulator.wait_until_device_is_in_state):

Modified Paths

Diff

Modified: trunk/Tools/ChangeLog (204658 => 204659)


--- trunk/Tools/ChangeLog	2016-08-19 22:13:00 UTC (rev 204658)
+++ trunk/Tools/ChangeLog	2016-08-19 22:13:15 UTC (rev 204659)
@@ -1,3 +1,63 @@
+2016-08-18  Simon Fraser  <[email protected]>
+
+        REGRESSION (r204477): Running LayoutTests on ios-simulator became ~15 minutes slower
+        https://bugs.webkit.org/show_bug.cgi?id=160985
+
+        Reviewed by Alexey Proskuryakov.
+        
+        r204477 removed @memoized on a couple of ios.py functions, causing them to instantiate
+        a Simulator() on every call, which causes 'xcrun simctl list' to run. The functions
+        must not be @memoized, because their return value depends on the value of simulator_device_type().
+        
+        Fix by adding some global state in simulator.py that tracks the created devices 
+        in a worker number -> Device dictionary. Explicitly create devices in _create_simulators(),
+        and delete them in clean_up_test_run().
+        
+        Also explicitly called 'xcrun simctl shutdown' to shut down devices, since it seems
+        that killing the Simulator apps isn't enough.
+        
+        Simulator tracks the devices in a global dictionary, since state needs to persist
+        across different instances of IOSSimulatorPort.
+        
+        Annoyingly, the "Command line:" dumping tried to access a device before we'd done
+        any setup. Rather than implicitly creating a device here (which the old code did),
+        override the more clearly named driver_cmd_line_for_logging() in IOSSimulatorPort
+        and set flag to say that device_id_for_worker_number() doesn't need to return a real
+        device id.
+
+        * Scripts/webkitpy/layout_tests/views/printing.py:
+        (print_options):
+        (Printer.print_config):
+        * Scripts/webkitpy/port/base.py:
+        (Port.driver_cmd_line_for_logging):
+        (Port.driver_cmd_line): Deleted.
+        * Scripts/webkitpy/port/driver.py:
+        (IOSSimulatorDriver.cmd_line):
+        * Scripts/webkitpy/port/ios.py:
+        (IOSSimulatorPort.__init__):
+        (IOSSimulatorPort.driver_cmd_line_for_logging):
+        (IOSSimulatorPort._create_simulators):
+        (IOSSimulatorPort.setup_test_run):
+        (IOSSimulatorPort.clean_up_test_run):
+        (IOSSimulatorPort._create_device):
+        (IOSSimulatorPort):
+        (IOSSimulatorPort._remove_device):
+        (IOSSimulatorPort._testing_device):
+        (IOSSimulatorPort.device_id_for_worker_number):
+        (IOSSimulatorPort._set_device_class): Deleted.
+        (IOSSimulatorPort.testing_device): Deleted.
+        * Scripts/webkitpy/port/port_testcase.py:
+        (PortTestCase.test_driver_cmd_line):
+        * Scripts/webkitpy/xcode/simulator.py:
+        (Device.shutdown):
+        (Device.delete):
+        (Device.reset):
+        (Simulator.create_device):
+        (Simulator.remove_device):
+        (Simulator.device_number):
+        (Simulator.device_state_description):
+        (Simulator.wait_until_device_is_in_state):
+
 2016-08-19  Alexey Proskuryakov  <[email protected]>
 
         Adopt SimServiceContext in LayoutTestRelay

Modified: trunk/Tools/Scripts/webkitpy/layout_tests/views/printing.py (204658 => 204659)


--- trunk/Tools/Scripts/webkitpy/layout_tests/views/printing.py	2016-08-19 22:13:00 UTC (rev 204658)
+++ trunk/Tools/Scripts/webkitpy/layout_tests/views/printing.py	2016-08-19 22:13:15 UTC (rev 204659)
@@ -94,7 +94,7 @@
         self._print_default("Regular timeout: %s, slow test timeout: %s" %
                   (self._options.time_out_ms, self._options.slow_time_out_ms))
 
-        self._print_default('Command line: ' + ' '.join(self._port.driver_cmd_line()))
+        self._print_default('Command line: ' + ' '.join(self._port.driver_cmd_line_for_logging()))
         self._print_default('')
 
     def print_found(self, num_all_test_files, num_to_run, repeat_each, iterations):

Modified: trunk/Tools/Scripts/webkitpy/port/base.py (204658 => 204659)


--- trunk/Tools/Scripts/webkitpy/port/base.py	2016-08-19 22:13:00 UTC (rev 204658)
+++ trunk/Tools/Scripts/webkitpy/port/base.py	2016-08-19 22:13:15 UTC (rev 204659)
@@ -658,7 +658,7 @@
             return test_name + '/'
         return test_name
 
-    def driver_cmd_line(self):
+    def driver_cmd_line_for_logging(self):
         """Prints the DRT command line that will be used."""
         driver = self.create_driver(0)
         return driver.cmd_line(self.get_option('pixel_tests'), [])

Modified: trunk/Tools/Scripts/webkitpy/port/driver.py (204658 => 204659)


--- trunk/Tools/Scripts/webkitpy/port/driver.py	2016-08-19 22:13:00 UTC (rev 204658)
+++ trunk/Tools/Scripts/webkitpy/port/driver.py	2016-08-19 22:13:15 UTC (rev 204659)
@@ -600,7 +600,7 @@
         product_dir = self._port._build_path()
         relay_args = [
             '-developerDir', self._port.developer_dir,
-            '-udid', self._port.testing_device(self._worker_number).udid,
+            '-udid', self._port.device_id_for_worker_number(self._worker_number),
             '-productDir', product_dir,
             '-app', dump_tool,
         ]

Modified: trunk/Tools/Scripts/webkitpy/port/ios.py (204658 => 204659)


--- trunk/Tools/Scripts/webkitpy/port/ios.py	2016-08-19 22:13:00 UTC (rev 204658)
+++ trunk/Tools/Scripts/webkitpy/port/ios.py	2016-08-19 22:13:15 UTC (rev 204659)
@@ -98,6 +98,7 @@
         super(IOSSimulatorPort, self).__init__(host, port_name, **kwargs)
 
         optional_device_class = self.get_option('device_class')
+        self._printing_cmd_line = False
         self._device_class = optional_device_class if optional_device_class else self.DEFAULT_DEVICE_CLASS
         _log.debug('IOSSimulatorPort _device_class is %s', self._device_class)
 
@@ -108,6 +109,13 @@
             return 'WebKitTestRunnerApp.app'
         return 'DumpRenderTree.app'
 
+    def driver_cmd_line_for_logging(self):
+        # Avoid spinning up devices just for logging the commandline.
+        self._printing_cmd_line = True
+        result = super(IOSSimulatorPort, self).driver_cmd_line_for_logging()
+        self._printing_cmd_line = False
+        return result
+
     @property
     @memoized
     def simulator_runtime(self):
@@ -224,7 +232,6 @@
         return list(reversed([self._filesystem.join(self._webkit_baseline_path(p), 'TestExpectations') for p in self.baseline_search_path()]))
 
     def _set_device_class(self, device_class):
-        # Ideally we'd ensure that no simulators are running when this is called.
         self._device_class = device_class if device_class else self.DEFAULT_DEVICE_CLASS
 
     def _create_simulators(self):
@@ -236,9 +243,13 @@
         self._createSimulatorApps()
 
         for i in xrange(self.child_processes()):
-            Simulator.wait_until_device_is_in_state(self.testing_device(i).udid, Simulator.DeviceState.SHUTDOWN)
-            Simulator.reset_device(self.testing_device(i).udid)
+            self._create_device(i)
 
+        for i in xrange(self.child_processes()):
+            device_udid = self._testing_device(i).udid
+            Simulator.wait_until_device_is_in_state(device_udid, Simulator.DeviceState.SHUTDOWN)
+            Simulator.reset_device(device_udid)
+
     def setup_test_run(self, device_class=None):
         mac_os_version = self.host.platform.os_version
 
@@ -250,7 +261,7 @@
         self._create_simulators()
 
         for i in xrange(self.child_processes()):
-            device_udid = self.testing_device(i).udid
+            device_udid = self._testing_device(i).udid
             _log.debug('testing device %s has udid %s', i, device_udid)
 
             # FIXME: <rdar://problem/20916140> Switch to using CoreSimulator.framework for launching and quitting iOS Simulator
@@ -263,10 +274,10 @@
 
         _log.info('Waiting for all iOS Simulators to finish booting.')
         for i in xrange(self.child_processes()):
-            Simulator.wait_until_device_is_booted(self.testing_device(i).udid)
+            Simulator.wait_until_device_is_booted(self._testing_device(i).udid)
 
     def _quit_ios_simulator(self):
-        _log.debug("_quit_ios_simulator")
+        _log.debug("_quit_ios_simulator killing all Simulator processes")
         # FIXME: We should kill only the Simulators we started.
         subprocess.call(["killall", "-9", "-m", "Simulator"])
 
@@ -284,7 +295,9 @@
 
         for i in xrange(self.child_processes()):
             simulator_path = self.get_simulator_path(i)
-            device_udid = self.testing_device(i).udid
+            device_udid = self._testing_device(i).udid
+            self._remove_device(i)
+
             if not os.path.exists(simulator_path):
                 continue
             try:
@@ -301,7 +314,6 @@
                 _log.debug('rmtree %s', saved_state_path)
                 self._filesystem.rmtree(saved_state_path)
 
-                Simulator().delete_device(device_udid)
             except:
                 _log.warning('Unable to remove Simulator' + str(i))
 
@@ -369,13 +381,21 @@
             return stderr, None
         return stderr, crash_log
 
-    def testing_device(self, number):
-        # FIXME: rather than calling lookup_or_create_device every time, we should just store a mapping of
-        # number to device_udid.
-        device_type = self.simulator_device_type()
-        _log.debug(' testing_device %s using device_type %s', number, device_type)
-        return Simulator().lookup_or_create_device(device_type.name + ' WebKit Tester' + str(number), device_type, self.simulator_runtime)
+    def _create_device(self, number):
+        return Simulator.create_device(number, self.simulator_device_type(), self.simulator_runtime)
 
+    def _remove_device(self, number):
+        Simulator.remove_device(number)
+
+    def _testing_device(self, number):
+        return Simulator.device_number(number)
+
+    # This is only exposed so that IOSSimulatorDriver can use it.
+    def device_id_for_worker_number(self, number):
+        if self._printing_cmd_line:
+            return '<dummy id>'
+        return self._testing_device(number).udid
+
     def get_simulator_path(self, suffix=""):
         return os.path.join(self.SIMULATOR_DIRECTORY, "Simulator" + str(suffix) + ".app")
 

Modified: trunk/Tools/Scripts/webkitpy/port/port_testcase.py (204658 => 204659)


--- trunk/Tools/Scripts/webkitpy/port/port_testcase.py	2016-08-19 22:13:00 UTC (rev 204658)
+++ trunk/Tools/Scripts/webkitpy/port/port_testcase.py	2016-08-19 22:13:15 UTC (rev 204659)
@@ -100,11 +100,11 @@
 
     def test_driver_cmd_line(self):
         port = self.make_port()
-        self.assertTrue(len(port.driver_cmd_line()))
+        self.assertTrue(len(port.driver_cmd_line_for_logging()))
 
         options = MockOptions(additional_drt_flag=['--foo=bar', '--foo=baz'])
         port = self.make_port(options=options)
-        cmd_line = port.driver_cmd_line()
+        cmd_line = port.driver_cmd_line_for_logging()
         self.assertTrue('--foo=bar' in cmd_line)
         self.assertTrue('--foo=baz' in cmd_line)
 

Modified: trunk/Tools/Scripts/webkitpy/xcode/simulator.py (204658 => 204659)


--- trunk/Tools/Scripts/webkitpy/xcode/simulator.py	2016-08-19 22:13:00 UTC (rev 204658)
+++ trunk/Tools/Scripts/webkitpy/xcode/simulator.py	2016-08-19 22:13:15 UTC (rev 204659)
@@ -218,6 +218,23 @@
         return Simulator().find_device_by_udid(device_udid)
 
     @classmethod
+    def shutdown(cls, udid):
+        """
+        Shut down the given CoreSimulator device.
+        :param udid: The udid of the device.
+        :type udid: str
+        """
+        device_state = Simulator.device_state(udid)
+        if device_state == Simulator.DeviceState.BOOTING or device_state == Simulator.DeviceState.BOOTED:
+            try:
+                _log.debug('xcrun simctl shutdown %s', udid)
+                subprocess.check_call(['xcrun', 'simctl', 'shutdown', udid])
+            except subprocess.CalledProcessError:
+                raise RuntimeError('"xcrun simctl shutdown" failed')
+
+        Simulator.wait_until_device_is_in_state(udid, Simulator.DeviceState.SHUTDOWN)
+
+    @classmethod
     def delete(cls, udid):
         """
         Delete the given CoreSimulator device.
@@ -224,8 +241,7 @@
         :param udid: The udid of the device.
         :type udid: str
         """
-        _log.debug('deleting device %s', udid)
-        Simulator.wait_until_device_is_in_state(udid, Simulator.DeviceState.SHUTDOWN)
+        Device.shutdown(udid)
         try:
             _log.debug('xcrun simctl delete %s', udid)
             subprocess.check_call(['xcrun', 'simctl', 'delete', udid])
@@ -239,8 +255,7 @@
         :param udid: The udid of the device.
         :type udid: str
         """
-        _log.debug('resetting device %s', udid)
-        Simulator.wait_until_device_is_in_state(udid, Simulator.DeviceState.SHUTDOWN)
+        Device.shutdown(udid)
         try:
             _log.debug('xcrun simctl erase %s', udid)
             subprocess.check_call(['xcrun', 'simctl', 'erase', udid])
@@ -281,6 +296,8 @@
     devices_re = re.compile(
         '\s*(?P<name>[^(]+ )\((?P<udid>[^)]+)\) \((?P<state>[^)]+)\)( \((?P<availability>[^)]+)\))?')
 
+    _managed_devices = {}
+
     def __init__(self, host=None):
         self._host = host or Host()
         self.runtimes = []
@@ -296,7 +313,41 @@
         BOOTED = 3
         SHUTTING_DOWN = 4
 
+    NAME_FOR_STATE = [
+        'CREATING',
+        'SHUTDOWN',
+        'BOOTING',
+        'BOOTED',
+        'SHUTTING_DOWN'
+    ]
+
     @staticmethod
+    def create_device(number, device_type, runtime):
+        device = Simulator().lookup_or_create_device(device_type.name + ' WebKit Tester' + str(number), device_type, runtime)
+        _log.debug('created device {} {}'.format(number, device))
+        assert(len(Simulator._managed_devices) == number)
+        Simulator._managed_devices[number] = device
+
+    @staticmethod
+    def remove_device(number):
+        if not Simulator._managed_devices[number]:
+            return
+        device_udid = Simulator._managed_devices[number].udid
+        _log.debug('removing device {} {}'.format(number, device_udid))
+        del Simulator._managed_devices[number]
+        Simulator.delete_device(device_udid)
+
+    @staticmethod
+    def device_number(number):
+        return Simulator._managed_devices[number]
+
+    @staticmethod
+    def device_state_description(state):
+        if (state == Simulator.DeviceState.DOES_NOT_EXIST):
+            return 'DOES_NOT_EXIST'
+        return Simulator.NAME_FOR_STATE[state]
+
+    @staticmethod
     def wait_until_device_is_booted(udid, timeout_seconds=60 * 5):
         Simulator.wait_until_device_is_in_state(udid, Simulator.DeviceState.BOOTED, timeout_seconds)
         with timeout(seconds=timeout_seconds):
@@ -315,17 +366,17 @@
 
     @staticmethod
     def wait_until_device_is_in_state(udid, wait_until_state, timeout_seconds=60 * 5):
-        _log.debug('waiting for device %s to enter state %s with timeout %s', udid, wait_until_state, timeout_seconds)
+        _log.debug('waiting for device %s to enter state %s with timeout %s', udid, Simulator.device_state_description(wait_until_state), timeout_seconds)
         with timeout(seconds=timeout_seconds):
             device_state = Simulator.device_state(udid)
             while (device_state != wait_until_state):
                 device_state = Simulator.device_state(udid)
-                _log.debug(' device state %s', device_state)
+                _log.debug(' device state %s', Simulator.device_state_description(device_state))
                 time.sleep(0.5)
 
         end_state = Simulator.device_state(udid)
         if (end_state != wait_until_state):
-            raise RuntimeError('Timed out waiting for simulator device to enter state {0}; current state is {1}'.format(wait_until_state, end_state))
+            raise RuntimeError('Timed out waiting for simulator device to enter state {0}; current state is {1}'.format(Simulator.device_state_description(wait_until_state), Simulator.device_state_description(end_state)))
 
     @staticmethod
     def device_state(udid):
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to