blob: a2758c30a0a4f3906a48f132bb7366185938d9e0 [file] [log] [blame]
import os
from collections import namedtuple
SensorInfo = namedtuple("SensorInfo", ['value', 'is_accumulated'])
# SensorInfo = NamedTuple("SensorInfo", [('value', int), ('is_accumulated', bool)])
def provides(name: str):
def closure(func):
return func
return closure
def is_dev_accepted(name, disallowed_prefixes, allowed_prefixes):
dev_ok = True
if disallowed_prefixes is not None:
dev_ok = all(not name.startswith(prefix)
for prefix in disallowed_prefixes)
if dev_ok and allowed_prefixes is not None:
dev_ok = any(name.startswith(prefix)
for prefix in allowed_prefixes)
return dev_ok
def get_pid_list(disallowed_prefixes, allowed_prefixes):
"""Return pid list from list of pids and names"""
# exceptions
but = disallowed_prefixes if disallowed_prefixes is not None else []
if allowed_prefixes is None:
# if nothing setted - all ps will be returned except setted
result = [pid
for pid in os.listdir('/proc')
if pid.isdigit() and pid not in but]
else:
result = []
for pid in os.listdir('/proc'):
if pid.isdigit() and pid not in but:
name = get_pid_name(pid)
if pid in allowed_prefixes or \
any(name.startswith(val) for val in allowed_prefixes):
# this is allowed pid?
result.append(pid)
return result
def get_pid_name(pid):
"""Return name by pid"""
try:
with open(os.path.join('/proc/', pid, 'cmdline'), 'r') as pidfile:
try:
cmd = pidfile.readline().split()[0]
return os.path.basename(cmd).rstrip('\x00')
except IndexError:
# no cmd returned
return "<NO NAME>"
except IOError:
# upstream wait any string, no matter if we couldn't read proc
return "no_such_process"
def delta(func, only_upd=True):
prev = {}
while True:
for dev_name, vals in func():
if dev_name not in prev:
prev[dev_name] = {}
for name, (val, _) in vals.items():
prev[dev_name][name] = val
else:
dev_prev = prev[dev_name]
res = {}
for stat_name, (val, accum_val) in vals.items():
if accum_val:
if stat_name in dev_prev:
delta = int(val) - int(dev_prev[stat_name])
if not only_upd or 0 != delta:
res[stat_name] = str(delta)
dev_prev[stat_name] = val
elif not only_upd or '0' != val:
res[stat_name] = val
if only_upd and len(res) == 0:
continue
yield dev_name, res
yield None, None
# 1 - major number
# 2 - minor mumber
# 3 - device name
# 4 - reads completed successfully
# 5 - reads merged
# 6 - sectors read
# 7 - time spent reading (ms)
# 8 - writes completed
# 9 - writes merged
# 10 - sectors written
# 11 - time spent writing (ms)
# 12 - I/Os currently in progress
# 13 - time spent doing I/Os (ms)
# 14 - weighted time spent doing I/Os (ms)
io_values_pos = [
(3, 'reads_completed', True),
(5, 'sectors_read', True),
(6, 'rtime', True),
(7, 'writes_completed', True),
(9, 'sectors_written', True),
(10, 'wtime', True),
(11, 'io_queue', False),
(13, 'io_time', True)
]
@provides("block-io")
def io_stat(disallowed_prefixes=('ram', 'loop'), allowed_prefixes=None):
results = {}
for line in open('/proc/diskstats'):
vals = line.split()
dev_name = vals[2]
dev_ok = is_dev_accepted(dev_name,
disallowed_prefixes,
allowed_prefixes)
if dev_name[-1].isdigit():
dev_ok = False
if dev_ok:
for pos, name, accum_val in io_values_pos:
sensor_name = "{0}.{1}".format(dev_name, name)
results[sensor_name] = SensorInfo(int(vals[pos]), accum_val)
return results
def get_latency(stat1, stat2):
disks = set(i.split('.', 1)[0] for i in stat1)
results = {}
for disk in disks:
rdc = disk + '.reads_completed'
wrc = disk + '.writes_completed'
rdt = disk + '.rtime'
wrt = disk + '.wtime'
lat = 0.0
io_ops1 = stat1[rdc].value + stat1[wrc].value
io_ops2 = stat2[rdc].value + stat2[wrc].value
diops = io_ops2 - io_ops1
if diops != 0:
io1 = stat1[rdt].value + stat1[wrt].value
io2 = stat2[rdt].value + stat2[wrt].value
lat = abs(float(io1 - io2)) / diops
results[disk + '.latence'] = SensorInfo(lat, False)
return results
# 1 - major number
# 2 - minor mumber
# 3 - device name
# 4 - reads completed successfully
# 5 - reads merged
# 6 - sectors read
# 7 - time spent reading (ms)
# 8 - writes completed
# 9 - writes merged
# 10 - sectors written
# 11 - time spent writing (ms)
# 12 - I/Os currently in progress
# 13 - time spent doing I/Os (ms)
# 14 - weighted time spent doing I/Os (ms)
net_values_pos = [
(0, 'recv_bytes', True),
(1, 'recv_packets', True),
(8, 'send_bytes', True),
(9, 'send_packets', True),
]
@provides("net-io")
def net_stat(disallowed_prefixes=('docker', 'lo'), allowed_prefixes=('eth',)):
results = {}
for line in open('/proc/net/dev').readlines()[2:]:
dev_name, stats = line.split(":", 1)
dev_name = dev_name.strip()
vals = stats.split()
dev_ok = is_dev_accepted(dev_name,
disallowed_prefixes,
allowed_prefixes)
if '.' in dev_name and dev_name.split('.')[-1].isdigit():
dev_ok = False
if dev_ok:
for pos, name, accum_val in net_values_pos:
sensor_name = "{0}.{1}".format(dev_name, name)
results[sensor_name] = SensorInfo(int(vals[pos]), accum_val)
return results
@provides("perprocess-cpu")
def pscpu_stat(disallowed_prefixes=None, allowed_prefixes=None):
results = {}
pid_list = get_pid_list(disallowed_prefixes, allowed_prefixes)
for pid in pid_list:
try:
dev_name = get_pid_name(pid)
pid_stat1 = pid_stat(pid)
sensor_name = "{0}.{1}".format(dev_name, pid)
results[sensor_name] = SensorInfo(pid_stat1, True)
except IOError:
# may be, proc has already terminated, skip it
continue
return results
def pid_stat(pid):
"""Return total cpu usage time from process"""
# read /proc/pid/stat
with open(os.path.join('/proc/', pid, 'stat'), 'r') as pidfile:
proctimes = pidfile.readline().split()
# get utime from /proc/<pid>/stat, 14 item
utime = proctimes[13]
# get stime from proc/<pid>/stat, 15 item
stime = proctimes[14]
# count total process used time
return float(int(utime) + int(stime))
# Based on ps_mem.py:
# Licence: LGPLv2
# Author: P@draigBrady.com
# Source: http://www.pixelbeat.org/scripts/ps_mem.py
# http://github.com/pixelb/scripts/commits/master/scripts/ps_mem.py
# Note shared is always a subset of rss (trs is not always)
def get_mem_stats(pid):
"""Return memory data of pid in format (private, shared)"""
fname = '/proc/{0}/{1}'.format(pid, "smaps")
lines = open(fname).readlines()
shared = 0
private = 0
pss = 0
# add 0.5KiB as this avg error due to trunctation
pss_adjust = 0.5
for line in lines:
if line.startswith("Shared"):
shared += int(line.split()[1])
if line.startswith("Private"):
private += int(line.split()[1])
if line.startswith("Pss"):
pss += float(line.split()[1]) + pss_adjust
# Note Shared + Private = Rss above
# The Rss in smaps includes video card mem etc.
if pss != 0:
shared = int(pss - private)
return (private, shared)
@provides("perprocess-ram")
def psram_stat(disallowed_prefixes=None, allowed_prefixes=None):
results = {}
pid_list = get_pid_list(disallowed_prefixes, allowed_prefixes)
for pid in pid_list:
try:
dev_name = get_pid_name(pid)
private, shared = get_mem_stats(pid)
total = private + shared
sys_total = get_ram_size()
usage = float(total) / float(sys_total)
sensor_name = "{0}({1})".format(dev_name, pid)
results[sensor_name + ".private_mem"] = SensorInfo(private, False)
results[sensor_name + ".shared_mem"] = SensorInfo(shared, False)
results[sensor_name + ".used_mem"] = SensorInfo(total, False)
name = sensor_name + ".mem_usage_percent"
results[name] = SensorInfo(usage * 100, False)
except IOError:
# permission denied or proc die
continue
return results
def get_ram_size():
"""Return RAM size in Kb"""
with open("/proc/meminfo") as proc:
mem_total = proc.readline().split()
return mem_total[1]
# 0 - cpu name
# 1 - user: normal processes executing in user mode
# 2 - nice: niced processes executing in user mode
# 3 - system: processes executing in kernel mode
# 4 - idle: twiddling thumbs
# 5 - iowait: waiting for I/O to complete
# 6 - irq: servicing interrupts
# 7 - softirq: servicing softirqs
cpu_values_pos = [
(1, 'user_processes', True),
(2, 'nice_processes', True),
(3, 'system_processes', True),
(4, 'idle_time', True),
]
@provides("system-cpu")
def syscpu_stat(disallowed_prefixes=None, allowed_prefixes=None):
results = {}
# calculate core count
core_count = 0
for line in open('/proc/stat'):
vals = line.split()
dev_name = vals[0]
if dev_name == 'cpu':
for pos, name, accum_val in cpu_values_pos:
sensor_name = "{0}.{1}".format(dev_name, name)
results[sensor_name] = SensorInfo(int(vals[pos]),
accum_val)
elif dev_name == 'procs_blocked':
val = int(vals[1])
results["cpu.procs_blocked"] = SensorInfo(val, False)
elif dev_name.startswith('cpu'):
core_count += 1
# procs in queue
TASKSPOS = 3
vals = open('/proc/loadavg').read().split()
ready_procs = vals[TASKSPOS].partition('/')[0]
# dec on current proc
procs_queue = (float(ready_procs) - 1) / core_count
results["cpu.procs_queue"] = SensorInfo(procs_queue, False)
return results
# return this values or setted in allowed
ram_fields = [
'MemTotal',
'MemFree',
'Buffers',
'Cached',
'SwapCached',
'Dirty',
'Writeback',
'SwapTotal',
'SwapFree'
]
@provides("system-ram")
def sysram_stat(disallowed_prefixes=None, allowed_prefixes=None):
if allowed_prefixes is None:
allowed_prefixes = ram_fields
results = {}
for line in open('/proc/meminfo'):
vals = line.split()
dev_name = vals[0].rstrip(":")
dev_ok = is_dev_accepted(dev_name,
disallowed_prefixes,
allowed_prefixes)
title = "ram.{0}".format(dev_name)
if dev_ok:
results[title] = SensorInfo(int(vals[1]), False)
if 'ram.MemFree' in results and 'ram.MemTotal' in results:
used = results['ram.MemTotal'].value - results['ram.MemFree'].value
usage = float(used) / results['ram.MemTotal'].value
results["ram.usage_percent"] = SensorInfo(usage, False)
return results