1# Copyright 2018 The Chromium OS 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
5import logging
6import time
7
8from autotest_lib.client.bin import test
9from autotest_lib.client.bin import utils
10from autotest_lib.client.common_lib import error
11from autotest_lib.client.common_lib.cros import chrome
12from autotest_lib.client.common_lib.cros import arc_util
13from autotest_lib.client.cros.power import sys_power
14
15
16# Stop adding tab when swap_free / swap_total is less than this value.
17_LOW_SWAP_THRESHOLD = 0.5
18# Terminate the test if active_tabs / created_tabs is less than this value.
19_TOO_FEW_ACTIVE_TABS_THRESHOLD = 0.33
20
21
22class power_LowMemorySuspend(test.test):
23    """Low memory suspending stress test."""
24    version = 1
25
26    def low_swap_free(self):
27        """Returns true if free swap is low."""
28        meminfo = utils.get_meminfo()
29        if meminfo.SwapFree < meminfo.SwapTotal * _LOW_SWAP_THRESHOLD:
30            logging.info("Swap is low, swap free: %d, swap total: %d",
31                         meminfo.SwapFree, meminfo.SwapTotal)
32            return True
33        return False
34
35    def create_tabs(self, cr):
36        """Creates tabs until swap free is low.
37
38        @return: list of created tabs
39        """
40        # Any non-trivial web page is suitable to consume memory.
41        URL = 'https://inbox.google.com/'
42        tabs = []
43
44        # There is some race condition to navigate the first tab, navigating
45        # the first tab may fail unless sleep 2 seconds before navigation.
46        # Skip the first tab.
47
48        while not self.low_swap_free():
49            logging.info('creating tab %d', len(tabs))
50            tab = cr.browser.tabs.New()
51            tabs.append(tab)
52            tab.Navigate(URL);
53            try:
54                tab.WaitForDocumentReadyStateToBeComplete(timeout=20)
55            except Exception as e:
56                logging.warning('Exception when waiting page ready: %s', e)
57
58        return tabs
59
60    def check_tab_discard(self, cr, tabs):
61        """Raises error if too many tabs are discarded."""
62        try:
63            active_tabs = len(cr.browser.tabs)
64        except Exception as e:
65            logging.info('error getting active tab count: %s', e)
66            return
67        created_tabs = len(tabs)
68        if (active_tabs < created_tabs * _TOO_FEW_ACTIVE_TABS_THRESHOLD):
69            msg = ('Too many discards, active tabs: %d, created tabs: %d' %
70                   (active_tabs, created_tabs))
71            raise error.TestFail(msg)
72
73    def cycling_suspend(self, cr, tabs, switches_per_suspend,
74                        total_suspend_duration, suspend_seconds,
75                        additional_sleep):
76        """Page cycling and suspending.
77
78        @return: total suspending count.
79        """
80        start_time = time.time()
81        suspend_count = 0
82        tab_index = 0
83        spurious_wakeup_count = 0
84        MAX_SPURIOUS_WAKEUP = 5
85
86        while time.time() - start_time < total_suspend_duration:
87            # Page cycling
88            for _ in range(switches_per_suspend):
89                try:
90                    tabs[tab_index].Activate()
91                    tabs[tab_index].WaitForFrameToBeDisplayed()
92                except Exception as e:
93                    logging.info('cannot activate tab: %s', e)
94                tab_index = (tab_index + 1) % len(tabs)
95
96            self.check_tab_discard(cr, tabs)
97
98            # Suspending for the specified seconds.
99            try:
100                sys_power.do_suspend(suspend_seconds)
101            except sys_power.SpuriousWakeupError:
102                spurious_wakeup_count += 1
103                if spurious_wakeup_count > MAX_SPURIOUS_WAKEUP:
104                    raise error.TestFail('Too many SpuriousWakeupError.')
105            suspend_count += 1
106
107            # Waiting for system stable, otherwise the subsequent tab
108            # operations may fail.
109            time.sleep(additional_sleep)
110
111            self.check_tab_discard(cr, tabs)
112
113        return suspend_count
114
115    def run_once(self, switches_per_suspend=15, total_suspend_duration=2400,
116                 suspend_seconds=10, additional_sleep=10):
117        """Runs the test once."""
118        username, password = arc_util.get_test_account_info()
119        with chrome.Chrome(gaia_login=True, username=username,
120                           password=password) as cr:
121            tabs = self.create_tabs(cr)
122            suspend_count = self.cycling_suspend(
123                cr, tabs, switches_per_suspend, total_suspend_duration,
124                suspend_seconds, additional_sleep)
125
126            tabs_after_suspending = len(cr.browser.tabs)
127            meminfo = utils.get_meminfo()
128            ending_swap_free = meminfo.SwapFree
129            swap_total = meminfo.SwapTotal
130
131        perf_results = {}
132        perf_results['number_of_tabs'] = len(tabs)
133        perf_results['number_of_suspending'] = suspend_count
134        perf_results['tabs_after_suspending'] = tabs_after_suspending
135        perf_results['ending_swap_free'] = ending_swap_free
136        perf_results['swap_total'] = swap_total
137        self.write_perf_keyval(perf_results)
138
139        self.output_perf_value(description='number_of_tabs',
140                               value=len(tabs))
141        self.output_perf_value(description='number_of_suspending',
142                               value=suspend_count)
143        self.output_perf_value(description='tabs_after_suspending',
144                               value=tabs_after_suspending)
145        self.output_perf_value(description='ending_swap_free',
146                               value=ending_swap_free, units='KB')
147        self.output_perf_value(description='swap_total',
148                               value=swap_total, units='KB')
149
150