# Copyright (c) 2013 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.

"""
Provides utility class for stopping and restarting services

When using this class, one likely wishes to do the following:

    def initialize(self):
        self._services = service_stopper.ServiceStopper(['service'])
        self._services.stop_services()


    def cleanup(self):
        self._services.start_services()

As this ensures that the services will be off before the test code runs, and
the test framework will ensure that the services are restarted through any
code path out of the test.
"""

import logging
import os

from autotest_lib.client.bin import utils
from autotest_lib.client.cros import upstart


class ServiceStopper(object):
    """Class to manage CrOS services.
    Public attributes:
      services_to_stop: list of services that should be stopped

   Public constants:
      POWER_DRAW_SERVICES: list of services that influence power test in
    unpredictable/undesirable manners.

    Public methods:
      stop_sevices: stop running system services.
      restore_services: restore services that were previously stopped.

    Private attributes:
      _services_stopped: list of services that were successfully stopped
    """

    POWER_DRAW_SERVICES = ['fwupd', 'powerd', 'update-engine', 'vnc']

    # List of thermal throttling services that should be disabled.
    # - temp_metrics for link.
    # - thermal for daisy, snow, pit etc.
    # - dptf for intel >= baytrail
    # TODO(ihf): cpu_quiet on nyan isn't a service. We still need to disable it
    #            on nyan. See crbug.com/357457.
    THERMAL_SERVICES = ['dptf', 'temp_metrics', 'thermal']

    def __init__(self, services_to_stop=[]):
        """Initialize instance of class.

        By Default sets an empty list of services.
        """
        self.services_to_stop = services_to_stop
        self._services_stopped = []


    def stop_services(self):
        """Turn off managed services."""

        for service in self.services_to_stop:
            if not upstart.has_service(service):
                continue
            if not upstart.is_running(service):
                continue
            upstart.stop_job(service)
            self._services_stopped.append(service)


    def restore_services(self):
        """Restore services that were stopped."""
        for service in reversed(self._services_stopped):
            upstart.restart_job(service)
        self._services_stopped = []


    def __enter__(self):
        self.stop_services()
        return self


    def __exit__(self, exnval, exntype, exnstack):
        self.restore_services()


    def close(self):
        """Equivalent to restore_services."""
        self.restore_services()


    def _dptf_fixup_pl1(self):
        """For intel devices that don't set their PL1 override in coreboot's
        devicetree.cb (See 'register "tdp_pl1_override') stopping DPTF will
        change the PL1 limit to the platform default.  For eve (KBL-Y) that
        would be 4.5W.  To workaround this until FW can be fixed we should
        instead query what the PL1 limit is for the proc_thermal driver and
        write it to the PL1 constraint.

        TODO(b/144020442)
        """
        pl1_max_path = \
        '/sys/devices/pci0000:00/0000:00:04.0/power_limits/power_limit_0_max_uw'
        pl1_constraint_path = \
        '/sys/class/powercap/intel-rapl:0/constraint_0_power_limit_uw'
        if not os.path.exists(pl1_max_path):
            return

        pl1 = int(utils.read_one_line(pl1_max_path))
        logging.debug('PL1 set to %d uw', pl1)
        utils.system('echo %d > %s' % (pl1, pl1_constraint_path))


def get_thermal_service_stopper():
    """Convenience method to retrieve thermal service stopper."""
    return ServiceStopper(services_to_stop=ServiceStopper.THERMAL_SERVICES)
