Repository: Azure/WALinuxAgent
Pull request #1553 (2019-06-11 21:36:29)

Preventing incorrect metrics to be sent on telemetry
<!-- DO NOT DELETE THIS TEMPLATE -->

## Description

Issue # <!-- if any -->
<!--
Please add an informative description that covers that changes made by the pull request. 
This checklist is used to make sure that common issues in a pull request are addressed.
This will expedite the process of getting your pull request merged and avoid extra work on your part to fix issues discovered during the review process.
-->

---

### PR information
- [x] The title of the PR is clear and informative.
- [x] There are a small number of commits, each of which has an informative message. This means that previously merged commits do not appear in the history of the PR. For information on cleaning up the commits in your pull request, [see this page](https://github.com/Azure/azure-powershell/blob/master/documentation/development-docs/cleaning-up-commits.md).
- [x] Except for special cases involving multiple contributors, the PR is started from a fork of the main repository, not a branch.
- [x] If applicable, the PR references the bug/issue that it fixes in the description.
- [x] New Unit tests were added for the changes made and Travis.CI is passing.

### Quality of Code and Contribution Guidelines
- [x] I have read the [contribution guidelines](https://github.com/Azure/WALinuxAgent/blob/master/.github/CONTRIBUTING.md).

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/azure/walinuxagent/1553)
<!-- Reviewable:end -->



Patch:
diff --git a/azurelinuxagent/common/cgroup.py b/azurelinuxagent/common/cgroup.py
index 17fe2afd..07913c48 100644
--- a/azurelinuxagent/common/cgroup.py
+++ b/azurelinuxagent/common/cgroup.py
@@ -86,23 +86,31 @@ class CGroup(object):
         except IndexError:
             parameter_filename = self._get_cgroup_file(parameter_name)
             logger.error("File {0} is empty but should not be".format(parameter_filename))
+            raise CGroupsException("File {0} is empty but should not be".format(parameter_filename))
         except CGroupsException:
-            return None
+            raise
         except Exception as e:
             parameter_filename = self._get_cgroup_file(parameter_name)
             logger.error("Exception while attempting to read {0}: {1}".format(parameter_filename, ustr(e)))
+            raise CGroupsException("Exception while attempting to read {0}".format(parameter_filename), e)
         return result
 
     def collect(self):
         raise NotImplementedError()
 
     def is_active(self):
-        tasks = self._get_parameters("tasks")
-        if tasks:
-            return len(tasks) != 0
-        else:
+        try:
+            tasks = self._get_parameters("tasks")
+            if tasks:
+                return len(tasks) != 0
+        except CGroupsException as e:
+            logger.periodic_warn(logger.EVERY_HALF_HOUR,
+                                 'Could not get list of tasks from "tasks" file in the cgroup: {0}.'
+                                 ' Internal error: {1}'.format(self.path, ustr(e)))
             return False
 
+        return False
+
 
 class CpuCgroup(CGroup):
     def __init__(self, name, cgroup_path):
@@ -115,7 +123,14 @@ class CpuCgroup(CGroup):
         super(CpuCgroup, self).__init__(name, cgroup_path, "cpu")
 
         self._osutil = get_osutil()
-        self._current_cpu_total = self._get_current_cpu_total()
+        self._current_cpu_total = 0
+        try:
+            self._current_cpu_total = self._get_current_cpu_total()
+        except CGroupsException as e:
+            logger.periodic_warn(logger.EVERY_HALF_HOUR,
+                                 'Could not get current CPU total usage from cgroup: {0}. '
+                                 'Internal error :{1}'.format(self.path, ustr(e)))
+
         self._previous_cpu_total = 0
         self._current_system_cpu = self._osutil.get_total_cpu_ticks_since_boot()
         self._previous_system_cpu = 0
@@ -132,18 +147,11 @@ class CpuCgroup(CGroup):
         :return: int
         """
         cpu_total = 0
-        try:
-            cpu_stat = self._get_file_contents('cpuacct.stat')
-            if cpu_stat is not None:
-                m = re_user_system_times.match(cpu_stat)
-                if m:
-                    cpu_total = int(m.groups()[0]) + int(m.groups()[1])
-        except CGroupsException:
-            # There are valid reasons for file contents to be unavailable; for example, if an extension
-            # has not yet started (or has stopped) an associated service on a VM using systemd, the cgroup for
-            # the service will not exist ('cause systemd will tear it down). This might be a transient or a
-            # long-lived state, so there's no point in logging it, much less emitting telemetry.
-            pass
+        cpu_stat = self._get_file_contents('cpuacct.stat')
+        if cpu_stat is not None:
+            m = re_user_system_times.match(cpu_stat)
+            if m:
+                cpu_total = int(m.groups()[0]) + int(m.groups()[1])
         return cpu_total
 
     def _update_cpu_data(self):
@@ -202,6 +210,7 @@ class MemoryCgroup(CGroup):
         :rtype: int
         """
         usage = self._get_parameters('memory.usage_in_bytes', first_line_only=True)
+
         if not usage:
             usage = "0"
         return int(usage)
diff --git a/azurelinuxagent/common/cgroupstelemetry.py b/azurelinuxagent/common/cgroupstelemetry.py
index b5e915d0..e680a6e2 100644
--- a/azurelinuxagent/common/cgroupstelemetry.py
+++ b/azurelinuxagent/common/cgroupstelemetry.py
@@ -17,6 +17,7 @@ import threading
 from datetime import datetime as dt
 
 from azurelinuxagent.common import logger
+from azurelinuxagent.common.future import ustr
 
 
 class CGroupsTelemetry(object):
@@ -34,15 +35,24 @@ class CGroupsTelemetry(object):
     @staticmethod
     def _process_cgroup_metric(cgroup_metrics):
         current_memory_usage, max_memory_levels, current_cpu_usage = cgroup_metrics.get_metrics()
-        processed_extension = {
-            "memory": {
-                "cur_mem": CGroupsTelemetry._get_metrics_list(current_memory_usage),
-                "max_mem": CGroupsTelemetry._get_metrics_list(max_memory_levels)
-            },
-            "cpu": {
-                "cur_cpu": CGroupsTelemetry._get_metrics_list(current_cpu_usage)
-            }
-        }
+
+        processed_extension = {}
+
+        if current_cpu_usage.count() > 0:
+            processed_extension["cpu"] = {"cur_cpu": CGroupsTelemetry._get_metrics_list(current_cpu_usage)}
+
+        if current_memory_usage.count() > 0:
+            if "memory" in processed_extension:
+                processed_extension["memory"]["cur_mem"] = CGroupsTelemetry._get_metrics_list(current_memory_usage)
+            else:
+                processed_extension["memory"] = {"cur_mem": CGroupsTelemetry._get_metrics_list(current_memory_usage)}
+
+        if max_memory_levels.count() > 0:
+            if "memory" in processed_extension:
+                processed_extension["memory"]["max_mem"] = CGroupsTelemetry._get_metrics_list(max_memory_levels)
+            else:
+                processed_extension["memory"] = {"max_mem": CGroupsTelemetry._get_metrics_list(max_memory_levels)}
+
         return processed_extension
 
     @staticmethod
@@ -90,15 +100,28 @@ class CGroupsTelemetry(object):
                     CGroupsTelemetry._cgroup_metrics[key].marked_for_delete]:
             del CGroupsTelemetry._cgroup_metrics[key]
 
+        logger.reset_periodic()
+
         return collected_metrics
 
     @staticmethod
     def poll_all_tracked():
         with CGroupsTelemetry._rlock:
             for cgroup in CGroupsTelemetry._tracked[:]:
+
                 if cgroup.name not in CGroupsTelemetry._cgroup_metrics:
                     CGroupsTelemetry._cgroup_metrics[cgroup.name] = CgroupMetrics()
-                CGroupsTelemetry._cgroup_metrics[cgroup.name].add_new_data(cgroup.controller, cgroup.collect())
+
+                metric = None
+                # noinspection PyBroadException
+                try:
+                    metric = cgroup.collect()
+                except Exception as e:
+                    logger.periodic_warn(logger.EVERY_HALF_HOUR,
+                                         'Could not collect the cgroup metrics for cgroup path {0}. '
+                                         'Internal error : {1}'.format(cgroup.path, ustr(e)))
+                if metric:
+                    CGroupsTelemetry._cgroup_metrics[cgroup.name].add_new_data(cgroup.controller, metric)
 
                 if not cgroup.is_active():
                     CGroupsTelemetry.stop_tracking(cgroup)
@@ -115,6 +138,7 @@ class CGroupsTelemetry(object):
     def cleanup():
         with CGroupsTelemetry._rlock:
             CGroupsTelemetry._tracked *= 0  # emptying the list
+            CGroupsTelemetry._cgroup_metrics = {}
 
 
 class CgroupMetrics(object):
@@ -132,10 +156,11 @@ class CgroupMetrics(object):
         self._current_cpu_usage.append(metric[0].value)
 
     def add_new_data(self, controller, metric):
-        if controller is "cpu":
-            self._add_cpu_usage(metric)
-        elif controller is "memory":
-            self._add_memory_usage(metric)
+        if metric:
+            if controller is "cpu":
+                self._add_cpu_usage(metric)
+            elif controller is "memory":
+                self._add_memory_usage(metric)
 
     def get_metrics(self):
         return self._current_memory_usage, self._max_memory_levels, self._current_cpu_usage
diff --git a/azurelinuxagent/common/event.py b/azurelinuxagent/common/event.py
index 82e2ad9f..583d6400 100644
--- a/azurelinuxagent/common/event.py
+++ b/azurelinuxagent/common/event.py
@@ -22,12 +22,10 @@ import os
 import sys
 import time
 import traceback
-
 from datetime import datetime
 
 import azurelinuxagent.common.conf as conf
 import azurelinuxagent.common.logger as logger
-
 from azurelinuxagent.common.exception import EventError
 from azurelinuxagent.common.future import ustr
 from azurelinuxagent.common.protocol.restapi import TelemetryEventParam, \
@@ -236,17 +234,17 @@ class EventLogger(object):
             (self.periodic_events[h] + delta) <= datetime.now()
 
     def add_periodic(self,
-        delta, name, op=WALAEventOperation.Unknown, is_success=True, duration=0,
-        version=CURRENT_VERSION, message="", evt_type="",
-        is_internal=False, log_event=True, force=False):
+                     delta, name, op=WALAEventOperation.Unknown, is_success=True, duration=0,
+                     version=CURRENT_VERSION, message="", evt_type="",
+                     is_internal=False, log_event=True, force=False):
+
+        h = hash(name + op + ustr(is_success) + message)
 
-        h = hash(name+op+ustr(is_success)+message)
-        
         if force or self.is_period_elapsed(delta, h):
             self.add_event(name,
-                op=op, is_success=is_success, duration=duration,
-                version=version, message=message, evt_type=evt_type,
-                is_internal=is_internal, log_event=log_event)
+                           op=op, is_success=is_success, duration=duration,
+                           version=version, message=message, evt_type=evt_type,
+                           is_internal=is_internal, log_event=log_event)
             self.periodic_events[h] = datetime.now()
 
     def add_event(self,
diff --git a/azurelinuxagent/common/exception.py b/azurelinuxagent/common/exception.py
index 672cf543..9a3c2c6f 100644
--- a/azurelinuxagent/common/exception.py
+++ b/azurelinuxagent/common/exception.py
@@ -20,13 +20,6 @@
 Defines all exceptions
 """
 
-class CGroupsException(Exception):
-
-    def __init__(self, msg):
-        self.msg = msg
-
-    def __str__(self):
-        return repr(self.msg)
 
 class AgentError(Exception):
     """
@@ -58,6 +51,12 @@ class AgentNetworkError(AgentError):
         super(AgentNetworkError, self).__init__(msg, inner)
 
 
+class CGroupsException(AgentError):
+
+    def __init__(self, msg, inner=None):
+        super(AgentError, self).__init__(msg, inner)
+
+
 class ExtensionError(AgentError):
     """
     When failed to execute an extension
diff --git a/azurelinuxagent/common/logger.py b/azurelinuxagent/common/logger.py
index 92fc550e..b52f5fd6 100644
--- a/azurelinuxagent/common/logger.py
+++ b/azurelinuxagent/common/logger.py
@@ -49,12 +49,24 @@ class Logger(object):
         return h not in self.logger.periodic_messages or \
             (self.logger.periodic_messages[h] + delta) <= datetime.now()
 
-    def periodic(self, delta, msg_format, *args):
+    def _periodic(self, delta, log_level_op, msg_format, *args):
         h = hash(msg_format)
         if self.is_period_elapsed(delta, h):
-            self.info(msg_format, *args)
+            log_level_op(msg_format, *args)
             self.logger.periodic_messages[h] = datetime.now()
 
+    def periodic_info(self, delta, msg_format, *args):
+        self._periodic(delta, self.info, msg_format, *args)
+
+    def periodic_verbose(self, delta, msg_format, *args):
+        self._periodic(delta, self.verbose, msg_format, *args)
+
+    def periodic_warn(self, delta, msg_format, *args):
+        self._periodic(delta, self.warn, msg_format, *args)
+
+    def periodic_error(self, delta, msg_format, *args):
+        self._periodic(delta, self.error, msg_format, *args)
+
     def verbose(self, msg_format, *args):
         self.log(LogLevel.VERBOSE, msg_format, *args)
 
@@ -150,7 +162,7 @@ class TelemetryAppender(object):
                 pass
 
 
-#Initialize logger instance
+# Initialize logger instance
 DEFAULT_LOGGER = Logger()
 
 
@@ -181,11 +193,25 @@ def add_logger_appender(appender_type, level=LogLevel.INFO, path=None):
 def reset_periodic():
     DEFAULT_LOGGER.reset_periodic()
 
+
 def set_prefix(prefix):
     DEFAULT_LOGGER.set_prefix(prefix)
 
-def periodic(delta, msg_format, *args):
-    DEFAULT_LOGGER.periodic(delta, msg_format, *args)
+
+def periodic_info(delta, msg_format, *args):
+    DEFAULT_LOGGER.periodic_info(delta, msg_format, *args)
+
+
+def periodic_verbose(delta, msg_format, *args):
+    DEFAULT_LOGGER.periodic_verbose(delta, msg_format, *args)
+
+
+def periodic_error(delta, msg_format, *args):
+    DEFAULT_LOGGER.periodic_error(delta, msg_format, *args)
+
+
+def periodic_warn(delta, msg_format, *args):
+    DEFAULT_LOGGER.periodic_warn(delta, msg_format, *args)
 
 
 def verbose(msg_format, *args):
diff --git a/azurelinuxagent/common/osutil/default.py b/azurelinuxagent/common/osutil/default.py
index f504a811..7cc2be87 100644
--- a/azurelinuxagent/common/osutil/default.py
+++ b/azurelinuxagent/common/osutil/default.py
@@ -1270,6 +1270,7 @@ class DefaultOSUtil(object):
             results = fileutil.read_file('/proc/stat')
         except (OSError, IOError) as ex:
             logger.warn("Couldn't read /proc/stat: {0}".format(ex.strerror))
+            raise
 
         return results
 


Test patch:
diff --git a/tests/common/test_cgroupstelemetry.py b/tests/common/test_cgroupstelemetry.py
index 02c1d281..6a7de45a 100644
--- a/tests/common/test_cgroupstelemetry.py
+++ b/tests/common/test_cgroupstelemetry.py
@@ -76,6 +76,10 @@ class TestCGroupsTelemetry(AgentTestCase):
         AgentTestCase.setUp(self)
         CGroupsTelemetry.cleanup()
 
+    def tearDown(self):
+        AgentTestCase.tearDown(self)
+        CGroupsTelemetry.cleanup()
+
     @patch("azurelinuxagent.common.cgroup.CpuCgroup._get_current_cpu_total")
     @patch("azurelinuxagent.common.osutil.default.DefaultOSUtil.get_total_cpu_ticks_since_boot")
     def test_telemetry_polling_with_active_cgroups(self, *args):
@@ -125,43 +129,6 @@ class TestCGroupsTelemetry(AgentTestCase):
                             self.assertEqual(len(max_memory_levels._data), 0)
                             self.assertEqual(len(current_cpu_usage._data), 0)
 
-        with patch("azurelinuxagent.common.cgroup.MemoryCgroup._get_memory_max_usage") as patch_get_memory_max_usage:
-            with patch("azurelinuxagent.common.cgroup.MemoryCgroup._get_memory_usage") as patch_get_memory_usage:
-                with patch("azurelinuxagent.common.cgroup.CpuCgroup._get_cpu_percent") as patch_get_cpu_percent:
-                    with patch("azurelinuxagent.common.cgroup.CGroup.is_active") as patch_is_active:
-                        patch_is_active.return_value = True
-
-                        current_cpu = 30
-                        current_memory = 209715200
-                        current_max_memory = 471859200
-
-                        patch_get_cpu_percent.return_value = current_cpu
-                        patch_get_memory_usage.return_value = current_memory  # example 200 MB
-                        patch_get_memory_max_usage.return_value = current_max_memory  # example 450 MB
-
-                        poll_count = 1
-
-                        for data_count in range(poll_count, 10):
-                            CGroupsTelemetry.poll_all_tracked()
-                            self.assertEqual(CGroupsTelemetry._cgroup_metrics.__len__(), num_extensions)
-                            for cgroup_name, cgroup_metric in CGroupsTelemetry._cgroup_metrics.items():
-                                current_memory_usage, max_memory_levels, current_cpu_usage = cgroup_metric.get_metrics()
-                                self.assertEqual(len(current_memory_usage._data), data_count)
-                                self.assertListEqual(current_memory_usage._data, [current_memory] * data_count)
-                                self.assertEqual(len(max_memory_levels._data), data_count)
-                                self.assertListEqual(max_memory_levels._data, [current_max_memory] * data_count)
-                                self.assertEqual(len(current_cpu_usage._data), data_count)
-                                self.assertListEqual(current_cpu_usage._data, [current_cpu] * data_count)
-
-                        CGroupsTelemetry.report_all_tracked()
-
-                        self.assertEqual(CGroupsTelemetry._cgroup_metrics.__len__(), num_extensions)
-                        for cgroup_name, cgroup_metric in CGroupsTelemetry._cgroup_metrics.items():
-                            current_memory_usage, max_memory_levels, current_cpu_usage = cgroup_metric.get_metrics()
-                            self.assertEqual(len(current_memory_usage._data), 0)
-                            self.assertEqual(len(max_memory_levels._data), 0)
-                            self.assertEqual(len(current_cpu_usage._data), 0)
-
     @patch("azurelinuxagent.common.cgroup.CpuCgroup._get_current_cpu_total")
     @patch("azurelinuxagent.common.osutil.default.DefaultOSUtil.get_total_cpu_ticks_since_boot")
     def test_telemetry_polling_with_inactive_cgroups(self, *args):
@@ -204,6 +171,7 @@ class TestCGroupsTelemetry(AgentTestCase):
 
                         for cgroup_name, cgroup_metric in CGroupsTelemetry._cgroup_metrics.items():
                             current_memory_usage, max_memory_levels, current_cpu_usage = cgroup_metric.get_metrics()
+
                             self.assertEqual(len(current_memory_usage._data), data_count)
                             self.assertListEqual(current_memory_usage._data, [current_memory] * data_count)
                             self.assertEqual(len(max_memory_levels._data), data_count)
@@ -393,7 +361,8 @@ class TestCGroupsTelemetry(AgentTestCase):
             dummy_cpu_cgroup = CGroup.create("dummy_cpu_path_{0}".format(i), "cpu", "dummy_extension_{0}".format(i))
             CGroupsTelemetry.track_cgroup(dummy_cpu_cgroup)
 
-            dummy_memory_cgroup = CGroup.create("dummy_memory_path_{0}".format(i), "memory", "dummy_extension_{0}".format(i))
+            dummy_memory_cgroup = CGroup.create("dummy_memory_path_{0}".format(i), "memory", "dummy_extension_{0}".
+                                                format(i))
             CGroupsTelemetry.track_cgroup(dummy_memory_cgroup)
 
         for i in range(num_extensions):
@@ -403,6 +372,130 @@ class TestCGroupsTelemetry(AgentTestCase):
         self.assertFalse(CGroupsTelemetry.is_tracked("not_present_cpu_dummy_path"))
         self.assertFalse(CGroupsTelemetry.is_tracked("not_present_memory_dummy_path"))
 
+    def test_process_cgroup_metric_with_incorrect_cgroups_mounted(self):
+        num_extensions = 5
+        for i in range(num_extensions):
+            dummy_cpu_cgroup = CGroup.create("dummy_cpu_path_{0}".format(i), "cpu", "dummy_extension_{0}".format(i))
+            CGroupsTelemetry.track_cgroup(dummy_cpu_cgroup)
+
+            dummy_memory_cgroup = CGroup.create("dummy_memory_path_{0}".format(i), "memory",
+                                                "dummy_extension_{0}".format(i))
+            CGroupsTelemetry.track_cgroup(dummy_memory_cgroup)
+
+        with patch("azurelinuxagent.common.cgroup.CpuCgroup.collect") as patch_cpu_cgroup_collect:
+            with patch("azurelinuxagent.common.cgroup.MemoryCgroup.collect") as patch_memory_cgroup_collect:
+                patch_cpu_cgroup_collect.side_effect = Exception("File not found")
+                patch_memory_cgroup_collect.side_effect = Exception("File not found")
+
+                for data_count in range(1, 10):
+                    CGroupsTelemetry.poll_all_tracked()
+
+                self.assertEqual(CGroupsTelemetry._cgroup_metrics.__len__(), num_extensions)
+
+                collected_metrics = {}
+                for name, cgroup_metrics in CGroupsTelemetry._cgroup_metrics.items():
+                    collected_metrics[name] = CGroupsTelemetry._process_cgroup_metric(cgroup_metrics)
+                    self.assertEqual(collected_metrics[name], {})  #empty
+
+    @patch("azurelinuxagent.common.cgroup.CpuCgroup._get_current_cpu_total")
+    @patch("azurelinuxagent.common.osutil.default.DefaultOSUtil.get_total_cpu_ticks_since_boot")
+    def test_process_cgroup_metric_with_no_memory_cgroup_mounted(self, *args):
+        num_extensions = 5
+
+        for i in range(num_extensions):
+            dummy_cpu_cgroup = CGroup.create("dummy_cpu_path_{0}".format(i), "cpu", "dummy_extension_{0}".format(i))
+            CGroupsTelemetry.track_cgroup(dummy_cpu_cgroup)
+
+            dummy_memory_cgroup = CGroup.create("dummy_memory_path_{0}".format(i), "memory",
+                                                "dummy_extension_{0}".format(i))
+            CGroupsTelemetry.track_cgroup(dummy_memory_cgroup)
+
+        with patch("azurelinuxagent.common.cgroup.CpuCgroup._get_cpu_percent") as patch_get_cpu_percent:
+            with patch("azurelinuxagent.common.cgroup.MemoryCgroup.collect") as patch_memory_cgroup_collect:
+                with patch("azurelinuxagent.common.cgroup.CGroup.is_active") as patch_is_active:
+                    patch_is_active.return_value = True
+                    patch_memory_cgroup_collect.side_effect = Exception("File not found")
+
+                    current_cpu = 30
+                    patch_get_cpu_percent.return_value = current_cpu
+
+                    poll_count = 1
+
+                    for data_count in range(poll_count, 10):
+                        CGroupsTelemetry.poll_all_tracked()
+
+                        self.assertEqual(CGroupsTelemetry._cgroup_metrics.__len__(), num_extensions)
+                        for cgroup_name, cgroup_metric in CGroupsTelemetry._cgroup_metrics.items():
+                            current_memory_usage, max_memory_levels, current_cpu_usage = cgroup_metric.get_metrics()
+
+                            self.assertEqual(len(current_memory_usage._data), 0)
+                            self.assertListEqual(current_memory_usage._data, [])
+                            self.assertEqual(len(max_memory_levels._data), 0)
+                            self.assertListEqual(max_memory_levels._data, [])
+
+                            self.assertEqual(len(current_cpu_usage._data), data_count)
+                            self.assertListEqual(current_cpu_usage._data, [current_cpu] * data_count)
+
+                    CGroupsTelemetry.report_all_tracked()
+
+                    self.assertEqual(CGroupsTelemetry._cgroup_metrics.__len__(), num_extensions)
+                    for cgroup_name, cgroup_metric in CGroupsTelemetry._cgroup_metrics.items():
+                        current_memory_usage, max_memory_levels, current_cpu_usage = cgroup_metric.get_metrics()
+                        self.assertEqual(len(current_memory_usage._data), 0)
+                        self.assertEqual(len(max_memory_levels._data), 0)
+                        self.assertEqual(len(current_cpu_usage._data), 0)
+
+    @patch("azurelinuxagent.common.cgroup.CpuCgroup._get_current_cpu_total")
+    @patch("azurelinuxagent.common.osutil.default.DefaultOSUtil.get_total_cpu_ticks_since_boot")
+    def test_process_cgroup_metric_with_no_cpu_cgroup_mounted(self, *args):
+        num_extensions = 5
+        for i in range(num_extensions):
+            dummy_cpu_cgroup = CGroup.create("dummy_cpu_path_{0}".format(i), "cpu", "dummy_extension_{0}".format(i))
+            CGroupsTelemetry.track_cgroup(dummy_cpu_cgroup)
+
+            dummy_memory_cgroup = CGroup.create("dummy_memory_path_{0}".format(i), "memory",
+                                                "dummy_extension_{0}".format(i))
+            CGroupsTelemetry.track_cgroup(dummy_memory_cgroup)
+
+        with patch("azurelinuxagent.common.cgroup.MemoryCgroup._get_memory_max_usage") as patch_get_memory_max_usage:
+            with patch("azurelinuxagent.common.cgroup.MemoryCgroup._get_memory_usage") as patch_get_memory_usage:
+                with patch("azurelinuxagent.common.cgroup.CpuCgroup.collect") as patch_cpu_cgroup_collect:
+                    with patch("azurelinuxagent.common.cgroup.CGroup.is_active") as patch_is_active:
+                        patch_is_active.return_value = True
+
+                        patch_cpu_cgroup_collect.side_effect = Exception("File not found")
+
+                        current_memory = 209715200
+                        current_max_memory = 471859200
+
+                        patch_get_memory_usage.return_value = current_memory  # example 200 MB
+                        patch_get_memory_max_usage.return_value = current_max_memory  # example 450 MB
+
+                        poll_count = 1
+
+                        for data_count in range(poll_count, 10):
+                            CGroupsTelemetry.poll_all_tracked()
+                            self.assertEqual(CGroupsTelemetry._cgroup_metrics.__len__(), num_extensions)
+
+                            for cgroup_name, cgroup_metric in CGroupsTelemetry._cgroup_metrics.items():
+                                current_memory_usage, max_memory_levels, current_cpu_usage = cgroup_metric.get_metrics()
+                                self.assertEqual(len(current_memory_usage._data), data_count)
+                                self.assertListEqual(current_memory_usage._data, [current_memory] * data_count)
+                                self.assertEqual(len(max_memory_levels._data), data_count)
+                                self.assertListEqual(max_memory_levels._data, [current_max_memory] * data_count)
+
+                                self.assertEqual(len(current_cpu_usage._data), 0)
+                                self.assertListEqual(current_cpu_usage._data, [])
+
+                        CGroupsTelemetry.report_all_tracked()
+
+                        self.assertEqual(CGroupsTelemetry._cgroup_metrics.__len__(), num_extensions)
+                        for cgroup_name, cgroup_metric in CGroupsTelemetry._cgroup_metrics.items():
+                            current_memory_usage, max_memory_levels, current_cpu_usage = cgroup_metric.get_metrics()
+                            self.assertEqual(len(current_memory_usage._data), 0)
+                            self.assertEqual(len(max_memory_levels._data), 0)
+                            self.assertEqual(len(current_cpu_usage._data), 0)
+
     @skip_if_predicate_false(i_am_root, "This test will only run as root")
     @skip_if_predicate_false(CGroupConfigurator.get_instance().enabled, "Does not run when Cgroups are not enabled")
     def test_telemetry_with_tracked_cgroup(self):
diff --git a/tests/common/test_logger.py b/tests/common/test_logger.py
index e28d385e..63505259 100644
--- a/tests/common/test_logger.py
+++ b/tests/common/test_logger.py
@@ -24,49 +24,127 @@ from azurelinuxagent.common.version import CURRENT_AGENT, CURRENT_VERSION
 
 from tests.tools import *
 
-_MSG = "This is our test logging message {0} {1}"
+_MSG_INFO = "This is our test info logging message {0} {1}"
+_MSG_WARN = "This is our test warn logging message {0} {1}"
+_MSG_ERROR = "This is our test error logging message {0} {1}"
+_MSG_VERBOSE = "This is our test verbose logging message {0} {1}"
 _DATA = ["arg1", "arg2"]
 
 
 class TestLogger(AgentTestCase):
-    @patch('azurelinuxagent.common.logger.Logger.info')
-    def test_periodic_emits_if_not_previously_sent(self, mock_info):
+    def setUp(self):
+        AgentTestCase.setUp(self)
         logger.reset_periodic()
 
-        logger.periodic(logger.EVERY_DAY, _MSG, *_DATA)
-        self.assertEqual(1, mock_info.call_count)
-
-    @patch('azurelinuxagent.common.logger.Logger.info')
-    def test_periodic_does_not_emit_if_previously_sent(self, mock_info):
+    def tearDown(self):
+        AgentTestCase.tearDown(self)
         logger.reset_periodic()
 
-        logger.periodic(logger.EVERY_DAY, _MSG, *_DATA)
+    @patch('azurelinuxagent.common.logger.Logger.verbose')
+    @patch('azurelinuxagent.common.logger.Logger.warn')
+    @patch('azurelinuxagent.common.logger.Logger.error')
+    @patch('azurelinuxagent.common.logger.Logger.info')
+    def test_periodic_emits_if_not_previously_sent(self, mock_info, mock_error, mock_warn, mock_verbose):
+        logger.periodic_info(logger.EVERY_DAY, _MSG_INFO, logger.LogLevel.INFO, *_DATA)
         self.assertEqual(1, mock_info.call_count)
 
-        logger.periodic(logger.EVERY_DAY, _MSG, *_DATA)
-        self.assertEqual(1, mock_info.call_count)
+        logger.periodic_error(logger.EVERY_DAY, _MSG_ERROR, logger.LogLevel.ERROR, *_DATA)
+        self.assertEqual(1, mock_error.call_count)
+
+        logger.periodic_warn(logger.EVERY_DAY, _MSG_WARN, logger.LogLevel.WARNING, *_DATA)
+        self.assertEqual(1, mock_warn.call_count)
 
+        logger.periodic_verbose(logger.EVERY_DAY, _MSG_VERBOSE, logger.LogLevel.VERBOSE, *_DATA)
+        self.assertEqual(1, mock_verbose.call_count)
+
+    @patch('azurelinuxagent.common.logger.Logger.verbose')
+    @patch('azurelinuxagent.common.logger.Logger.warn')
+    @patch('azurelinuxagent.common.logger.Logger.error')
     @patch('azurelinuxagent.common.logger.Logger.info')
-    def test_periodic_emits_after_elapsed_delta(self, mock_info):
-        logger.reset_periodic()
+    def test_periodic_does_not_emit_if_previously_sent(self, mock_info, mock_error, mock_warn, mock_verbose):
+        # The count does not increase from 1 - the first time it sends the data.
+        logger.periodic_info(logger.EVERY_DAY, _MSG_INFO, *_DATA)
+        self.assertEqual(1, mock_info.call_count)
+        logger.periodic_info(logger.EVERY_DAY, _MSG_INFO, *_DATA)
+        self.assertEqual(1, mock_info.call_count)
+
+        logger.periodic_warn(logger.EVERY_DAY, _MSG_WARN, *_DATA)
+        self.assertEqual(1, mock_warn.call_count)
+        logger.periodic_warn(logger.EVERY_DAY, _MSG_WARN, *_DATA)
+        self.assertEqual(1, mock_warn.call_count)
+
+        logger.periodic_error(logger.EVERY_DAY, _MSG_ERROR, *_DATA)
+        self.assertEqual(1, mock_error.call_count)
+        logger.periodic_error(logger.EVERY_DAY, _MSG_ERROR, *_DATA)
+        self.assertEqual(1, mock_error.call_count)
+
+        logger.periodic_verbose(logger.EVERY_DAY, _MSG_VERBOSE, *_DATA)
+        self.assertEqual(1, mock_verbose.call_count)
+        logger.periodic_verbose(logger.EVERY_DAY, _MSG_VERBOSE, *_DATA)
+        self.assertEqual(1, mock_verbose.call_count)
 
-        logger.periodic(logger.EVERY_DAY, _MSG, *_DATA)
+    @patch('azurelinuxagent.common.logger.Logger.verbose')
+    @patch('azurelinuxagent.common.logger.Logger.warn')
+    @patch('azurelinuxagent.common.logger.Logger.error')
+    @patch('azurelinuxagent.common.logger.Logger.info')
+    def test_periodic_emits_after_elapsed_delta(self, mock_info, mock_error, mock_warn, mock_verbose):
+        logger.periodic_info(logger.EVERY_DAY, _MSG_INFO, *_DATA)
         self.assertEqual(1, mock_info.call_count)
 
-        logger.periodic(logger.EVERY_DAY, _MSG, *_DATA)
+        logger.periodic_info(logger.EVERY_DAY, _MSG_INFO, *_DATA)
         self.assertEqual(1, mock_info.call_count)
 
-        logger.DEFAULT_LOGGER.periodic_messages[hash(_MSG)] = \
-            datetime.now() - logger.EVERY_DAY - logger.EVERY_HOUR
-        logger.periodic(logger.EVERY_DAY, _MSG, *_DATA)
+        logger.DEFAULT_LOGGER.periodic_messages[hash(_MSG_INFO)] = datetime.now() - \
+                                                                   logger.EVERY_DAY - logger.EVERY_HOUR
+        logger.periodic_info(logger.EVERY_DAY, _MSG_INFO, *_DATA)
+        self.assertEqual(2, mock_info.call_count)
+
+        logger.periodic_warn(logger.EVERY_DAY, _MSG_WARN, *_DATA)
+        self.assertEqual(1, mock_warn.call_count)
+        logger.periodic_warn(logger.EVERY_DAY, _MSG_WARN, *_DATA)
+        self.assertEqual(1, mock_warn.call_count)
+
+        logger.DEFAULT_LOGGER.periodic_messages[hash(_MSG_WARN)] = datetime.now() - \
+                                                                   logger.EVERY_DAY - logger.EVERY_HOUR
+        logger.periodic_warn(logger.EVERY_DAY, _MSG_WARN, *_DATA)
+        self.assertEqual(2, mock_info.call_count)
+
+        logger.periodic_error(logger.EVERY_DAY, _MSG_ERROR, *_DATA)
+        self.assertEqual(1, mock_error.call_count)
+        logger.periodic_error(logger.EVERY_DAY, _MSG_ERROR, *_DATA)
+        self.assertEqual(1, mock_error.call_count)
+
+        logger.DEFAULT_LOGGER.periodic_messages[hash(_MSG_ERROR)] = datetime.now() - \
+                                                                    logger.EVERY_DAY - logger.EVERY_HOUR
+        logger.periodic_error(logger.EVERY_DAY, _MSG_ERROR, *_DATA)
         self.assertEqual(2, mock_info.call_count)
 
+        logger.periodic_verbose(logger.EVERY_DAY, _MSG_VERBOSE, *_DATA)
+        self.assertEqual(1, mock_verbose.call_count)
+        logger.periodic_verbose(logger.EVERY_DAY, _MSG_VERBOSE, *_DATA)
+        self.assertEqual(1, mock_verbose.call_count)
+
+        logger.DEFAULT_LOGGER.periodic_messages[hash(_MSG_VERBOSE)] = datetime.now() - \
+                                                                      logger.EVERY_DAY - logger.EVERY_HOUR
+        logger.periodic_verbose(logger.EVERY_DAY, _MSG_VERBOSE, *_DATA)
+        self.assertEqual(2, mock_info.call_count)
+
+    @patch('azurelinuxagent.common.logger.Logger.verbose')
+    @patch('azurelinuxagent.common.logger.Logger.warn')
+    @patch('azurelinuxagent.common.logger.Logger.error')
     @patch('azurelinuxagent.common.logger.Logger.info')
-    def test_periodic_forwards_message_and_args(self, mock_info):
-        logger.reset_periodic()
+    def test_periodic_forwards_message_and_args(self, mock_info, mock_error, mock_warn, mock_verbose):
+        logger.periodic_info(logger.EVERY_DAY, _MSG_INFO, *_DATA)
+        mock_info.assert_called_once_with(_MSG_INFO, *_DATA)
+
+        logger.periodic_error(logger.EVERY_DAY, _MSG_ERROR, *_DATA)
+        mock_error.assert_called_once_with(_MSG_ERROR, *_DATA)
+
+        logger.periodic_warn(logger.EVERY_DAY, _MSG_WARN, *_DATA)
+        mock_warn.assert_called_once_with(_MSG_WARN, *_DATA)
 
-        logger.periodic(logger.EVERY_DAY, _MSG, *_DATA)
-        mock_info.assert_called_once_with(_MSG, *_DATA)
+        logger.periodic_verbose(logger.EVERY_DAY, _MSG_VERBOSE, *_DATA)
+        mock_verbose.assert_called_once_with(_MSG_VERBOSE, *_DATA)
 
     def test_telemetry_logger(self):
         mock = MagicMock()
diff --git a/tests/ga/test_monitor.py b/tests/ga/test_monitor.py
index 91398f18..4595fae5 100644
--- a/tests/ga/test_monitor.py
+++ b/tests/ga/test_monitor.py
@@ -347,13 +347,14 @@ class TestExtensionMetricsDataTelemetry(AgentTestCase):
         with patch("azurelinuxagent.common.cgroup.MemoryCgroup._get_memory_max_usage") as patch_get_memory_max_usage:
             with patch("azurelinuxagent.common.cgroup.MemoryCgroup._get_memory_usage") as patch_get_memory_usage:
                 with patch("azurelinuxagent.common.cgroup.CpuCgroup._get_cpu_percent") as patch_get_cpu_percent:
-                    with patch("azurelinuxagent.common.cgroup.CGroup.is_active") as patch_is_active:
-                        for i in range(num_polls):
-                            patch_is_active.return_value = True
-                            patch_get_cpu_percent.return_value = cpu_percent_values[i]
-                            patch_get_memory_usage.return_value = memory_usage_values[i]  # example 200 MB
-                            patch_get_memory_max_usage.return_value = max_memory_usage_values[i]  # example 450 MB
-                            CGroupsTelemetry.poll_all_tracked()
+                    with patch("azurelinuxagent.common.cgroup.CpuCgroup._update_cpu_data") as patch_update_cpu_data:
+                        with patch("azurelinuxagent.common.cgroup.CGroup.is_active") as patch_is_active:
+                            for i in range(num_polls):
+                                patch_is_active.return_value = True
+                                patch_get_cpu_percent.return_value = cpu_percent_values[i]
+                                patch_get_memory_usage.return_value = memory_usage_values[i]  # example 200 MB
+                                patch_get_memory_max_usage.return_value = max_memory_usage_values[i]  # example 450 MB
+                                CGroupsTelemetry.poll_all_tracked()
 
         performance_metrics = CGroupsTelemetry.report_all_tracked()