Skip to content

Commit

Permalink
[procfs] make /proc configurable.
Browse files Browse the repository at this point in the history
[procfs] consistent naming with other checks that allow procfs path configuration.

[procfs] if set in agentconfig, override everywhere where psutil comes to play too.

[procfs] adding procfs_pathoption to example datadog.conf.

[procfs] use procfs_path where relevant.
  • Loading branch information
truthbk committed Jun 2, 2016
1 parent 0337e06 commit b8c25d5
Show file tree
Hide file tree
Showing 17 changed files with 95 additions and 19 deletions.
4 changes: 3 additions & 1 deletion agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@ def run(self, config=None):

self._agentConfig = self._set_agent_config_hostname(config)
hostname = get_hostname(self._agentConfig)
systemStats = get_system_stats()
systemStats = get_system_stats(
proc_path=self._agentConfig.get('procfs_path', '/proc').rstrip('/')
)
emitters = self._get_emitters()

# Initialize service discovery
Expand Down
1 change: 1 addition & 0 deletions checks.d/agent_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ def get_metric_context(self):
return self._collector_payload, self._metric_context

def check(self, instance):

if self.in_developer_mode:
stats, names_to_metric_types = self._psutil_config_to_stats(instance)
self._register_psutil_metrics(stats, names_to_metric_types)
Expand Down
6 changes: 6 additions & 0 deletions checks.d/btrfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

# project
from checks import AgentCheck
from utils.platform import Platform

MIXED = "mixed"
DATA = "data"
Expand Down Expand Up @@ -119,6 +120,11 @@ def get_usage(self, mountpoint):
def check(self, instance):
btrfs_devices = {}
excluded_devices = instance.get('excluded_devices', [])

if Platform.is_linux():
procfs_path = self.agentConfig.get('procfs_path', '/proc').rstrip('/')
psutil.PROCFS_PATH = procfs_path

for p in psutil.disk_partitions():
if (p.fstype == 'btrfs' and p.device not in btrfs_devices
and p.device not in excluded_devices):
Expand Down
3 changes: 3 additions & 0 deletions checks.d/disk.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ def check(self, instance):
# Windows and Mac will always have psutil
# (we have packaged for both of them)
if self._psutil():
if Platform.is_linux():
procfs_path = self.agentConfig.get('procfs_path', '/proc').rstrip('/')
psutil.PROCFS_PATH = procfs_path
self.collect_metrics_psutil()
else:
# FIXME: implement all_partitions (df -a)
Expand Down
5 changes: 5 additions & 0 deletions checks.d/gunicorn.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

# project
from checks import AgentCheck
from util import Platform


class GUnicornCheck(AgentCheck):
Expand All @@ -37,6 +38,10 @@ def check(self, instance):
""" Collect metrics for the given gunicorn instance. """
self.log.debug("Running instance: %s", instance)

if Platform.is_linux():
procfs_path = self.agentConfig.get('procfs_path', '/proc').rstrip('/')
psutil.PROCFS_PATH = procfs_path

# Validate the config.
if not instance or self.PROC_NAME not in instance:
raise GUnicornCheckError("instance must specify: %s" % self.PROC_NAME)
Expand Down
17 changes: 14 additions & 3 deletions checks.d/linux_proc_extras.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,23 @@ def check(self, instance):

prio_counts = defaultdict(int)

with open('/proc/sys/fs/inode-nr', 'r') as inode_info:
proc_location = self.agentConfig.get('procfs_path', '/proc').rstrip('/')

proc_path_map = {
"inode_info": "sys/fs/inode-nr",
"stat_info": "stat",
"entropy_info": "sys/kernel/random/entropy_avail",
}

for key, path in proc_path_map.iteritems():
proc_path_map[key] = "{procfs}/{path}".format(procfs=proc_location, path=path)

with open(proc_path_map['inode_info'], 'r') as inode_info:
inode_stats = inode_info.readline().split()
self.gauge('system.inodes.total', float(inode_stats[0]), tags=tags)
self.gauge('system.inodes.used', float(inode_stats[1]), tags=tags)

with open('/proc/stat', 'r') as stat_info:
with open(proc_path_map['stat_info'], 'r') as stat_info:
lines = [line.strip() for line in stat_info.readlines()]

for line in lines:
Expand All @@ -50,7 +61,7 @@ def check(self, instance):
interrupts = int(line.split(' ')[1])
self.monotonic_count('system.linux.interrupts', interrupts, tags=tags)

with open('/proc/sys/kernel/random/entropy_avail') as entropy_info:
with open(proc_path_map['entropy_info'], 'r') as entropy_info:
entropy = entropy_info.readline()
self.gauge('system.entropy.available', float(entropy), tags=tags)

Expand Down
6 changes: 6 additions & 0 deletions checks.d/mysql.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
# project
from config import _is_affirmative
from checks import AgentCheck
from util import Platform

GAUGE = "gauge"
RATE = "rate"
Expand Down Expand Up @@ -288,6 +289,11 @@ def get_library_versions(self):
return {"pymysql": pymysql.__version__}

def check(self, instance):

if Platform.is_linux() and PSUTIL_AVAILABLE:
procfs_path = self.agentConfig.get('procfs_path', '/proc').rstrip('/')
psutil.PROCFS_PATH = procfs_path

host, port, user, password, mysql_sock, defaults_file, tags, options, queries, ssl = \
self._get_config(instance)

Expand Down
9 changes: 6 additions & 3 deletions checks.d/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ def _submit_regexed_values(self, output, regex_list):
self.rate(metric, self._parse_value(value.group(1)))

def _check_linux(self, instance):
proc_location = self.agentConfig.get('procfs_path', '/proc').rstrip('/')
if self._collect_cx_state:
try:
self.log.debug("Using `ss` to collect connection state")
Expand Down Expand Up @@ -186,7 +187,8 @@ def _check_linux(self, instance):
except SubprocessOutputEmptyError:
self.log.exception("Error collecting connection stats.")

proc = open('/proc/net/dev', 'r')
proc_dev_path = "{}/net/dev".format(proc_location)
proc = open(proc_dev_path, 'r')
try:
lines = proc.readlines()
finally:
Expand All @@ -213,7 +215,8 @@ def _check_linux(self, instance):
self._submit_devicemetrics(iface, metrics)

try:
proc = open('/proc/net/snmp', 'r')
proc_snmp_path = "{}/net/snmp".format(proc_location)
proc = open(proc_snmp_path, 'r')

# IP: Forwarding DefaultTTL InReceives InHdrErrors ...
# IP: 2 64 377145470 0 ...
Expand Down Expand Up @@ -271,7 +274,7 @@ def _check_linux(self, instance):

except IOError:
# On Openshift, /proc/net/snmp is only readable by root
self.log.debug("Unable to read /proc/net/snmp.")
self.log.debug("Unable to read %s.", proc_snmp_path)

# Parse the output of the command that retrieves the connection state (either `ss` or `netstat`)
# Returns a dict metric_name -> value
Expand Down
6 changes: 4 additions & 2 deletions checks.d/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,10 @@ def __init__(self, name, init_config, agentConfig, instances=None):

if Platform.is_linux():
procfs_path = init_config.get('procfs_path')
if procfs_path:
psutil.PROCFS_PATH = procfs_path
if not procfs_path:
procfs_path = self.agentConfig.get('procfs_path', '/proc').rstrip('/')

psutil.PROCFS_PATH = procfs_path

# Process cache, indexed by instance
self.process_cache = defaultdict(dict)
Expand Down
6 changes: 6 additions & 0 deletions checks.d/system_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@

# project
from checks import AgentCheck
from utils.platform import Platform


class SystemCore(AgentCheck):

def check(self, instance):

if Platform.is_linux():
procfs_path = self.agentConfig.get('procfs_path', '/proc').rstrip('/')
psutil.PROCFS_PATH = procfs_path

cpu_times = psutil.cpu_times(percpu=True)
self.gauge("system.core.count", len(cpu_times))

Expand Down
6 changes: 6 additions & 0 deletions checks.d/system_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@

# project
from checks import AgentCheck
from utils.platform import Platform


class SystemSwap(AgentCheck):

def check(self, instance):

if Platform.is_linux():
procfs_path = self.agentConfig.get('procfs_path', '/proc').rstrip('/')
psutil.PROCFS_PATH = procfs_path

swap_mem = psutil.swap_memory()
self.rate('system.swap.swapped_in', swap_mem.sin)
self.rate('system.swap.swapped_out', swap_mem.sout)
4 changes: 4 additions & 0 deletions checks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,10 @@ def __init__(self, name, init_config, agentConfig, instances=None):
histogram_percentiles=agentConfig.get('histogram_percentiles')
)

if Platform.is_linux() and psutil is not None:
procfs_path = self.agentConfig.get('procfs_path', '/proc').rstrip('/')
psutil.PROCFS_PATH = procfs_path

self.events = []
self.service_checks = []
self.instances = instances or []
Expand Down
4 changes: 3 additions & 1 deletion checks/collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,9 @@ def _populate_payload_metadata(self, payload, check_statuses, start_event=True):
if gohai_metadata:
payload['gohai'] = gohai_metadata

payload['systemStats'] = get_system_stats()
payload['systemStats'] = get_system_stats(
proc_path=self.agentConfig.get('procfs_path', '/proc').rstrip('/')
)
payload['meta'] = self._get_hostname_metadata()

self.hostname_metadata_cache = payload['meta']
Expand Down
16 changes: 10 additions & 6 deletions checks/system/unix.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,10 @@ class Load(Check):

def check(self, agentConfig):
if Platform.is_linux():
proc_location = agentConfig.get('procfs_path', '/proc').rstrip('/')
try:
with open('/proc/loadavg', 'r') as load_avg:
proc_loadavg = "{}/loadavg".format(proc_location)
with open(proc_loadavg, 'r') as load_avg:
uptime = load_avg.readline().strip()
except Exception:
self.logger.exception('Cannot extract load')
Expand Down Expand Up @@ -286,11 +288,13 @@ def __init__(self, logger):

def check(self, agentConfig):
if Platform.is_linux():
proc_location = agentConfig.get('procfs_path', '/proc').rstrip('/')
try:
with open('/proc/meminfo', 'r') as mem_info:
proc_meminfo = "{}/meminfo".format(proc_location)
with open(proc_meminfo, 'r') as mem_info:
lines = mem_info.readlines()
except Exception:
self.logger.exception('Cannot get memory metrics from /proc/meminfo')
self.logger.exception('Cannot get memory metrics from %s', proc_meminfo)
return False

# NOTE: not all of the stats below are present on all systems as
Expand Down Expand Up @@ -349,7 +353,7 @@ def check(self, agentConfig):
if match is not None:
meminfo[match.group(1)] = match.group(2)
except Exception:
self.logger.exception("Cannot parse /proc/meminfo")
self.logger.exception("Cannot parse %s", proc_meminfo)

memData = {}

Expand All @@ -374,7 +378,7 @@ def check(self, agentConfig):
if memData['physTotal'] > 0:
memData['physPctUsable'] = float(memData['physUsable']) / float(memData['physTotal'])
except Exception:
self.logger.exception('Cannot compute stats from /proc/meminfo')
self.logger.exception('Cannot compute stats from %s', proc_meminfo)

# Swap
# FIXME units are in MB, we should use bytes instead
Expand Down Expand Up @@ -459,7 +463,7 @@ def check(self, agentConfig):
if memData['physTotal'] > 0:
memData['physPctUsable'] = float(memData['physUsable']) / float(memData['physTotal'])
except Exception:
self.logger.exception('Cannot compute stats from /proc/meminfo')
self.logger.exception('Cannot compute stats from %s', proc_meminfo)

# Swap
try:
Expand Down
7 changes: 5 additions & 2 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ def get_config(parse_args=True, cfg_path=None, options=None):
return agentConfig


def get_system_stats():
def get_system_stats(proc_path=None):
systemStats = {
'machine': platform.machine(),
'platform': sys.platform,
Expand All @@ -593,7 +593,10 @@ def get_system_stats():

try:
if Platform.is_linux(platf):
output, _, _ = get_subprocess_output(['grep', 'model name', '/proc/cpuinfo'], log)
if not proc_path:
proc_path = "/proc"
proc_cpuinfo = os.path.join(proc_path,'cpuinfo')
output, _, _ = get_subprocess_output(['grep', 'model name', proc_cpuinfo], log)
systemStats['cpuCores'] = len(output.splitlines())

if Platform.is_darwin(platf) or Platform.is_freebsd(platf):
Expand Down
9 changes: 9 additions & 0 deletions datadog.conf.example
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ use_mount: no
# `/datadog/check_configs` key in the back-end. If you wish otherwise, uncomment this option
# and modify its value.
# sd_template_dir: /datadog/check_configs
#
# ========================================================================== #
# Other #
# ========================================================================== #
#
# In some environments we may have the procfs file system mounted in a
# miscellaneous location. The procfs_path configuration paramenter allows
# us to override the standard default location '/proc'
# procfs_path: /proc

# ========================================================================== #
# DogStatsd configuration #
Expand Down
5 changes: 4 additions & 1 deletion utils/kubeutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from urlparse import urljoin

# project
from config import get_config
from util import check_yaml
from utils.checkfiles import get_conf_path
from utils.http import retrieve_json
Expand All @@ -33,6 +34,7 @@ class KubeUtil():
DEFAULT_CADVISOR_PORT = 4194
DEFAULT_KUBELET_PORT = 10255
DEFAULT_MASTER_PORT = 8080
CONFIG = get_config(parse_args=True)

def __init__(self):
config_file_path = get_conf_path(KUBERNETES_CHECK_NAME)
Expand Down Expand Up @@ -87,7 +89,8 @@ def retrieve_pods_list(self):
@classmethod
def _get_default_router(cls):
try:
with open('/proc/net/route') as f:
procfs_netroute = os.path.join(cls.CONFIG.get('procfs_path','/proc'), 'net', 'route')
with open(procfs_netroute) as f:
for line in f.readlines():
fields = line.strip().split()
if fields[1] == '00000000':
Expand Down

0 comments on commit b8c25d5

Please sign in to comment.