cpuidle.chart.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. # -*- coding: utf-8 -*-
  2. # Description: cpuidle netdata python.d module
  3. # Author: Steven Noonan (tycho)
  4. import glob
  5. import os
  6. import platform
  7. from bases.FrameworkServices.SimpleService import SimpleService
  8. import ctypes
  9. syscall = ctypes.CDLL('libc.so.6').syscall
  10. # default module values (can be overridden per job in `config`)
  11. # update_every = 2
  12. class Service(SimpleService):
  13. def __init__(self, configuration=None, name=None):
  14. prefix = os.getenv('NETDATA_HOST_PREFIX', "")
  15. if prefix.endswith('/'):
  16. prefix = prefix[:-1]
  17. self.sys_dir = prefix + "/sys/devices/system/cpu"
  18. self.schedstat_path = prefix + "/proc/schedstat"
  19. SimpleService.__init__(self, configuration=configuration, name=name)
  20. self.order = []
  21. self.definitions = {}
  22. self.fake_name = 'cpu'
  23. self.assignment = {}
  24. self.last_schedstat = None
  25. @staticmethod
  26. def __gettid():
  27. # This is horrendous. We need the *thread id* (not the *process id*),
  28. # but there's no Python standard library way of doing that. If you need
  29. # to enable this module on a non-x86 machine type, you'll have to find
  30. # the Linux syscall number for gettid() and add it to the dictionary
  31. # below.
  32. syscalls = {
  33. 'i386': 224,
  34. 'x86_64': 186,
  35. }
  36. if platform.machine() not in syscalls:
  37. return None
  38. tid = syscall(syscalls[platform.machine()])
  39. return tid
  40. def __wake_cpus(self, cpus):
  41. # Requires Python 3.3+. This will "tickle" each CPU to force it to
  42. # update its idle counters.
  43. if hasattr(os, 'sched_setaffinity'):
  44. pid = self.__gettid()
  45. save_affinity = os.sched_getaffinity(pid)
  46. for idx in cpus:
  47. os.sched_setaffinity(pid, [idx])
  48. os.sched_getaffinity(pid)
  49. os.sched_setaffinity(pid, save_affinity)
  50. def __read_schedstat(self):
  51. cpus = {}
  52. for line in open(self.schedstat_path, 'r'):
  53. if not line.startswith('cpu'):
  54. continue
  55. line = line.rstrip().split()
  56. cpu = line[0]
  57. active_time = line[7]
  58. cpus[cpu] = int(active_time) // 1000
  59. return cpus
  60. def _get_data(self):
  61. results = {}
  62. # Use the kernel scheduler stats to determine how much time was spent
  63. # in C0 (active).
  64. schedstat = self.__read_schedstat()
  65. # Determine if any of the CPUs are idle. If they are, then we need to
  66. # tickle them in order to update their C-state residency statistics.
  67. if self.last_schedstat is None:
  68. needs_tickle = list(self.assignment.keys())
  69. else:
  70. needs_tickle = []
  71. for cpu, active_time in self.last_schedstat.items():
  72. delta = schedstat[cpu] - active_time
  73. if delta < 1:
  74. needs_tickle.append(cpu)
  75. if needs_tickle:
  76. # This line is critical for the stats to update. If we don't "tickle"
  77. # idle CPUs, then the counters for those CPUs stop counting.
  78. self.__wake_cpus([int(cpu[3:]) for cpu in needs_tickle])
  79. # Re-read schedstat now that we've tickled any idlers.
  80. schedstat = self.__read_schedstat()
  81. self.last_schedstat = schedstat
  82. for cpu, metrics in self.assignment.items():
  83. update_time = schedstat[cpu]
  84. results[cpu + '_active_time'] = update_time
  85. for metric, path in metrics.items():
  86. residency = int(open(path, 'r').read())
  87. results[metric] = residency
  88. return results
  89. def check(self):
  90. if self.__gettid() is None:
  91. self.error("Cannot get thread ID. Stats would be completely broken.")
  92. return False
  93. for path in sorted(glob.glob(self.sys_dir + '/cpu*/cpuidle/state*/name')):
  94. # ['', 'sys', 'devices', 'system', 'cpu', 'cpu0', 'cpuidle', 'state3', 'name']
  95. path_elem = path.split('/')
  96. cpu = path_elem[-4]
  97. state = path_elem[-2]
  98. statename = open(path, 'rt').read().rstrip()
  99. orderid = '%s_cpuidle' % (cpu,)
  100. if orderid not in self.definitions:
  101. self.order.append(orderid)
  102. active_name = '%s_active_time' % (cpu,)
  103. self.definitions[orderid] = {
  104. 'options': [None, 'C-state residency', 'time%', 'cpuidle', 'cpuidle.cpuidle', 'stacked'],
  105. 'lines': [
  106. [active_name, 'C0 (active)', 'percentage-of-incremental-row', 1, 1],
  107. ],
  108. }
  109. self.assignment[cpu] = {}
  110. defid = '%s_%s_time' % (orderid, state)
  111. self.definitions[orderid]['lines'].append(
  112. [defid, statename, 'percentage-of-incremental-row', 1, 1]
  113. )
  114. self.assignment[cpu][defid] = '/'.join(path_elem[:-1] + ['time'])
  115. # Sort order by kernel-specified CPU index
  116. self.order.sort(key=lambda x: int(x.split('_')[0][3:]))
  117. if len(self.definitions) == 0:
  118. self.error("couldn't find cstate stats")
  119. return False
  120. return True