1
+# -*- coding: utf-8 -*-
2
+# Description: Linux power_supply netdata python.d module
3
+# Author: Austin S. Hemmelgarn (Ferroin)
4
+
5
+import os
6
+import platform
7
+
8
+from bases.FrameworkServices.SimpleService import SimpleService
9
+
10
+# Everything except percentages is reported as µ units.
11
+PRECISION = 10 ** 6
12
+
13
+# A priority of 90000 places us next to the other PSU related stuff.
14
+PRIORITY = 90000
15
+
16
+# We add our charts dynamically when we probe for the device attributes,
17
+# so these are empty by default.
18
+ORDER = []
19
+
20
+CHARTS = {}
21
+
22
+
23
+def get_capacity_chart(syspath):
24
+ # Capacity is measured in percent. We track one value.
25
+ options = [None, 'Capacity', '%', 'power_supply', 'power_supply.capacity', 'line']
26
+ lines = list()
27
+ attr_now = 'capacity'
28
+ if get_sysfs_value(os.path.join(syspath, attr_now)) is not None:
29
+ lines.append([attr_now, attr_now, 'absolute', 1, 1])
30
+ return {'capacity': {'options': options, 'lines': lines}}, [attr_now]
31
+ else:
32
+ return None, None
33
+
34
+
35
+def get_generic_chart(syspath, name, unit, maxname, minname):
36
+ # Used to generate charts for energy, charge, and voltage.
37
+ options = [None, name.title(), unit, 'power_supply', 'power_supply.{0}'.format(name), 'line']
38
+ lines = list()
39
+ attrlist = list()
40
+ attr_max_design = '{0}_{1}_design'.format(name, maxname)
41
+ attr_max = '{0}_{1}'.format(name, maxname)
42
+ attr_now = '{0}_now'.format(name)
43
+ attr_min = '{0}_{1}'.format(name, minname)
44
+ attr_min_design = '{0}_{1}_design'.format(name, minname)
45
+ if get_sysfs_value(os.path.join(syspath, attr_now)) is not None:
46
+ lines.append([attr_now, attr_now, 'absolute', 1, PRECISION])
47
+ attrlist.append(attr_now)
48
+ else:
49
+ return None, None
50
+ if get_sysfs_value(os.path.join(syspath, attr_max)) is not None:
51
+ lines.insert(0, [attr_max, attr_max, 'absolute', 1, PRECISION])
52
+ lines.append([attr_min, attr_min, 'absolute', 1, PRECISION])
53
+ attrlist.append(attr_max)
54
+ attrlist.append(attr_min)
55
+ elif get_sysfs_value(os.path.join(syspath, attr_min)) is not None:
56
+ lines.append([attr_min, attr_min, 'absolute', 1, PRECISION])
57
+ attrlist.append(attr_min)
58
+ if get_sysfs_value(os.path.join(syspath, attr_max_design)) is not None:
59
+ lines.insert(0, [attr_max_design, attr_max_design, 'absolute', 1, PRECISION])
60
+ lines.append([attr_min_design, attr_min_design, 'absolute', 1, PRECISION])
61
+ attrlist.append(attr_max_design)
62
+ attrlist.append(attr_min_design)
63
+ elif get_sysfs_value(os.path.join(syspath, attr_min_design)) is not None:
64
+ lines.append([attr_min_design, attr_min_design, 'absolute', 1, PRECISION])
65
+ attrlist.append(attr_min_design)
66
+ return {name: {'options': options, 'lines': lines}}, attrlist
67
+
68
+
69
+def get_charge_chart(syspath):
70
+ # Charge is measured in microamphours. We track up to five
71
+ # attributes.
72
+ return get_generic_chart(syspath, 'charge', 'µAh', 'full', 'empty')
73
+
74
+
75
+def get_energy_chart(syspath):
76
+ # Energy is measured in microwatthours. We track up to five
77
+ # attributes.
78
+ return get_generic_chart(syspath, 'energy', 'µWh', 'full', 'empty')
79
+
80
+
81
+def get_voltage_chart(syspath):
82
+ # Voltage is measured in microvolts. We track up to five attributes.
83
+ return get_generic_chart(syspath, 'voltage', 'µV', 'min', 'max')
84
+
85
+
86
+# This is a list of functions for generating charts. Used below to save
87
+# a bit of code (and to make it a bit easier to add new charts).
88
+GET_CHART = {
89
+ 'capacity': get_capacity_chart,
90
+ 'charge': get_charge_chart,
91
+ 'energy': get_energy_chart,
92
+ 'voltage': get_voltage_chart
93
+}
94
+
95
+
96
+# This opens the specified file and returns the value in it or None if
97
+# the file doesn't exist.
98
+def get_sysfs_value(filepath):
99
+ try:
100
+ with open(filepath, 'r') as datasource:
101
+ return int(datasource.read())
102
+ except (OSError, IOError):
103
+ return None
104
+
105
+
106
+class Service(SimpleService):
107
+ def __init__(self, configuration=None, name=None):
108
+ SimpleService.__init__(self, configuration=configuration, name=name)
109
+ self.definitions = dict()
110
+ self.order = list()
111
+ self.attrlist = list()
112
+ self.supply = self.configuration.get('supply', None)
113
+ if self.supply is not None:
114
+ self.syspath = '/sys/class/power_supply/{0}'.format(self.supply)
115
+ self.types = self.configuration.get('charts', 'capacity').split()
116
+
117
+ def check(self):
118
+ if platform.system() != 'Linux':
119
+ self.error('Only supported on Linux.')
120
+ return False
121
+ if self.supply is None:
122
+ self.error('No power supply specified for monitoring.')
123
+ return False
124
+ if not self.types:
125
+ self.error('No attributes requested for monitoring.')
126
+ return False
127
+ if not os.access(self.syspath, os.R_OK):
128
+ self.error('Unable to access {0}'.format(self.syspath))
129
+ return False
130
+ return self.create_charts()
131
+
132
+ def create_charts(self):
133
+ chartset = set(GET_CHART).intersection(set(self.types))
134
+ if not chartset:
135
+ self.error('No valid attributes requested for monitoring.')
136
+ return False
137
+ charts = dict()
138
+ attrlist = list()
139
+ for item in chartset:
140
+ chart, attrs = GET_CHART[item](self.syspath)
141
+ if chart is not None:
142
+ charts.update(chart)
143
+ attrlist.extend(attrs)
144
+ if len(charts) == 0:
145
+ self.error('No charts can be created.')
146
+ return False
147
+ self.definitions.update(charts)
148
+ self.order.extend(sorted(charts))
149
+ self.attrlist.extend(attrlist)
150
+ return True
151
+
152
+ def _get_data(self):
153
+ data = dict()
154
+ for attr in self.attrlist:
155
+ attrpath = os.path.join(self.syspath, attr)
156
+ if attr.endswith(('_min', '_min_design', '_empty', '_empty_design')):
157
+ data[attr] = get_sysfs_value(attrpath) or 0
158
+ else:
159
+ data[attr] = get_sysfs_value(attrpath)
160
+ return data