@cryptotaxi247 / netdata-1 / commits / b22a2ed78

cpufreq.chart.py: add more accurate cpufreq_stats-based calculations

There were two major problems with this module: - The 'cpuN' names weren't accurate. The 'self.paths.sort()' was trying to compensate for os.walk() enumerating the CPUs out of order (as any directory enumeration will do). Unfortunately the sort() function is alphabetical, so it would result in a list of paths like this: [ '/sys/.../cpu0/...' '/sys/.../cpu1/...' '/sys/.../cpu11/...' '/sys/.../cpu12/...' ... ] So the chart for cpu2 would actually map to cpu11's stats. This can be corrected by extracting the 'cpuN' value that's already inside the path anyway. - The scaling_cur_freq value is an instantaneous value. It only represents the current processor P-state at the time it was read, and doesn't account for the other 999ms that netdata wasn't looking at the value. This can be corrected by using data from cpufreq_stats, which includes P-state residency statistics. Note that the values in cpufreq_stats aren't always valid (e.g. if the cpufreq governor is set to 'schedutil', the statistic files exist but are are empty), so we can just fall back to the inaccurate scaling_cur_freq method if necessary. Signed-off-by: Steven Noonan <steven@uplinklabs.net>

Steven Noonan committed Jan 9, 2017 at 13:00 UTC b22a2ed789f0018d40a24aad1bebe618beeea89f
2 files changed +51 -28
python.d/README.md
+12 -4
@@ -186,13 +186,21 @@ If no configuration is given, module will attempt to read named.stats file at `
186
187 ---
188
189 -
189 # cpufreq
190
192 -Module shows current cpu frequency by looking at appropriate files in /sys/devices
191 +This module shows the current CPU frequency as set by the cpufreq kernel
192 +module.
193
194 **Requirement:**
195 -Processor which presents data scaling frequency data
195 +You need to have `CONFIG_CPU_FREQ` and (optionally) `CONFIG_CPU_FREQ_STAT`
196 +enabled in your kernel.
197 +
198 +This module tries to read from one of two possible locations. On
199 +initialization, it tries to read the `time_in_state` files provided by
200 +cpufreq\_stats. If this file does not exist, or doesn't contain valid data, it
201 +falls back to using the more inaccurate `scaling_cur_freq` file (which only
202 +represents the **current** CPU frequency, and doesn't account for any state
203 +changes which happen between updates).
204
205 It produces one chart with multiple lines (one line per core).
206
@@ -204,7 +212,7 @@ Sample:
212 sys_dir: "/sys/devices"
213 ```
214
207 -If no configuration is given, module will search for `scaling_cur_freq` files in `/sys/devices` directory.
215 +If no configuration is given, module will search for cpufreq files in `/sys/devices` directory.
216 Directory is also prefixed with `NETDATA_HOST_PREFIX` if specified.
217
218 ---
python.d/cpufreq.chart.py
+39 -24
@@ -1,7 +1,8 @@
1 # -*- coding: utf-8 -*-
2 # Description: cpufreq netdata python.d module
3 -# Author: Pawel Krupa (paulfantom)
3 +# Author: Pawel Krupa (paulfantom) and Steven Noonan (tycho)
4
5 +import glob
6 import os
7 from base import SimpleService
8
@@ -18,29 +19,31 @@ CHARTS = {
19 ]}
20 }
21
21 -
22 class Service(SimpleService):
23 def __init__(self, configuration=None, name=None):
24 prefix = os.getenv('NETDATA_HOST_PREFIX', "")
25 if prefix.endswith('/'):
26 prefix = prefix[:-1]
27 self.sys_dir = prefix + "/sys/devices"
28 - self.filename = "scaling_cur_freq"
28 SimpleService.__init__(self, configuration=configuration, name=name)
29 self.order = ORDER
30 self.definitions = CHARTS
31 self._orig_name = ""
32 self.assignment = {}
34 - self.paths = []
33 + self.accurate = True
34
35 def _get_data(self):
37 - raw = {}
38 - for path in self.paths:
39 - with open(path, 'r') as f:
40 - raw[path] = f.read()
36 data = {}
42 - for path in self.paths:
43 - data[self.assignment[path]] = raw[path]
37 + if self.accurate:
38 + for name, path in self.assignment.items():
39 + total = 0
40 + for line in open(path, 'r'):
41 + line = list(map(int, line.split()))
42 + total += (line[0] * line[1]) / 100
43 + data[name] = total
44 + else:
45 + for name, path in self.assignment.items():
46 + data[name] = open(path, 'r').read()
47 return data
48
49 def check(self):
@@ -51,23 +54,35 @@ class Service(SimpleService):
54
55 self._orig_name = self.chart_name
56
54 - for dirpath, _, filenames in os.walk(self.sys_dir):
55 - if self.filename in filenames:
56 - self.paths.append(dirpath + "/" + self.filename)
57 -
58 - if len(self.paths) == 0:
59 - self.error("cannot find", self.filename)
57 + for path in glob.glob(self.sys_dir + '/system/cpu/cpu*/cpufreq/stats/time_in_state'):
58 + if len(open(path, 'rb').read().rstrip()) == 0:
59 + self.alert("time_in_state is empty, broken cpufreq_stats data")
60 + self.assignment = {}
61 + break
62 + path_elem = path.split('/')
63 + cpu = path_elem[-4]
64 + self.assignment[cpu] = path
65 +
66 + if len(self.assignment) == 0:
67 + self.alert("trying less accurate scaling_cur_freq method")
68 + self.accurate = False
69 +
70 + for path in glob.glob(self.sys_dir + '/system/cpu/cpu*/cpufreq/scaling_cur_freq'):
71 + path_elem = path.split('/')
72 + cpu = path_elem[-3]
73 + self.assignment[cpu] = path
74 +
75 + if len(self.assignment) == 0:
76 + self.error("couldn't find a method to read cpufreq statistics")
77 return False
78
62 - self.paths.sort()
63 - i = 0
64 - for path in self.paths:
65 - self.assignment[path] = "cpu" + str(i)
66 - i += 1
79 + if self.accurate:
80 + algo = 'incremental'
81 + else:
82 + algo = 'absolute'
83
68 - for name in self.assignment:
69 - dim = self.assignment[name]
70 - self.definitions[ORDER[0]]['lines'].append([dim, dim, 'absolute', 1, 1000])
84 + for name in self.assignment.keys():
85 + self.definitions[ORDER[0]]['lines'].append([name, name, algo, 1, 1000])
86
87 return True
88