238 lines
10 KiB
Python
238 lines
10 KiB
Python
# 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.
|
|
|
|
import os
|
|
import re
|
|
|
|
import monasca_agent.collector.checks as checks
|
|
|
|
_LXC_CGROUP_PWD = '/sys/fs/cgroup'
|
|
_LXC_CGROUP_CPU_PWD = '{0}/cpu/lxc'.format(_LXC_CGROUP_PWD)
|
|
_LXC_CGROUP_CPUSET_PWD = '{0}/cpuset/lxc'.format(_LXC_CGROUP_PWD)
|
|
_LXC_CGROUP_MEM_PWD = '{0}/memory/lxc'.format(_LXC_CGROUP_PWD)
|
|
_LXC_CGROUP_DISK_PWD = '{0}/blkio/lxc'.format(_LXC_CGROUP_PWD)
|
|
|
|
_LXC_NET_REGEX = re.compile(r'(\w+):(.+)')
|
|
_LXC_DISK_REGEX = re.compile(r'(\w+)\s(\d+)')
|
|
|
|
|
|
class LXC(checks.AgentCheck):
|
|
"""Agent to collect LXC cgroup information.
|
|
|
|
The information is mostly based on cgroup files of each container.
|
|
"""
|
|
|
|
def check(self, instance):
|
|
self.instance = instance
|
|
self.containers = self._containers_name()
|
|
self.increment('running_containers', len(self.containers),
|
|
{'service': 'lxc'})
|
|
for container_name in self.containers:
|
|
self._collect_cpu_metrics(container_name)
|
|
self._collect_mem_metrics(container_name)
|
|
self._collect_swap_metrics(container_name)
|
|
self._collect_net_metrics(container_name)
|
|
self._collect_disk_metrics(container_name)
|
|
|
|
def _containers_name(self):
|
|
container_name = self.instance.get('container')
|
|
if container_name == 'all':
|
|
return [name for name in os.listdir(_LXC_CGROUP_CPU_PWD)
|
|
if os.path.isdir('{0}/{1}'.format(_LXC_CGROUP_CPU_PWD,
|
|
name))]
|
|
|
|
if os.path.isdir('{0}/{1}'.format(_LXC_CGROUP_CPU_PWD,
|
|
container_name)):
|
|
self.log.info('\tContainer name: ' + container_name)
|
|
return [container_name]
|
|
else:
|
|
self.log.error('\tContainer {0} was not found'
|
|
.format(container_name))
|
|
return
|
|
|
|
def _collect_cpu_metrics(self, container_name):
|
|
if not self.instance.get('cpu', True):
|
|
return
|
|
metrics = self._get_cpu_metrics(container_name)
|
|
cpu_dimensions = self._get_dimensions(container_name)
|
|
for metric, value in metrics.items():
|
|
self.gauge(metric, value, dimensions=cpu_dimensions)
|
|
|
|
def _collect_mem_metrics(self, container_name):
|
|
if not self.instance.get('mem', True):
|
|
return
|
|
metrics = self._get_mem_metrics(container_name)
|
|
mem_dimensions = self._get_dimensions(container_name)
|
|
for metric, value in metrics.items():
|
|
self.gauge(metric, value, dimensions=mem_dimensions)
|
|
|
|
def _collect_swap_metrics(self, container_name):
|
|
if not self.instance.get('swap', True):
|
|
return
|
|
metrics = self._get_swap_metrics(container_name)
|
|
if metrics:
|
|
swap_dimensions = self._get_dimensions(container_name)
|
|
for metric, value in metrics.items():
|
|
self.gauge(metric, value, dimensions=swap_dimensions)
|
|
|
|
def _collect_net_metrics(self, container_name):
|
|
if not self.instance.get('net', True):
|
|
return
|
|
metrics = self._get_net_metrics(container_name)
|
|
for iface_name, iface_metrics in metrics.items():
|
|
net_dimensions = self._get_dimensions(container_name,
|
|
{'iface': iface_name})
|
|
for metric, value in iface_metrics.items():
|
|
self.gauge(metric, value, dimensions=net_dimensions)
|
|
|
|
def _collect_disk_metrics(self, container_name):
|
|
if not self.instance.get('blkio', True):
|
|
return
|
|
metrics = self._get_disk_metrics(container_name)
|
|
disk_dimensions = self._get_dimensions(container_name)
|
|
for metric, value in metrics.items():
|
|
self.gauge(metric, value, dimensions=disk_dimensions)
|
|
|
|
def _get_cpu_metrics(self, container_name):
|
|
"""Get metrics from cpuacct.usage cgroup file
|
|
|
|
:return: a dictionary containing cpu metrics defined on container
|
|
cgroup
|
|
"""
|
|
metrics = {}
|
|
cpu_cgroup = '{0}/{1}/'.format(_LXC_CGROUP_CPU_PWD, container_name)
|
|
metrics['cpuacct.usage'] = int(open(cpu_cgroup + 'cpuacct.usage', 'r')
|
|
.readline().rstrip('\n'))
|
|
cpuacct_usage_percpu = open(cpu_cgroup + 'cpuacct.usage_percpu', 'r')\
|
|
.readline().rstrip(' \n').split(' ')
|
|
for cpu in range(len(cpuacct_usage_percpu)):
|
|
metrics['cpuacct.usage_percpu.cpu{0}'.format(cpu)] = \
|
|
int(cpuacct_usage_percpu[cpu])
|
|
metrics_stat = self._get_metrics_by_file(cpu_cgroup + 'cpuacct.stat',
|
|
'cpuacct')
|
|
metrics.update(metrics_stat)
|
|
return metrics
|
|
|
|
def _get_mem_metrics(self, container_name):
|
|
"""Get metrics from memory.stat and memory cgroup file
|
|
|
|
:returns: a dictionary containing memory metrics defined on
|
|
container cgroup
|
|
"""
|
|
mem_cgroup = '{0}/{1}/'.format(_LXC_CGROUP_MEM_PWD, container_name)
|
|
metrics = self._get_metrics_by_file(mem_cgroup + 'memory.stat',
|
|
'memory')
|
|
# Get others cgroup memory values
|
|
with open('{0}/memory.usage_in_bytes'.format(mem_cgroup)) as mem_file:
|
|
metrics['memory.usage_in_bytes'] = int(mem_file.read())
|
|
return metrics
|
|
|
|
def _get_swap_metrics(self, container_name):
|
|
"""Get swap metrics from memory cgroup file.
|
|
|
|
If swapaccount is defined, you can control swap memory. To active swap
|
|
memory control, set GRUB_CMDLINE_LINUX_DEFAULT="swapaccount=1" on
|
|
/etc/default/grub file and restart the machine.
|
|
|
|
:returns: a dictionary containing swap metrics defined on
|
|
container cgroup
|
|
"""
|
|
metrics = {}
|
|
swap_file = '{0}/{1}/memory.memsw.usage_in_bytes'.format(_LXC_CGROUP_MEM_PWD,
|
|
container_name)
|
|
if os.path.isfile(swap_file):
|
|
with open(swap_file) as mem_file:
|
|
metrics['memory.memsw.usage_in_bytes'] = int(mem_file.read())
|
|
else:
|
|
self.log.error('Swap cgroup fine not found. '
|
|
'Verify if swapaccount control is enable')
|
|
return metrics
|
|
|
|
def _get_net_metrics(self, container_name):
|
|
"""Get metrics for each net interface found
|
|
|
|
:returns: a dictionary containing metrics regarding each
|
|
net interface found, in the format:
|
|
{ 'lo': { 'net.rx.bytes': 1234 }, ...}
|
|
"""
|
|
metrics = {}
|
|
pid = self._get_pid_container(container_name)
|
|
net_cgroup = '/proc/{0}/net/'.format(pid)
|
|
with open(net_cgroup + 'dev', 'r') as dev_file:
|
|
for line in dev_file:
|
|
iface = re.search(_LXC_NET_REGEX, line)
|
|
if iface:
|
|
iface_name = iface.group(1)
|
|
iface_info = iface.group(2).split()
|
|
metrics[iface_name] = {
|
|
'net.rx.bytes': int(iface_info[0]),
|
|
'net.rx.packets': int(iface_info[1]),
|
|
'net.rx.errs': int(iface_info[2]),
|
|
'net.rx.drop': int(iface_info[3]),
|
|
'net.rx.fifo': int(iface_info[4]),
|
|
'net.rx.frame': int(iface_info[5]),
|
|
'net.rx.compressed': int(iface_info[6]),
|
|
'net.rx.multicast': int(iface_info[7]),
|
|
'net.tx.bytes': int(iface_info[8]),
|
|
'net.tx.packets': int(iface_info[9]),
|
|
'net.tx.errs': int(iface_info[10]),
|
|
'net.tx.drop': int(iface_info[11]),
|
|
'net.tx.fifo': int(iface_info[12]),
|
|
'net.tx.frame': int(iface_info[13]),
|
|
'net.tx.compressed': int(iface_info[14]),
|
|
'net.tx.multicast': int(iface_info[15])
|
|
}
|
|
return metrics
|
|
|
|
def _get_disk_metrics(self, container_name):
|
|
"""Get metrics blkio.throttle.io_service_bytes from cgroup file
|
|
|
|
:return: a dictionary containing blkio metrics used to verify disk
|
|
cgroup usage
|
|
"""
|
|
metrics = {}
|
|
disk_cgroup = '{0}/{1}/blkio.throttle.io_service_bytes'.format(
|
|
_LXC_CGROUP_DISK_PWD, container_name)
|
|
with open(disk_cgroup, 'r') as disk_file:
|
|
for line in disk_file:
|
|
disk = re.search(_LXC_DISK_REGEX, line)
|
|
if disk:
|
|
disk_key = 'blkio.{0}'.format(disk.group(1)).lower()
|
|
disk_value = disk.group(2)
|
|
metrics[disk_key] = int(disk_value)
|
|
return metrics
|
|
|
|
def _get_metrics_by_file(self, filename, pre_key):
|
|
"""Some cgroup files have a pattern 'key value' that can be easily
|
|
handled to a dictionary
|
|
"""
|
|
metrics = {}
|
|
with open(filename, 'r') as cgroup_file:
|
|
for line in cgroup_file:
|
|
resource_post_key, resource_value = line.split(' ')
|
|
resource_key = '{0}.{1}'.format(pre_key, resource_post_key)
|
|
metrics[resource_key] = int(resource_value)
|
|
return metrics
|
|
|
|
def _get_dimensions(self, container_name, options=None):
|
|
dimensions = {'container_name': container_name,
|
|
'service': 'lxc'}
|
|
if options:
|
|
dimensions.update(options)
|
|
return self._set_dimensions(dimensions, self.instance)
|
|
|
|
def _get_pid_container(self, container_name):
|
|
cpu_tasks = '{0}/{1}/tasks'.format(_LXC_CGROUP_CPU_PWD,
|
|
container_name)
|
|
pid = open(cpu_tasks, 'r').readline().rstrip('\n')
|
|
return pid
|