# Copyright 2018 Microsoft Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Requires Python 2.6+ and Openssl 1.0+
import errno
import threading

from azurelinuxagent.common import logger
from azurelinuxagent.ga.cpucontroller import _CpuController
from azurelinuxagent.common.future import ustr


class CGroupsTelemetry(object):
    """
    """
    _tracked = {}
    _rlock = threading.RLock()

    @staticmethod
    def _get_tracking_id(cgroup_controller):
        controller_type = cgroup_controller.get_controller_type()
        # Since the path is same for all controllers in v2, we need to differentiate to track them separately
        tracking_id = "{0}:{1}".format(controller_type, cgroup_controller.path)
        return tracking_id

    @staticmethod
    def track_cgroup_controller(cgroup_controller):
        """
        Adds the given item to the dictionary of tracked cgroup controllers
        """
        if isinstance(cgroup_controller, _CpuController):
            # set the current cpu usage
            cgroup_controller.initialize_cpu_usage()

        with CGroupsTelemetry._rlock:
            tracking_id = CGroupsTelemetry._get_tracking_id(cgroup_controller)
            if not CGroupsTelemetry.is_tracked(tracking_id):
                CGroupsTelemetry._tracked[tracking_id] = cgroup_controller
                logger.info("Started tracking {0} cgroup {1}", cgroup_controller.get_controller_type(), cgroup_controller)

    @staticmethod
    def is_tracked(path):
        """
        Returns true if the given item is in the list of tracked items
        O(1) operation.
        """
        with CGroupsTelemetry._rlock:
            if path in CGroupsTelemetry._tracked:
                return True

        return False

    @staticmethod
    def stop_tracking(cgroup):
        """
        Stop tracking the cgroups for the given path
        """
        with CGroupsTelemetry._rlock:
            tracking_id = CGroupsTelemetry._get_tracking_id(cgroup)
            if tracking_id in CGroupsTelemetry._tracked:
                CGroupsTelemetry._tracked.pop(tracking_id)
                logger.info("Stopped tracking {0} cgroup {1}", cgroup.get_controller_type(), cgroup)

    @staticmethod
    def poll_all_tracked():
        metrics = []
        inactive_controllers = []
        with CGroupsTelemetry._rlock:
            for controller in CGroupsTelemetry._tracked.values():
                try:
                    metrics.extend(controller.get_tracked_metrics())
                except Exception as e:
                    # There can be scenarios when the CGroup has been deleted by the time we are fetching the values
                    # from it. This would raise IOError with file entry not found (ERRNO: 2). We do not want to log
                    # every occurrences of such case as it would be very verbose. We do want to log all the other
                    # exceptions which could occur, which is why we do a periodic log for all the other errors.
                    if not isinstance(e, (IOError, OSError)) or e.errno != errno.ENOENT:  # pylint: disable=E1101
                        logger.periodic_warn(logger.EVERY_HOUR, '[PERIODIC] Could not collect metrics for cgroup '
                                                                '{0}. Error : {1}'.format(controller.name, ustr(e)))
                if not controller.is_active():
                    inactive_controllers.append(controller)
            for inactive_controller in inactive_controllers:
                CGroupsTelemetry.stop_tracking(inactive_controller)

        return metrics

    @staticmethod
    def reset():
        with CGroupsTelemetry._rlock:
            CGroupsTelemetry._tracked.clear()  # emptying the dictionary