OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Basic pyauto performance tests. | 6 """Basic pyauto performance tests. |
7 | 7 |
8 For tests that need to be run for multiple iterations (e.g., so that average | 8 For tests that need to be run for multiple iterations (e.g., so that average |
9 and standard deviation values can be reported), the default number of iterations | 9 and standard deviation values can be reported), the default number of iterations |
10 run for each of these tests is specified by |_DEFAULT_NUM_ITERATIONS|. | 10 run for each of these tests is specified by |_DEFAULT_NUM_ITERATIONS|. |
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
83 self._seen_graph_lines = {} | 83 self._seen_graph_lines = {} |
84 | 84 |
85 pyauto.PyUITest.setUp(self) | 85 pyauto.PyUITest.setUp(self) |
86 | 86 |
87 # Flush all buffers to disk and wait until system calms down. Must be done | 87 # Flush all buffers to disk and wait until system calms down. Must be done |
88 # *after* calling pyauto.PyUITest.setUp, since that is where Chrome is | 88 # *after* calling pyauto.PyUITest.setUp, since that is where Chrome is |
89 # killed and re-initialized for a new test. | 89 # killed and re-initialized for a new test. |
90 # TODO(dennisjeffrey): Implement wait for idle CPU on Windows/Mac. | 90 # TODO(dennisjeffrey): Implement wait for idle CPU on Windows/Mac. |
91 if self.IsLinux(): # IsLinux() also implies IsChromeOS(). | 91 if self.IsLinux(): # IsLinux() also implies IsChromeOS(). |
92 os.system('sync') | 92 os.system('sync') |
93 self._WaitForIdleCPU(60.0, 0.03) | 93 self._WaitForIdleCPU(60.0, 0.05) |
94 | 94 |
95 def _WaitForIdleCPU(self, timeout, utilization): | 95 def _WaitForIdleCPU(self, timeout, utilization): |
96 """Waits for the CPU to become idle (< utilization). | 96 """Waits for the CPU to become idle (< utilization). |
97 | 97 |
98 Args: | 98 Args: |
99 timeout: The longest time in seconds to wait before throwing an error. | 99 timeout: The longest time in seconds to wait before throwing an error. |
100 utilization: The CPU usage below which the system should be considered | 100 utilization: The CPU usage below which the system should be considered |
101 idle (between 0 and 1.0 independent of cores/hyperthreads). | 101 idle (between 0 and 1.0 independent of cores/hyperthreads). |
102 """ | 102 """ |
103 time_passed = 0.0 | 103 time_passed = 0.0 |
(...skipping 1654 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1758 def testIntl2File(self): | 1758 def testIntl2File(self): |
1759 self._RunPageCyclerTest('intl2', self._num_iterations, 'Intl2File') | 1759 self._RunPageCyclerTest('intl2', self._num_iterations, 'Intl2File') |
1760 | 1760 |
1761 def testMozFile(self): | 1761 def testMozFile(self): |
1762 self._RunPageCyclerTest('moz', self._num_iterations, 'MozFile') | 1762 self._RunPageCyclerTest('moz', self._num_iterations, 'MozFile') |
1763 | 1763 |
1764 def testMoz2File(self): | 1764 def testMoz2File(self): |
1765 self._RunPageCyclerTest('moz2', self._num_iterations, 'Moz2File') | 1765 self._RunPageCyclerTest('moz2', self._num_iterations, 'Moz2File') |
1766 | 1766 |
1767 | 1767 |
| 1768 class MemoryTest(BasePerfTest): |
| 1769 """Tests to measure memory consumption under different usage scenarios.""" |
| 1770 |
| 1771 def setUp(self): |
| 1772 pyauto.PyUITest.setUp(self) |
| 1773 |
| 1774 # Log in to get a fresh Chrome instance with a clean memory state (if |
| 1775 # already logged in, log out first). |
| 1776 if self.GetLoginInfo()['is_logged_in']: |
| 1777 self.Logout() |
| 1778 self.assertFalse(self.GetLoginInfo()['is_logged_in'], |
| 1779 msg='Failed to log out.') |
| 1780 |
| 1781 credentials = self.GetPrivateInfo()['test_google_account'] |
| 1782 self.Login(credentials['username'], credentials['password']) |
| 1783 self.assertTrue(self.GetLoginInfo()['is_logged_in'], |
| 1784 msg='Failed to log in.') |
| 1785 |
| 1786 def ExtraChromeFlags(self): |
| 1787 """Launches Chrome with custom flags. |
| 1788 |
| 1789 Returns: |
| 1790 A list of extra flags to pass to Chrome when it is launched. |
| 1791 """ |
| 1792 # Ensure Chrome assigns one renderer process to each tab. |
| 1793 return super(MemoryTest, self).ExtraChromeFlags() + ['--process-per-tab'] |
| 1794 |
| 1795 def _GetMemoryStats(self, duration): |
| 1796 """Identifies and returns different kinds of current memory usage stats. |
| 1797 |
| 1798 This function samples values each second for |duration| seconds, then |
| 1799 outputs the min, max, and ending values for each measurement type. |
| 1800 |
| 1801 Args: |
| 1802 duration: The number of seconds to sample data before outputting the |
| 1803 minimum, maximum, and ending values for each measurement type. |
| 1804 |
| 1805 Returns: |
| 1806 A dictionary containing memory usage information. Each measurement type |
| 1807 is associated with the min, max, and ending values from among all |
| 1808 sampled values. Values are specified in KB. |
| 1809 { |
| 1810 'gem_obj': { # GPU memory usage. |
| 1811 'min': ..., |
| 1812 'max': ..., |
| 1813 'end': ..., |
| 1814 }, |
| 1815 'gtt': { ... }, # GPU memory usage (graphics translation table). |
| 1816 'mem_free': { ... }, # CPU free memory. |
| 1817 'mem_available': { ... }, # CPU available memory. |
| 1818 'mem_shared': { ... }, # CPU shared memory. |
| 1819 'mem_cached': { ... }, # CPU cached memory. |
| 1820 'mem_anon': { ... }, # CPU anon memory (active + inactive). |
| 1821 'mem_file': { ... }, # CPU file memory (active + inactive). |
| 1822 'mem_slab': { ... }, # CPU slab memory. |
| 1823 'browser_priv': { ... }, # Chrome browser private memory. |
| 1824 'browser_shared': { ... }, # Chrome browser shared memory. |
| 1825 'gpu_priv': { ... }, # Chrome GPU private memory. |
| 1826 'gpu_shared': { ... }, # Chrome GPU shared memory. |
| 1827 'renderer_priv': { ... }, # Total private memory of all renderers. |
| 1828 'renderer_shared': { ... }, # Total shared memory of all renderers. |
| 1829 } |
| 1830 """ |
| 1831 logging.debug('Sampling memory information for %d seconds...' % duration) |
| 1832 stats = {} |
| 1833 |
| 1834 for _ in xrange(duration): |
| 1835 # GPU memory. |
| 1836 gem_obj_path = '/sys/kernel/debug/dri/0/i915_gem_objects' |
| 1837 if os.path.exists(gem_obj_path): |
| 1838 p = subprocess.Popen('grep bytes %s' % gem_obj_path, |
| 1839 stdout=subprocess.PIPE, shell=True) |
| 1840 stdout = p.communicate()[0] |
| 1841 |
| 1842 gem_obj = re.search( |
| 1843 '\d+ objects, (\d+) bytes\n', stdout).group(1) |
| 1844 if 'gem_obj' not in stats: |
| 1845 stats['gem_obj'] = [] |
| 1846 stats['gem_obj'].append(int(gem_obj) / 1024.0) |
| 1847 |
| 1848 gtt_path = '/sys/kernel/debug/dri/0/i915_gem_gtt' |
| 1849 if os.path.exists(gtt_path): |
| 1850 p = subprocess.Popen('grep bytes %s' % gtt_path, |
| 1851 stdout=subprocess.PIPE, shell=True) |
| 1852 stdout = p.communicate()[0] |
| 1853 |
| 1854 gtt = re.search( |
| 1855 'Total [\d]+ objects, ([\d]+) bytes', stdout).group(1) |
| 1856 if 'gtt' not in stats: |
| 1857 stats['gtt'] = [] |
| 1858 stats['gtt'].append(int(gtt) / 1024.0) |
| 1859 |
| 1860 # CPU memory. |
| 1861 stdout = '' |
| 1862 with open('/proc/meminfo') as f: |
| 1863 stdout = f.read() |
| 1864 mem_free = re.search('MemFree:\s*([\d]+) kB', stdout).group(1) |
| 1865 |
| 1866 if 'mem_free' not in stats: |
| 1867 stats['mem_free'] = [] |
| 1868 stats['mem_free'].append(int(mem_free)) |
| 1869 |
| 1870 mem_dirty = re.search('Dirty:\s*([\d]+) kB', stdout).group(1) |
| 1871 mem_active_file = re.search( |
| 1872 'Active\(file\):\s*([\d]+) kB', stdout).group(1) |
| 1873 mem_inactive_file = re.search( |
| 1874 'Inactive\(file\):\s*([\d]+) kB', stdout).group(1) |
| 1875 |
| 1876 with open('/proc/sys/vm/min_filelist_kbytes') as f: |
| 1877 mem_min_file = f.read() |
| 1878 |
| 1879 # Available memory = |
| 1880 # MemFree + ActiveFile + InactiveFile - DirtyMem - MinFileMem |
| 1881 if 'mem_available' not in stats: |
| 1882 stats['mem_available'] = [] |
| 1883 stats['mem_available'].append( |
| 1884 int(mem_free) + int(mem_active_file) + int(mem_inactive_file) - |
| 1885 int(mem_dirty) - int(mem_min_file)) |
| 1886 |
| 1887 mem_shared = re.search('Shmem:\s*([\d]+) kB', stdout).group(1) |
| 1888 if 'mem_shared' not in stats: |
| 1889 stats['mem_shared'] = [] |
| 1890 stats['mem_shared'].append(int(mem_shared)) |
| 1891 |
| 1892 mem_cached = re.search('Cached:\s*([\d]+) kB', stdout).group(1) |
| 1893 if 'mem_cached' not in stats: |
| 1894 stats['mem_cached'] = [] |
| 1895 stats['mem_cached'].append(int(mem_cached)) |
| 1896 |
| 1897 mem_anon_active = re.search('Active\(anon\):\s*([\d]+) kB', |
| 1898 stdout).group(1) |
| 1899 mem_anon_inactive = re.search('Inactive\(anon\):\s*([\d]+) kB', |
| 1900 stdout).group(1) |
| 1901 if 'mem_anon' not in stats: |
| 1902 stats['mem_anon'] = [] |
| 1903 stats['mem_anon'].append(int(mem_anon_active) + int(mem_anon_inactive)) |
| 1904 |
| 1905 mem_file_active = re.search('Active\(file\):\s*([\d]+) kB', |
| 1906 stdout).group(1) |
| 1907 mem_file_inactive = re.search('Inactive\(file\):\s*([\d]+) kB', |
| 1908 stdout).group(1) |
| 1909 if 'mem_file' not in stats: |
| 1910 stats['mem_file'] = [] |
| 1911 stats['mem_file'].append(int(mem_file_active) + int(mem_file_inactive)) |
| 1912 |
| 1913 mem_slab = re.search('Slab:\s*([\d]+) kB', stdout).group(1) |
| 1914 if 'mem_slab' not in stats: |
| 1915 stats['mem_slab'] = [] |
| 1916 stats['mem_slab'].append(int(mem_slab)) |
| 1917 |
| 1918 # Chrome process memory. |
| 1919 pinfo = self.GetProcessInfo()['browsers'][0]['processes'] |
| 1920 total_renderer_priv = 0 |
| 1921 total_renderer_shared = 0 |
| 1922 for process in pinfo: |
| 1923 mem_priv = process['working_set_mem']['priv'] |
| 1924 mem_shared = process['working_set_mem']['shared'] |
| 1925 if process['child_process_type'] == 'Browser': |
| 1926 if 'browser_priv' not in stats: |
| 1927 stats['browser_priv'] = [] |
| 1928 stats['browser_priv'].append(int(mem_priv)) |
| 1929 if 'browser_shared' not in stats: |
| 1930 stats['browser_shared'] = [] |
| 1931 stats['browser_shared'].append(int(mem_shared)) |
| 1932 elif process['child_process_type'] == 'GPU': |
| 1933 if 'gpu_priv' not in stats: |
| 1934 stats['gpu_priv'] = [] |
| 1935 stats['gpu_priv'].append(int(mem_priv)) |
| 1936 if 'gpu_shared' not in stats: |
| 1937 stats['gpu_shared'] = [] |
| 1938 stats['gpu_shared'].append(int(mem_shared)) |
| 1939 elif process['child_process_type'] == 'Tab': |
| 1940 # Sum the memory of all renderer processes. |
| 1941 total_renderer_priv += int(mem_priv) |
| 1942 total_renderer_shared += int(mem_shared) |
| 1943 if 'renderer_priv' not in stats: |
| 1944 stats['renderer_priv'] = [] |
| 1945 stats['renderer_priv'].append(int(total_renderer_priv)) |
| 1946 if 'renderer_shared' not in stats: |
| 1947 stats['renderer_shared'] = [] |
| 1948 stats['renderer_shared'].append(int(total_renderer_shared)) |
| 1949 |
| 1950 time.sleep(1) |
| 1951 |
| 1952 # Compute min, max, and ending values to return. |
| 1953 result = {} |
| 1954 for measurement_type in stats: |
| 1955 values = stats[measurement_type] |
| 1956 result[measurement_type] = { |
| 1957 'min': min(values), |
| 1958 'max': max(values), |
| 1959 'end': values[-1], |
| 1960 } |
| 1961 |
| 1962 return result |
| 1963 |
| 1964 def _RecordMemoryStats(self, description, when, duration): |
| 1965 """Outputs memory statistics to be graphed. |
| 1966 |
| 1967 Args: |
| 1968 description: A string description for the test. Should not contain |
| 1969 spaces. For example, 'MemCtrl'. |
| 1970 when: A string description of when the memory stats are being recorded |
| 1971 during test execution (since memory stats may be recorded multiple |
| 1972 times during a test execution at certain "interesting" times). Should |
| 1973 not contain spaces. |
| 1974 duration: The number of seconds to sample data before outputting the |
| 1975 memory statistics. |
| 1976 """ |
| 1977 mem = self._GetMemoryStats(duration) |
| 1978 measurement_types = [ |
| 1979 ('gem_obj', 'GemObj'), |
| 1980 ('gtt', 'GTT'), |
| 1981 ('mem_free', 'MemFree'), |
| 1982 ('mem_available', 'MemAvail'), |
| 1983 ('mem_shared', 'MemShare'), |
| 1984 ('mem_cached', 'MemCache'), |
| 1985 ('mem_anon', 'MemAnon'), |
| 1986 ('mem_file', 'MemFile'), |
| 1987 ('mem_slab', 'MemSlab'), |
| 1988 ('browser_priv', 'BrowPriv'), |
| 1989 ('browser_shared', 'BrowShar'), |
| 1990 ('gpu_priv', 'GpuPriv'), |
| 1991 ('gpu_shared', 'GpuShar'), |
| 1992 ('renderer_priv', 'RendPriv'), |
| 1993 ('renderer_shared', 'RendShar'), |
| 1994 ] |
| 1995 for type_key, type_string in measurement_types: |
| 1996 if type_key not in mem: |
| 1997 continue |
| 1998 self._OutputPerfGraphValue( |
| 1999 '%s-Min%s-%s' % (description, type_string, when), |
| 2000 mem[type_key]['min'], 'KB', '%s-%s' % (description, type_string)) |
| 2001 self._OutputPerfGraphValue( |
| 2002 '%s-Max%s-%s' % (description, type_string, when), |
| 2003 mem[type_key]['max'], 'KB', '%s-%s' % (description, type_string)) |
| 2004 self._OutputPerfGraphValue( |
| 2005 '%s-End%s-%s' % (description, type_string, when), |
| 2006 mem[type_key]['end'], 'KB', '%s-%s' % (description, type_string)) |
| 2007 |
| 2008 def _RunTest(self, tabs, description, duration): |
| 2009 """Runs a general memory test. |
| 2010 |
| 2011 Args: |
| 2012 tabs: A list of strings representing the URLs of the websites to open |
| 2013 during this test. |
| 2014 description: A string description for the test. Should not contain |
| 2015 spaces. For example, 'MemCtrl'. |
| 2016 duration: The number of seconds to sample data before outputting memory |
| 2017 statistics. |
| 2018 """ |
| 2019 self._RecordMemoryStats(description, '0Tabs0', duration) |
| 2020 |
| 2021 for iteration_num in xrange(2): |
| 2022 for site in tabs: |
| 2023 self.AppendTab(pyauto.GURL(site)) |
| 2024 |
| 2025 self._RecordMemoryStats(description, |
| 2026 '%dTabs%d' % (len(tabs), iteration_num + 1), |
| 2027 duration) |
| 2028 |
| 2029 for _ in xrange(len(tabs)): |
| 2030 self.GetBrowserWindow(0).GetTab(1).Close(True) |
| 2031 |
| 2032 self._RecordMemoryStats(description, '0Tabs%d' % (iteration_num + 1), |
| 2033 duration) |
| 2034 |
| 2035 def testOpenCloseTabsControl(self): |
| 2036 """Measures memory usage when opening/closing tabs to about:blank.""" |
| 2037 tabs = ['about:blank'] * 10 |
| 2038 self._RunTest(tabs, 'MemCtrl', 15) |
| 2039 |
| 2040 def testOpenCloseTabsLiveSites(self): |
| 2041 """Measures memory usage when opening/closing tabs to live sites.""" |
| 2042 tabs = [ |
| 2043 'http://www.google.com/gmail', |
| 2044 'http://www.google.com/calendar', |
| 2045 'http://www.google.com/plus', |
| 2046 'http://www.google.com/youtube', |
| 2047 'http://www.nytimes.com', |
| 2048 'http://www.cnn.com', |
| 2049 'http://www.facebook.com', |
| 2050 'http://www.techcrunch.com', |
| 2051 'http://www.theverge.com', |
| 2052 'http://www.yahoo.com', |
| 2053 ] |
| 2054 # Log in to a test Google account to make connections to the above Google |
| 2055 # websites more interesting. |
| 2056 self._LoginToGoogleAccount() |
| 2057 self._RunTest(tabs, 'MemLive', 20) |
| 2058 |
| 2059 |
1768 class PerfTestServerRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): | 2060 class PerfTestServerRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): |
1769 """Request handler for the local performance test server.""" | 2061 """Request handler for the local performance test server.""" |
1770 | 2062 |
1771 def _IgnoreHandler(self, unused_args): | 2063 def _IgnoreHandler(self, unused_args): |
1772 """A GET request handler that simply replies with status code 200. | 2064 """A GET request handler that simply replies with status code 200. |
1773 | 2065 |
1774 Args: | 2066 Args: |
1775 unused_args: A dictionary of arguments for the current GET request. | 2067 unused_args: A dictionary of arguments for the current GET request. |
1776 The arguments are ignored. | 2068 The arguments are ignored. |
1777 """ | 2069 """ |
(...skipping 212 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1990 """Identifies the port number to which the server is currently bound. | 2282 """Identifies the port number to which the server is currently bound. |
1991 | 2283 |
1992 Returns: | 2284 Returns: |
1993 The numeric port number to which the server is currently bound. | 2285 The numeric port number to which the server is currently bound. |
1994 """ | 2286 """ |
1995 return self._server.server_address[1] | 2287 return self._server.server_address[1] |
1996 | 2288 |
1997 | 2289 |
1998 if __name__ == '__main__': | 2290 if __name__ == '__main__': |
1999 pyauto_functional.Main() | 2291 pyauto_functional.Main() |
OLD | NEW |