Bug 1567954 - Part 1: Initialize Mozpower module and implement PowerBase class. r=perftest-reviewers,ahal,rwood
authorGregory Mierzwinski <gmierz2@outlook.com>
Thu, 01 Aug 2019 20:36:00 +0000
changeset 486816 e067cbbbe4d4ee6764cc4343ce43a22dc24412bc
parent 486815 54d0d836b0970a9db8af7844326fdc0d72845d64
child 486817 d2a56641f494f2573f1643325869e70a6dbc8c05
push id36407
push userncsoregi@mozilla.com
push dateThu, 08 Aug 2019 09:33:10 +0000
treeherdermozilla-central@a28a338396c3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersperftest-reviewers, ahal, rwood
bugs1567954
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1567954 - Part 1: Initialize Mozpower module and implement PowerBase class. r=perftest-reviewers,ahal,rwood This patch initializes the Mozpower module and implements the PowerBase class. This class is used as a base for subclasses that implement power measurement tooling for various OS and CPU combinations. Differential Revision: https://phabricator.services.mozilla.com/D39191
testing/config/mozbase_requirements.txt
testing/config/mozbase_source_requirements.txt
testing/mozbase/moz.build
testing/mozbase/mozpower/mozpower/__init__.py
testing/mozbase/mozpower/mozpower/mozpowerutils.py
testing/mozbase/mozpower/mozpower/powerbase.py
testing/mozbase/mozpower/setup.cfg
testing/mozbase/mozpower/setup.py
testing/mozbase/mozpower/tests/conftest.py
testing/mozbase/mozpower/tests/manifest.ini
testing/mozbase/mozpower/tests/test_powerbase.py
testing/mozbase/packages.txt
testing/tools/mach_test_package_bootstrap.py
--- a/testing/config/mozbase_requirements.txt
+++ b/testing/config/mozbase_requirements.txt
@@ -6,15 +6,16 @@
 ../mozbase/mozdevice
 ../mozbase/mozfile
 ../mozbase/mozhttpd
 ../mozbase/mozinfo
 ../mozbase/mozinstall
 ../mozbase/mozleak
 ../mozbase/mozlog
 ../mozbase/moznetwork
+../mozbase/mozpower
 ../mozbase/mozprocess
 ../mozbase/mozprofile
 ../mozbase/mozproxy
 ../mozbase/mozrunner
 ../mozbase/mozscreenshot
 ../mozbase/moztest
 ../mozbase/mozversion
--- a/testing/config/mozbase_source_requirements.txt
+++ b/testing/config/mozbase_source_requirements.txt
@@ -6,15 +6,16 @@
 --editable ../mozbase/mozdevice
 --editable ../mozbase/mozfile
 --editable ../mozbase/mozhttpd
 --editable ../mozbase/mozinfo
 --editable ../mozbase/mozinstall
 --editable ../mozbase/mozleak
 --editable ../mozbase/mozlog
 --editable ../mozbase/moznetwork
+--editable ../mozbase/mozpower
 --editable ../mozbase/mozprocess
 --editable ../mozbase/mozprofile
 --editable ../mozbase/mozproxy
 --editable ../mozbase/mozrunner
 --editable ../mozbase/mozscreenshot
 --editable ../mozbase/moztest
 --editable ../mozbase/mozversion
--- a/testing/mozbase/moz.build
+++ b/testing/mozbase/moz.build
@@ -11,16 +11,17 @@ PYTHON_UNITTEST_MANIFESTS += [
     'mozdevice/tests/manifest.ini',
     'mozfile/tests/manifest.ini',
     'mozhttpd/tests/manifest.ini',
     'mozinfo/tests/manifest.ini',
     'mozinstall/tests/manifest.ini',
     'mozleak/tests/manifest.ini',
     'mozlog/tests/manifest.ini',
     'moznetwork/tests/manifest.ini',
+    'mozpower/tests/manifest.ini',
     'mozprocess/tests/manifest.ini',
     'mozprofile/tests/manifest.ini',
     'mozproxy/tests/manifest.ini',
     'mozrunner/tests/manifest.ini',
     'mozsystemmonitor/tests/manifest.ini',
     'moztest/tests/manifest.ini',
     'mozversion/tests/manifest.ini',
 ]
@@ -32,16 +33,17 @@ python_modules = [
     'mozdevice',
     'mozfile',
     'mozhttpd',
     'mozinfo',
     'mozinstall',
     'mozleak',
     'mozlog',
     'moznetwork',
+    'mozpower',
     'mozprocess',
     'mozprofile',
     'mozproxy',
     'mozrunner',
     'mozscreenshot',
     'mozsystemmonitor',
     'moztest',
     'mozversion',
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozpower/mozpower/mozpowerutils.py
@@ -0,0 +1,24 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+from __future__ import absolute_import, unicode_literals
+
+
+def get_logger(logger_name):
+    """Returns the logger that should be used based on logger_name.
+    Defaults to the logging logger if mozlog cannot be imported.
+
+    :returns: mozlog or logging logger object
+    """
+    logger = None
+    try:
+        import mozlog
+        logger = mozlog.get_default_logger(logger_name)
+    except ImportError:
+        pass
+
+    if logger is None:
+        import logging
+        logging.basicConfig()
+        logger = logging.getLogger(logger_name)
+    return logger
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozpower/mozpower/powerbase.py
@@ -0,0 +1,121 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+from __future__ import absolute_import, unicode_literals
+
+import os
+
+from .mozpowerutils import get_logger
+
+
+class IPGExecutableMissingError(Exception):
+    """IPGExecutableMissingError is raised when we cannot find
+    the executable for Intel Power Gadget at the expected location.
+    """
+    pass
+
+
+class PlatformUnsupportedError(Exception):
+    """PlatformUnsupportedError is raised when we cannot find
+    an expected IPG path for the OS being tested.
+    """
+    pass
+
+
+class PowerBase(object):
+    """PowerBase provides an interface for power measurement objects
+    that depend on the os and cpu. When using this class as a base class
+    the `initialize_power_measurements`, `finalize_power_measurements`,
+    and `get_perfherder_data` functions must be implemented, otherwise
+    a NotImplementedError will be raised.
+
+    PowerBase should only be used as the base class for other
+    classes and should not be instantiated directly. To enforce this
+    restriction calling PowerBase's constructor will raise a
+    NonImplementedError exception.
+
+    ::
+
+       from mozpower.powerbase import PowerBase
+
+       try:
+           pb = PowerBase()
+       except NotImplementedError:
+           print "PowerBase cannot be instantiated."
+
+    """
+    def __init__(self, logger_name='mozpower', os=None, cpu=None):
+        """Initializes the PowerBase object.
+
+        :param str logger_name: logging logger name. Defaults to 'mozpower'.
+        :param str os: operating system being tested. Defaults to None.
+        :param str cpu: cpu type being tested (either intel or arm64). Defaults to None.
+        :raises: * NotImplementedError
+        """
+        if self.__class__ == PowerBase:
+            raise NotImplementedError
+
+        self._logger_name = logger_name
+        self._logger = get_logger(logger_name)
+        self._os = os
+        self._cpu = cpu
+        self._ipg_path = self.get_ipg_path()
+
+    @property
+    def ipg_path(self):
+        return self._ipg_path
+
+    @property
+    def logger_name(self):
+        return self._logger_name
+
+    def initialize_power_measurements(self):
+        """Starts power measurement gathering, must be implemented by subclass.
+
+        :raises: * NotImplementedError
+        """
+        raise NotImplementedError
+
+    def finalize_power_measurements(self, test_name='power-testing', **kwargs):
+        """Stops power measurement gathering, must be implemented by subclass.
+
+        :raises: * NotImplementedError
+        """
+        raise NotImplementedError
+
+    def get_perfherder_data(self):
+        """Returns the perfherder data output produced from the tests, must
+        be implemented by subclass.
+
+        :raises: * NotImplementedError
+        """
+        raise NotImplementedError
+
+    def get_ipg_path(self):
+        """Returns the path to where we expect to find Intel Power Gadget
+        depending on the OS being tested. Raises a PlatformUnsupportedError
+        if the OS being tested is unsupported, and raises a IPGExecutableMissingError
+        if the Intel Power Gadget executable cannot be found at the expected
+        location.
+
+        :returns: str
+        :raises: * IPGExecutableMissingError
+                 * PlatformUnsupportedError
+        """
+        if self._cpu != "intel":
+            return None
+
+        if self._os == "darwin":
+            exe_path = "/Applications/Intel Power Gadget/PowerLog"
+        else:
+            raise PlatformUnsupportedError(
+                "%s platform currently not supported for Intel Power Gadget measurements" %
+                self._os
+            )
+        if not os.path.exists(exe_path):
+            raise IPGExecutableMissingError(
+                "Intel Power Gadget is not installed, or cannot be found at the location %s" %
+                exe_path
+            )
+
+        return exe_path
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozpower/setup.cfg
@@ -0,0 +1,2 @@
+[bdist_wheel]
+universal = 1
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozpower/setup.py
@@ -0,0 +1,34 @@
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import absolute_import
+
+from setuptools import setup
+
+PACKAGE_NAME = 'mozpower'
+PACKAGE_VERSION = '1.0.0'
+
+deps = ['mozlog >= 3.0', 'mozdevice >= 3.0.2']
+
+setup(name=PACKAGE_NAME,
+      version=PACKAGE_VERSION,
+      description="Mozilla-authored power usage measurement tools",
+      long_description="see https://firefox-source-docs.mozilla.org/mozbase/index.html",
+      classifiers=['Programming Language :: Python :: 2.7',
+                   'Programming Language :: Python :: 3.5'],
+      # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
+      keywords='',
+      author='Mozilla Performance Test Engineering Team',
+      author_email='tools@lists.mozilla.org',
+      url='https://wiki.mozilla.org/Auto-tools/Projects/Mozbase',
+      license='MPL',
+      packages=['mozpower'],
+      include_package_data=True,
+      zip_safe=False,
+      install_requires=deps,
+      entry_points="""
+      # -*- Entry points: -*-
+      """,
+      )
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozpower/tests/conftest.py
@@ -0,0 +1,15 @@
+from __future__ import absolute_import, print_function
+
+import pytest
+
+from mozpower.powerbase import PowerBase
+
+
+@pytest.fixture(scope='function')
+def powermeasurer():
+    """Returns a testing subclass of the PowerBase class
+    for testing.
+    """
+    class PowerMeasurer(PowerBase):
+        pass
+    return PowerMeasurer()
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozpower/tests/manifest.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+subsuite = mozbase
+[test_powerbase.py]
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozpower/tests/test_powerbase.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python
+
+from __future__ import absolute_import
+
+import mock
+import mozunit
+import pytest
+
+from mozpower.powerbase import (
+    PowerBase,
+    IPGExecutableMissingError,
+    PlatformUnsupportedError
+)
+
+
+def test_powerbase_intialization():
+    """Tests that the PowerBase class correctly raises
+    a NotImplementedError when attempting to instantiate
+    it directly.
+    """
+    with pytest.raises(NotImplementedError):
+        PowerBase()
+
+
+def test_powerbase_missing_methods(powermeasurer):
+    """Tests that trying to call PowerBase methods
+    without an implementation in the subclass raises
+    the NotImplementedError.
+    """
+    with pytest.raises(NotImplementedError):
+        powermeasurer.initialize_power_measurements()
+
+    with pytest.raises(NotImplementedError):
+        powermeasurer.finalize_power_measurements()
+
+    with pytest.raises(NotImplementedError):
+        powermeasurer.get_perfherder_data()
+
+
+def test_powerbase_get_ipg_path_mac(powermeasurer):
+    """Tests that getting the IPG path returns the expected results.
+    """
+    powermeasurer._os = 'darwin'
+    powermeasurer._cpu = 'not-intel'
+
+    def os_side_effect(arg):
+        """Used to get around the os.path.exists check in
+        get_ipg_path which raises an IPGExecutableMissingError
+        otherwise.
+        """
+        return True
+
+    with mock.patch('os.path.exists', return_value=True) as m:
+        m.side_effect = os_side_effect
+
+        # None is returned when a non-intel based machine is
+        # tested.
+        ipg_path = powermeasurer.get_ipg_path()
+        assert ipg_path is None
+
+        # Check the path returned for mac intel-based machines.
+        powermeasurer._cpu = 'intel'
+        ipg_path = powermeasurer.get_ipg_path()
+        assert ipg_path == "/Applications/Intel Power Gadget/PowerLog"
+
+
+def test_powerbase_get_ipg_path_errors(powermeasurer):
+    """Tests that the appropriate error is output when calling
+    get_ipg_path with invalid/unsupported _os and _cpu entries.
+    """
+
+    powermeasurer._cpu = 'intel'
+    powermeasurer._os = 'Not-An-OS'
+
+    def os_side_effect(arg):
+        """Used to force the error IPGExecutableMissingError to occur
+        (in case a machine running these tests is a mac, and has intel
+        power gadget installed).
+        """
+        return False
+
+    with pytest.raises(PlatformUnsupportedError):
+        powermeasurer.get_ipg_path()
+
+    with mock.patch('os.path.exists', return_value=False) as m:
+        m.side_effect = os_side_effect
+
+        powermeasurer._os = 'darwin'
+        with pytest.raises(IPGExecutableMissingError):
+            powermeasurer.get_ipg_path()
+
+
+if __name__ == '__main__':
+    mozunit.main()
--- a/testing/mozbase/packages.txt
+++ b/testing/mozbase/packages.txt
@@ -4,16 +4,17 @@ mozdebug.pth:testing/mozbase/mozdebug
 mozdevice.pth:testing/mozbase/mozdevice
 mozfile.pth:testing/mozbase/mozfile
 mozhttpd.pth:testing/mozbase/mozhttpd
 mozinfo.pth:testing/mozbase/mozinfo
 mozinstall.pth:testing/mozbase/mozinstall
 mozleak.pth:testing/mozbase/mozleak
 mozlog.pth:testing/mozbase/mozlog
 moznetwork.pth:testing/mozbase/moznetwork
+mozpower.pth:testing/mozbase/mozpower
 mozprocess.pth:testing/mozbase/mozprocess
 mozprofile.pth:testing/mozbase/mozprofile
 mozproxy.pth:testing/mozbase/mozproxy
 mozrunner.pth:testing/mozbase/mozrunner
 mozsystemmonitor.pth:testing/mozbase/mozsystemmonitor
 mozscreenshot.pth:testing/mozbase/mozscreenshot
 moztest.pth:testing/mozbase/moztest
 mozversion.pth:testing/mozbase/mozversion
--- a/testing/tools/mach_test_package_bootstrap.py
+++ b/testing/tools/mach_test_package_bootstrap.py
@@ -21,16 +21,17 @@ SEARCH_PATHS = [
     'mozbase/mozdevice',
     'mozbase/mozfile',
     'mozbase/mozhttpd',
     'mozbase/mozinfo',
     'mozbase/mozinstall',
     'mozbase/mozleak',
     'mozbase/mozlog',
     'mozbase/moznetwork',
+    'mozbase/mozpower',
     'mozbase/mozprocess',
     'mozbase/mozprofile',
     'mozbase/mozrunner',
     'mozbase/mozscreenshot',
     'mozbase/mozsystemmonitor',
     'mozbase/moztest',
     'mozbase/mozversion',
     'reftest',