nginx-amplify-agent/amplify/agent/collectors/system/meta.py

228 lines
8.4 KiB
Python

# -*- coding: utf-8 -*-
import netifaces
import re
import netaddr
import psutil
from amplify.agent.common.context import context
from amplify.agent.common.errors import AmplifySubprocessError
from amplify.agent.common.util import subp
from amplify.agent.common.util.ec2 import AmazonEC2
from amplify.agent.common.util.host import os_name, etc_release, alive_interfaces
from amplify.agent.collectors.abstract import AbstractMetaCollector
__author__ = "Mike Belov"
__copyright__ = "Copyright (C) Nginx, Inc. All rights reserved."
__license__ = ""
__maintainer__ = "Mike Belov"
__email__ = "dedm@nginx.com"
class SystemMetaCollector(AbstractMetaCollector):
"""
Collects metadata about the system the agent is running on.
"""
short_name = 'sys_meta'
proc_cpuinfo_re = re.compile('([\w\.\s]+):\s*(.+)')
lscpu_re = re.compile('([\w\d\s\(\)\.]+):\s+([\w\d].*)')
def __init__(self, **kwargs):
super(SystemMetaCollector, self).__init__(**kwargs)
self.uuid = self.object.data['uuid']
self.register(
self.disk_partitions,
self.etc_release,
self.proc_cpuinfo,
self.lscpu,
self.uname,
self.network
)
if not self.in_container:
self.ec2_metadata = AmazonEC2.read_meta()
@property
def default_meta(self):
meta = {
'type': self.object.type,
'uuid': self.uuid,
'os-type': os_name(),
'display_name': self.object.display_name,
'tags': context.tags,
'network': {'interfaces': [], 'default': None},
'disk_partitions': [],
'release': {'name': None, 'version_id': None, 'version': None},
'processor': {'cache': {}}
}
if not self.in_container:
meta['hostname'] = self.object.hostname
meta['boot'] = int(psutil.boot_time()) * 1000
meta['ec2'] = self.ec2_metadata or None
else:
meta['imagename'] = self.object.imagename
meta['container_type'] = context.container_type or 'None'
return meta
def disk_partitions(self):
""" disk partitions """
self.meta['disk_partitions'] = [
{'mountpoint': x.mountpoint, 'device': x.device, 'fstype': x.fstype}
for x in psutil.disk_partitions(all=False)
]
def etc_release(self):
self.meta['release'].update(etc_release())
def proc_cpuinfo(self):
""" cat /proc/cpuinfo """
proc_cpuinfo_out, _ = subp.call('cat /proc/cpuinfo')
for line in proc_cpuinfo_out:
kv = re.match(self.proc_cpuinfo_re, line)
if kv:
key, value = kv.group(1), kv.group(2)
if key.startswith('model name'):
self.meta['processor']['model'] = value
elif key.startswith('cpu cores'):
self.meta['processor']['cores'] = value
def lscpu(self):
""" lscpu """
lscpu_out, _ = subp.call('lscpu')
for line in lscpu_out:
kv = re.match(self.lscpu_re, line)
if kv:
key, value = kv.group(1), kv.group(2)
if key == 'Architecture':
self.meta['processor']['architecture'] = value
elif key == 'CPU MHz':
self.meta['processor']['mhz'] = value
elif key == 'Hypervisor vendor':
self.meta['processor']['hypervisor'] = value
elif key == 'Virtualization type':
self.meta['processor']['virtualization'] = value
elif key == 'CPU(s)':
self.meta['processor']['cpus'] = value
elif 'cache' in key:
key = key.replace(' cache', '')
self.meta['processor']['cache'][key] = value
def uname(self):
""" Collects the full uname for the OS """
uname_cmd = 'uname -a' if not self.in_container else 'uname -s -r -v -m -p'
uname_out, _ = subp.call(uname_cmd)
self.meta['uname'] = uname_out.pop(0)
def network(self):
""" network """
# collect info for all the alive interfaces
for interface in alive_interfaces():
addresses = netifaces.ifaddresses(interface)
interface_info = {'name': interface}
# collect addresses (if not running in a container)
if not self.in_container:
for proto, key in (('ipv4', netifaces.AF_INET), ('ipv6', netifaces.AF_INET6)):
protocol_data = addresses.get(key, [{}])[0]
if protocol_data:
interface_info[proto] = {
'address': protocol_data.get('addr').split('%').pop(0),
'netmask': protocol_data.get('netmask')
}
try:
address = '%(address)s/%(netmask)s' % interface_info[proto]
interface_info[proto]['prefixlen'] = netaddr.IPNetwork(address).prefixlen
except:
interface_info[proto]['prefixlen'] = None
# collect MAC address
interface_info['mac'] = addresses.get(netifaces.AF_LINK, [{}])[0].get('addr')
self.meta['network']['interfaces'].append(interface_info)
# get default interface name
netstat_out, _ = subp.call("netstat -nr | egrep -i '^0.0.0.0|default'", check=False)
if netstat_out and netstat_out[0]:
first_matched_line = netstat_out[0]
default_interface = first_matched_line.split(' ')[-1]
elif self.meta['network']['interfaces']:
default_interface = self.meta['network']['interfaces'][0]['name']
else:
default_interface = None
self.meta['network']['default'] = default_interface
class GenericLinuxSystemMetaCollector(SystemMetaCollector):
pass
class DebianSystemMetaCollector(SystemMetaCollector):
pass
class CentosSystemMetaCollector(SystemMetaCollector):
etc_release_re = re.compile(r'^(.+?)\s+(\w+)\s+([\d\.]+)\s+([\w\(\)]+)')
def etc_release(self):
"""
Centos6 has different *-release format.
For example: CentOS release 6.7 (Final)
"""
super(CentosSystemMetaCollector, self).etc_release()
if self.meta['release']['version_id'] is None and self.meta['release']['version'] is None:
try:
etc_release_out, _ = subp.call('cat /etc/centos-release', check=True)
except AmplifySubprocessError:
etc_release_out, _ = subp.call('cat /etc/redhat-release', check=True)
for line in etc_release_out:
r = re.match(self.etc_release_re, line)
if r:
self.meta['release']['name'] = r.group(1)
self.meta['release']['version_id'] = r.group(3)
self.meta['release']['version'] = '%s %s' % (r.group(3), r.group(4))
class GentooSystemMetaCollector(SystemMetaCollector):
pass
class FreebsdSystemMetaCollector(SystemMetaCollector):
def etc_release(self):
"""
FreeBSD has no *-release files. This uses uname -sr instead.
"""
uname_out, _ = subp.call('uname -sr')
name, version = uname_out[0].split(' ', 1)
self.meta['release']['name'] = name
self.meta['release']['version'] = version
self.meta['release']['version_id'] = version
def proc_cpuinfo(self):
""" cat /proc/cpuinfo """
self.meta['processor']['cpus'] = psutil.cpu_count(logical=False)
self.meta['processor']['cores'] = psutil.cpu_count()
proc_cpuinfo_out, _ = subp.call('sysctl hw.model')
for line in proc_cpuinfo_out:
kv = re.match(self.proc_cpuinfo_re, line)
if kv:
key, value = kv.group(1), kv.group(2)
if key.startswith('hw.model'):
self.meta['processor']['model'] = value
def lscpu(self):
""" lscpu """
lscpu_out, _ = subp.call('sysctl hw.machine_arch hw.clockrate')
for line in lscpu_out:
kv = re.match(self.lscpu_re, line)
if kv:
key, value = kv.group(1), kv.group(2)
if key == 'hw.machine_arch':
self.meta['processor']['architecture'] = value
elif key == 'hw.clockrate':
self.meta['processor']['mhz'] = value