summaryrefslogtreecommitdiffstats
path: root/python.d/cpuidle.chart.py
blob: f7199aebd95eaaf673576198d3754ac18ebe3786 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
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: