# Lint as: python2, python3
# Copyright 2018 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import logging
import time

from autotest_lib.client.bin import test
from autotest_lib.client.bin import utils
from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib.cros import chrome
from autotest_lib.client.common_lib.cros import power_load_util
from autotest_lib.client.cros.power import sys_power


# Stop adding tab when swap_free / swap_total is less than this value.
_LOW_SWAP_THRESHOLD = 0.5
# Terminate the test if active_tabs / created_tabs is less than this value.
_TOO_FEW_ACTIVE_TABS_THRESHOLD = 0.33


class power_LowMemorySuspend(test.test):
    """Low memory suspending stress test."""
    version = 1

    def low_swap_free(self):
        """Returns true if free swap is low."""
        meminfo = utils.get_meminfo()
        if meminfo.SwapFree < meminfo.SwapTotal * _LOW_SWAP_THRESHOLD:
            logging.info("Swap is low, swap free: %d, swap total: %d",
                         meminfo.SwapFree, meminfo.SwapTotal)
            return True
        return False

    def create_tabs(self, cr):
        """Creates tabs until swap free is low.

        @return: list of created tabs
        """
        # Any non-trivial web page is suitable to consume memory.
        URL = 'https://inbox.google.com/'
        tabs = []

        # There is some race condition to navigate the first tab, navigating
        # the first tab may fail unless sleep 2 seconds before navigation.
        # Skip the first tab.

        while not self.low_swap_free():
            logging.info('creating tab %d', len(tabs))
            tab = cr.browser.tabs.New()
            tabs.append(tab)
            tab.Navigate(URL);
            try:
                tab.WaitForDocumentReadyStateToBeComplete(timeout=20)
            except Exception as e:
                logging.warning('Exception when waiting page ready: %s', e)

        return tabs

    def check_tab_discard(self, cr, tabs):
        """Raises error if too many tabs are discarded."""
        try:
            active_tabs = len(cr.browser.tabs)
        except Exception as e:
            logging.info('error getting active tab count: %s', e)
            return
        created_tabs = len(tabs)
        if (active_tabs < created_tabs * _TOO_FEW_ACTIVE_TABS_THRESHOLD):
            msg = ('Too many discards, active tabs: %d, created tabs: %d' %
                   (active_tabs, created_tabs))
            raise error.TestFail(msg)

    def cycling_suspend(self, cr, tabs, switches_per_suspend,
                        total_suspend_duration, suspend_seconds,
                        additional_sleep):
        """Page cycling and suspending.

        @return: total suspending count.
        """
        start_time = time.time()
        suspend_count = 0
        tab_index = 0
        spurious_wakeup_count = 0
        MAX_SPURIOUS_WAKEUP = 5

        while time.time() - start_time < total_suspend_duration:
            # Page cycling
            for _ in range(switches_per_suspend):
                try:
                    tabs[tab_index].Activate()
                    tabs[tab_index].WaitForFrameToBeDisplayed()
                except Exception as e:
                    logging.info('cannot activate tab: %s', e)
                tab_index = (tab_index + 1) % len(tabs)

            self.check_tab_discard(cr, tabs)

            # Suspending for the specified seconds.
            try:
                sys_power.do_suspend(suspend_seconds)
            except sys_power.SpuriousWakeupError:
                spurious_wakeup_count += 1
                if spurious_wakeup_count > MAX_SPURIOUS_WAKEUP:
                    raise error.TestFail('Too many SpuriousWakeupError.')
            suspend_count += 1

            # Waiting for system stable, otherwise the subsequent tab
            # operations may fail.
            time.sleep(additional_sleep)

            self.check_tab_discard(cr, tabs)

        return suspend_count

    def run_once(self, switches_per_suspend=15, total_suspend_duration=2400,
                 suspend_seconds=10, additional_sleep=10):
        """Runs the test once."""
        username = power_load_util.get_username()
        password = power_load_util.get_password()
        with chrome.Chrome(gaia_login=True, username=username,
                           password=password) as cr:
            tabs = self.create_tabs(cr)
            suspend_count = self.cycling_suspend(
                cr, tabs, switches_per_suspend, total_suspend_duration,
                suspend_seconds, additional_sleep)

            tabs_after_suspending = len(cr.browser.tabs)
            meminfo = utils.get_meminfo()
            ending_swap_free = meminfo.SwapFree
            swap_total = meminfo.SwapTotal

        perf_results = {}
        perf_results['number_of_tabs'] = len(tabs)
        perf_results['number_of_suspending'] = suspend_count
        perf_results['tabs_after_suspending'] = tabs_after_suspending
        perf_results['ending_swap_free'] = ending_swap_free
        perf_results['swap_total'] = swap_total
        self.write_perf_keyval(perf_results)

        self.output_perf_value(description='number_of_tabs',
                               value=len(tabs))
        self.output_perf_value(description='number_of_suspending',
                               value=suspend_count)
        self.output_perf_value(description='tabs_after_suspending',
                               value=tabs_after_suspending)
        self.output_perf_value(description='ending_swap_free',
                               value=ending_swap_free, units='KB')
        self.output_perf_value(description='swap_total',
                               value=swap_total, units='KB')

