]> arthur.barton.de Git - netdata.git/blob - python.d/cpuidle.chart.py
updated configs.signatures
[netdata.git] / python.d / cpuidle.chart.py
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: