@cryptotaxi247 / netdata-1 / commits / 70daed00e

cpuidle.chart.py: add plugin for monitoring cpuidle states

Signed-off-by: Steven Noonan <steven@uplinklabs.net>

Steven Noonan committed Jan 9, 2017 at 14:20 UTC 70daed00e400613b10a059fe176463c43a16ade0
3 files changed +156
python.d/Makefile.am
+1
@@ -12,6 +12,7 @@ dist_python_SCRIPTS = \
12 apache_cache.chart.py \
13 bind_rndc.chart.py \
14 cpufreq.chart.py \
15 + cpuidle.chart.py \
16 dovecot.chart.py \
17 example.chart.py \
18 exim.chart.py \
python.d/README.md
+12
@@ -209,6 +209,18 @@ Directory is also prefixed with `NETDATA_HOST_PREFIX` if specified.
209
210 ---
211
212 +# cpuidle
213 +
214 +This module monitors the usage of CPU idle states.
215 +
216 +**Requirement:**
217 +Your kernel needs to have `CONFIG_CPU_IDLE` enabled.
218 +
219 +It produces one stacked chart per CPU, showing the percentage of time spent in
220 +each state.
221 +
222 +---
223 +
224 # dovecot
225
226 This module provides statistics information from dovecot server.
python.d/cpuidle.chart.py new
+143
@@ -0,0 +1,143 @@
1 +# -*- coding: utf-8 -*-
2 +# Description: cpuidle netdata python.d module
3 +# Author: Steven Noonan (tycho)
4 +
5 +import glob
6 +import os
7 +import platform
8 +import time
9 +from base import SimpleService
10 +
11 +import ctypes
12 +syscall = ctypes.CDLL('libc.so.6').syscall
13 +
14 +# default module values (can be overridden per job in `config`)
15 +# update_every = 2
16 +
17 +class Service(SimpleService):
18 + def __init__(self, configuration=None, name=None):
19 + prefix = os.getenv('NETDATA_HOST_PREFIX', "")
20 + if prefix.endswith('/'):
21 + prefix = prefix[:-1]
22 + self.sys_dir = prefix + "/sys/devices/system/cpu"
23 + self.schedstat_path = prefix + "/proc/schedstat"
24 + SimpleService.__init__(self, configuration=configuration, name=name)
25 + self.order = []
26 + self.definitions = {}
27 + self._orig_name = ""
28 + self.assignment = {}
29 +
30 + def __gettid(self):
31 + # This is horrendous. We need the *thread id* (not the *process id*),
32 + # but there's no Python standard library way of doing that. If you need
33 + # to enable this module on a non-x86 machine type, you'll have to find
34 + # the Linux syscall number for gettid() and add it to the dictionary
35 + # below.
36 + syscalls = {
37 + 'i386': 224,
38 + 'x86_64': 186,
39 + }
40 + if platform.machine() not in syscalls:
41 + return None
42 + tid = syscall(syscalls[platform.machine()])
43 + return tid
44 +
45 + def __wake_cpus(self):
46 + # Requires Python 3.3+. This will "tickle" each CPU to force it to
47 + # update its idle counters.
48 + if hasattr(os, 'sched_setaffinity'):
49 + pid = self.__gettid()
50 + save_affinity = os.sched_getaffinity(pid)
51 + for idx in range(0, len(self.assignment)):
52 + os.sched_setaffinity(pid, [idx])
53 + os.sched_getaffinity(pid)
54 + os.sched_setaffinity(pid, save_affinity)
55 +
56 + def __read_schedstat(self):
57 + cpus = {}
58 + for line in open(self.schedstat_path, 'r'):
59 + if not line.startswith('cpu'):
60 + continue
61 + line = line.rstrip().split()
62 + cpu = line[0]
63 + active_time = line[7]
64 + cpus[cpu] = int(active_time) // 1000
65 + return cpus
66 +
67 + def _get_data(self):
68 + results = {}
69 +
70 + # This line is critical for the stats to update. If we don't "tickle"
71 + # all the CPUs, then all the counters stop counting.
72 + self.__wake_cpus()
73 +
74 + # Use the kernel scheduler stats to determine how much time was spent
75 + # in C0 (active).
76 + schedstat = self.__read_schedstat()
77 +
78 + for cpu, metrics in self.assignment.items():
79 + update_time = schedstat[cpu]
80 + results[cpu + '_active_time'] = update_time
81 +
82 + for metric, path in metrics.items():
83 + residency = int(open(path, 'r').read())
84 + results[metric] = residency
85 +
86 + return results
87 +
88 + def check(self):
89 + if self.__gettid() is None:
90 + self.error("Cannot get thread ID. Stats would be completely broken.")
91 + return False
92 +
93 + self._orig_name = self.chart_name
94 +
95 + for path in sorted(glob.glob(self.sys_dir + '/cpu*/cpuidle/state*/name')):
96 + # ['', 'sys', 'devices', 'system', 'cpu', 'cpu0', 'cpuidle', 'state3', 'name']
97 + path_elem = path.split('/')
98 + cpu = path_elem[-4]
99 + state = path_elem[-2]
100 + statename = open(path, 'rt').read().rstrip()
101 +
102 + orderid = '%s_cpuidle' % (cpu,)
103 + if orderid not in self.definitions:
104 + self.order.append(orderid)
105 + active_name = '%s_active_time' % (cpu,)
106 + self.definitions[orderid] = {
107 + 'options': [None, 'C-state residency', 'time%', 'cpuidle', None, 'stacked'],
108 + 'lines': [
109 + [active_name, 'C0 (active)', 'percentage-of-incremental-row', 1, 1],
110 + ],
111 + }
112 + self.assignment[cpu] = {}
113 +
114 + defid = '%s_%s_time' % (orderid, state)
115 +
116 + self.definitions[orderid]['lines'].append(
117 + [defid, statename, 'percentage-of-incremental-row', 1, 1]
118 + )
119 +
120 + self.assignment[cpu][defid] = '/'.join(path_elem[:-1] + ['time'])
121 +
122 + # Sort order by kernel-specified CPU index
123 + self.order.sort(key=lambda x: int(x.split('_')[0][3:]))
124 +
125 + if len(self.definitions) == 0:
126 + self.error("couldn't find cstate stats")
127 + return False
128 +
129 + return True
130 +
131 + def create(self):
132 + self.chart_name = "cpu"
133 + status = SimpleService.create(self)
134 + self.chart_name = self._orig_name
135 + return status
136 +
137 + def update(self, interval):
138 + self.chart_name = "cpu"
139 + status = SimpleService.update(self, interval=interval)
140 + self.chart_name = self._orig_name
141 + return status
142 +
143 +# vim: set ts=4 sts=4 sw=4 et: