Merge pull request #3 from Ved-vampir/master

perprocess cpu usage added
diff --git a/sensors/host1_config.json b/sensors/host1_config.json
index 450db06..5f9d2e8 100644
--- a/sensors/host1_config.json
+++ b/sensors/host1_config.json
@@ -9,5 +9,11 @@
         "allowed_prefixes": ["cpu"]
     },
     "system-ram": {
+    },
+    "perprocess-cpu": {
+        "allowed_prefixes": ["ceph"]
+    },
+    "perprocess-ram": {
+        "allowed_prefixes": ["ceph"]
     }
 }
diff --git a/sensors/main.py b/sensors/main.py
index 8668518..4f0a8d1 100644
--- a/sensors/main.py
+++ b/sensors/main.py
@@ -10,6 +10,8 @@
 import net_sensors
 import syscpu_sensors
 import sysram_sensors
+import pscpu_sensors
+import psram_sensors
 
 from utils import SensorInfo
 from daemonize import Daemonize
diff --git a/sensors/ps_mem.py b/sensors/ps_mem.py
new file mode 100644
index 0000000..2e0fed8
--- /dev/null
+++ b/sensors/ps_mem.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python
+
+# 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
+
+import errno
+import os
+import sys
+
+
+
+class Proc:
+    def __init__(self):
+        uname = os.uname()
+        if uname[0] == "FreeBSD":
+            self.proc = '/compat/linux/proc'
+        else:
+            self.proc = '/proc'
+
+    def path(self, *args):
+        return os.path.join(self.proc, *(str(a) for a in args))
+
+    def open(self, *args):
+        try:
+            return open(self.path(*args))
+        except (IOError, OSError):
+            val = sys.exc_info()[1]
+            if (val.errno == errno.ENOENT or # kernel thread or process gone
+                val.errno == errno.EPERM):
+                raise LookupError
+            raise
+
+
+def kernel_ver():
+    """ Return (major,minor,release)"""
+    proc = Proc()
+    kv = proc.open('sys/kernel/osrelease').readline().split(".")[:3]
+    last = len(kv)
+    if last == 2:
+        kv.append('0')
+    last -= 1
+    while last > 0:
+        for char in "-_":
+            kv[last] = kv[last].split(char)[0]
+        try:
+            int(kv[last])
+        except:
+            kv[last] = 0
+        last -= 1
+    return (int(kv[0]), int(kv[1]), int(kv[2]))
+
+
+#Note shared is always a subset of rss (trs is not always)
+def getMemStats(pid):
+    """ Return memory data of pid in format (Private, Shared) """
+
+    PAGESIZE = os.sysconf("SC_PAGE_SIZE") / 1024 #KiB
+    proc = Proc()
+
+    Private_lines = []
+    Shared_lines = []
+    Pss_lines = []
+
+    Rss = (int(proc.open(pid, 'statm').readline().split()[1])
+           * PAGESIZE)
+
+    if os.path.exists(proc.path(pid, 'smaps')): #stat
+        for line in proc.open(pid, 'smaps').readlines(): #open
+            # Note we checksum smaps as maps is usually but
+            # not always different for separate processes.
+            if line.startswith("Shared"):
+                Shared_lines.append(line)
+            elif line.startswith("Private"):
+                Private_lines.append(line)
+            elif line.startswith("Pss"):
+                have_pss = 1
+                Pss_lines.append(line)
+        Shared = sum([int(line.split()[1]) for line in Shared_lines])
+        Private = sum([int(line.split()[1]) for line in Private_lines])
+        #Note Shared + Private = Rss above
+        #The Rss in smaps includes video card mem etc.
+        if have_pss:
+            pss_adjust = 0.5 # add 0.5KiB as this avg error due to trunctation
+            Pss = sum([float(line.split()[1])+pss_adjust for line in Pss_lines])
+            Shared = Pss - Private
+    elif (2, 6, 1) <= kernel_ver() <= (2, 6, 9):
+        Shared = 0 #lots of overestimation, but what can we do?
+        Private = Rss
+    else:
+        Shared = int(proc.open(pid, 'statm').readline().split()[2])
+        Shared *= PAGESIZE
+        Private = Rss - Shared
+    return (Private, Shared)
diff --git a/sensors/pscpu_sensors.py b/sensors/pscpu_sensors.py
new file mode 100644
index 0000000..4e1849f
--- /dev/null
+++ b/sensors/pscpu_sensors.py
@@ -0,0 +1,64 @@
+import os
+import time
+
+from discover import provides
+from utils import SensorInfo, get_pid_name, get_pid_list
+
+
+
+@provides("perprocess-cpu")
+def pscpu_stat(disallowed_prefixes=None, allowed_prefixes=None):
+    results = {}
+    pid_stat0 = {}
+    sys_stat0 = {}
+    pid_list = get_pid_list(disallowed_prefixes, allowed_prefixes)
+
+    for pid in pid_list:
+        try:
+            pid_stat0[pid] = pid_stat(pid)
+            sys_stat0[pid] = sys_stat()
+        except IOError:
+            # may be, proc has already terminated
+            continue
+
+    time.sleep(1)
+
+    for pid in pid_list:
+        try:
+            dev_name = get_pid_name(pid)
+
+            pid_stat1 = pid_stat(pid)
+            sys_stat1 = sys_stat()
+            cpu = (pid_stat1 - pid_stat0[pid]) / (sys_stat1 - sys_stat0[pid])
+
+            sensor_name = "{0}.{1}".format(dev_name, pid)
+            results[sensor_name] = SensorInfo(cpu*100, False)
+        except IOError:
+            # may be, proc has already terminated
+            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
+        proctotal = int(utime) + int(stime)
+        return float(proctotal)
+
+
+def sys_stat():
+    """ Return total system cpu usage time"""
+    with open('/proc/stat', 'r') as procfile:
+        cputimes = procfile.readline().split()[1:]
+        cputotal = 0
+        # count from /proc/stat sum
+        for i in cputimes:
+            cputotal = cputotal + int(i)
+        return float(cputotal)
diff --git a/sensors/psram_sensors.py b/sensors/psram_sensors.py
new file mode 100644
index 0000000..db36710
--- /dev/null
+++ b/sensors/psram_sensors.py
@@ -0,0 +1,38 @@
+from ps_mem import getMemStats
+
+from discover import provides
+from utils import SensorInfo, get_pid_name
+
+
+
+@provides("perprocess-ram")
+def psram_stat(disallowed_prefixes=None, allowed_prefixes=None):
+    results = {}
+    pid_list = get_pid_list(disallowed_prefixes, allowed_prefixes)
+    print pid_list
+    for pid in pid_list:
+        try:
+            dev_name = get_pid_name(pid)
+
+            private, shared = getMemStats(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)
+            results[sensor_name + ".mem_usage_percent"] = 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]
diff --git a/sensors/utils.py b/sensors/utils.py
index 5af0a2a..546dee7 100644
--- a/sensors/utils.py
+++ b/sensors/utils.py
@@ -1,3 +1,5 @@
+import os
+
 from collections import namedtuple
 
 SensorInfo = namedtuple("SensorInfo", ['value', 'is_accumulated'])
@@ -17,6 +19,42 @@
     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):
+                    print name
+                    # 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:
+        return "no_such_process"
+
+
 def delta(func, only_upd=True):
     prev = {}
     while True: