]> arthur.barton.de Git - netdata.git/commitdiff
cpuidle.chart.py: add plugin for monitoring cpuidle states
authorSteven Noonan <steven@uplinklabs.net>
Mon, 9 Jan 2017 22:20:05 +0000 (14:20 -0800)
committerSteven Noonan <steven@uplinklabs.net>
Tue, 10 Jan 2017 03:00:23 +0000 (19:00 -0800)
Signed-off-by: Steven Noonan <steven@uplinklabs.net>
python.d/Makefile.am
python.d/README.md
python.d/cpuidle.chart.py [new file with mode: 0644]

index 6a4262b30520b416a9f0d4c0cabc2706460b0ede..7667025b102dfa74ce5fe21a9397cd148b0b0bf5 100644 (file)
@@ -12,6 +12,7 @@ dist_python_SCRIPTS = \
     apache_cache.chart.py \
     bind_rndc.chart.py \
     cpufreq.chart.py \
+    cpuidle.chart.py \
     dovecot.chart.py \
     example.chart.py \
     exim.chart.py \
index b7fb4e9f4706a04f9116f20f7cc4ef2ec61d7227..3a97da5cbb90395acbb255af270f1ab34bea1faa 100644 (file)
@@ -209,6 +209,18 @@ Directory is also prefixed with `NETDATA_HOST_PREFIX` if specified.
 
 ---
 
+# cpuidle
+
+This module monitors the usage of CPU idle states.
+
+**Requirement:**
+Your kernel needs to have `CONFIG_CPU_IDLE` enabled.
+
+It produces one stacked chart per CPU, showing the percentage of time spent in
+each state.
+
+---
+
 # dovecot
 
 This module provides statistics information from dovecot server. 
diff --git a/python.d/cpuidle.chart.py b/python.d/cpuidle.chart.py
new file mode 100644 (file)
index 0000000..f7199ae
--- /dev/null
@@ -0,0 +1,143 @@
+# -*- coding: utf-8 -*-
+# Description: cpuidle netdata python.d module
+# Author: Steven Noonan (tycho)
+
+import glob
+import os
+import platform
+import time
+from base import SimpleService
+
+import ctypes
+syscall = ctypes.CDLL('libc.so.6').syscall
+
+# default module values (can be overridden per job in `config`)
+# update_every = 2
+
+class Service(SimpleService):
+    def __init__(self, configuration=None, name=None):
+        prefix = os.getenv('NETDATA_HOST_PREFIX', "")
+        if prefix.endswith('/'):
+            prefix = prefix[:-1]
+        self.sys_dir = prefix + "/sys/devices/system/cpu"
+        self.schedstat_path = prefix + "/proc/schedstat"
+        SimpleService.__init__(self, configuration=configuration, name=name)
+        self.order = []
+        self.definitions = {}
+        self._orig_name = ""
+        self.assignment = {}
+
+    def __gettid(self):
+        # This is horrendous. We need the *thread id* (not the *process id*),
+        # but there's no Python standard library way of doing that. If you need
+        # to enable this module on a non-x86 machine type, you'll have to find
+        # the Linux syscall number for gettid() and add it to the dictionary
+        # below.
+        syscalls = {
+            'i386':    224,
+            'x86_64':  186,
+        }
+        if platform.machine() not in syscalls:
+            return None
+        tid = syscall(syscalls[platform.machine()])
+        return tid
+
+    def __wake_cpus(self):
+        # Requires Python 3.3+. This will "tickle" each CPU to force it to
+        # update its idle counters.
+        if hasattr(os, 'sched_setaffinity'):
+            pid = self.__gettid()
+            save_affinity = os.sched_getaffinity(pid)
+            for idx in range(0, len(self.assignment)):
+                os.sched_setaffinity(pid, [idx])
+                os.sched_getaffinity(pid)
+            os.sched_setaffinity(pid, save_affinity)
+
+    def __read_schedstat(self):
+        cpus = {}
+        for line in open(self.schedstat_path, 'r'):
+            if not line.startswith('cpu'):
+                continue
+            line = line.rstrip().split()
+            cpu = line[0]
+            active_time = line[7]
+            cpus[cpu] = int(active_time) // 1000
+        return cpus
+
+    def _get_data(self):
+        results = {}
+
+        # This line is critical for the stats to update. If we don't "tickle"
+        # all the CPUs, then all the counters stop counting.
+        self.__wake_cpus()
+
+        # Use the kernel scheduler stats to determine how much time was spent
+        # in C0 (active).
+        schedstat = self.__read_schedstat()
+
+        for cpu, metrics in self.assignment.items():
+            update_time = schedstat[cpu]
+            results[cpu + '_active_time'] = update_time
+
+            for metric, path in metrics.items():
+                residency = int(open(path, 'r').read())
+                results[metric] = residency
+
+        return results
+
+    def check(self):
+        if self.__gettid() is None:
+            self.error("Cannot get thread ID. Stats would be completely broken.")
+            return False
+
+        self._orig_name = self.chart_name
+
+        for path in sorted(glob.glob(self.sys_dir + '/cpu*/cpuidle/state*/name')):
+            # ['', 'sys', 'devices', 'system', 'cpu', 'cpu0', 'cpuidle', 'state3', 'name']
+            path_elem = path.split('/')
+            cpu = path_elem[-4]
+            state = path_elem[-2]
+            statename = open(path, 'rt').read().rstrip()
+
+            orderid = '%s_cpuidle' % (cpu,)
+            if orderid not in self.definitions:
+                self.order.append(orderid)
+                active_name = '%s_active_time' % (cpu,)
+                self.definitions[orderid] = {
+                    'options': [None, 'C-state residency', 'time%', 'cpuidle', None, 'stacked'],
+                    'lines': [
+                        [active_name, 'C0 (active)', 'percentage-of-incremental-row', 1, 1],
+                    ],
+                }
+                self.assignment[cpu] = {}
+
+            defid = '%s_%s_time' % (orderid, state)
+
+            self.definitions[orderid]['lines'].append(
+                [defid, statename, 'percentage-of-incremental-row', 1, 1]
+            )
+
+            self.assignment[cpu][defid] = '/'.join(path_elem[:-1] + ['time'])
+
+        # Sort order by kernel-specified CPU index
+        self.order.sort(key=lambda x: int(x.split('_')[0][3:]))
+
+        if len(self.definitions) == 0:
+            self.error("couldn't find cstate stats")
+            return False
+
+        return True
+
+    def create(self):
+        self.chart_name = "cpu"
+        status = SimpleService.create(self)
+        self.chart_name = self._orig_name
+        return status
+
+    def update(self, interval):
+        self.chart_name = "cpu"
+        status = SimpleService.update(self, interval=interval)
+        self.chart_name = self._orig_name
+        return status
+
+# vim: set ts=4 sts=4 sw=4 et: