mirror of
https://github.com/netdata/netdata.git
synced 2025-04-12 08:48:12 +00:00
148 lines
5.2 KiB
Python
148 lines
5.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Description: cpuidle netdata python.d module
|
|
# Author: Steven Noonan (tycho)
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
import ctypes
|
|
import glob
|
|
import os
|
|
import platform
|
|
|
|
from bases.FrameworkServices.SimpleService import SimpleService
|
|
|
|
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.fake_name = 'cpu'
|
|
self.assignment = {}
|
|
self.last_schedstat = None
|
|
|
|
@staticmethod
|
|
def __gettid():
|
|
# 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, cpus):
|
|
# 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 cpus:
|
|
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 = {}
|
|
|
|
# Use the kernel scheduler stats to determine how much time was spent
|
|
# in C0 (active).
|
|
schedstat = self.__read_schedstat()
|
|
|
|
# Determine if any of the CPUs are idle. If they are, then we need to
|
|
# tickle them in order to update their C-state residency statistics.
|
|
if self.last_schedstat is None:
|
|
needs_tickle = list(self.assignment.keys())
|
|
else:
|
|
needs_tickle = []
|
|
for cpu, active_time in self.last_schedstat.items():
|
|
delta = schedstat[cpu] - active_time
|
|
if delta < 1:
|
|
needs_tickle.append(cpu)
|
|
|
|
if needs_tickle:
|
|
# This line is critical for the stats to update. If we don't "tickle"
|
|
# idle CPUs, then the counters for those CPUs stop counting.
|
|
self.__wake_cpus([int(cpu[3:]) for cpu in needs_tickle])
|
|
|
|
# Re-read schedstat now that we've tickled any idlers.
|
|
schedstat = self.__read_schedstat()
|
|
|
|
self.last_schedstat = 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
|
|
|
|
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', 'cpuidle.cpuidle', '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 not self.definitions:
|
|
self.error("couldn't find cstate stats")
|
|
return False
|
|
|
|
return True
|