summaryrefslogtreecommitdiffstats
path: root/collectors/python.d.plugin/linux_power_supply/linux_power_supply.chart.py
blob: 71d834e5d21a3fbf3045fb0a55ded74bfb094145 (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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# -*- coding: utf-8 -*-
# Description: Linux power_supply netdata python.d module
# Author: Austin S. Hemmelgarn (Ferroin)

import os
import platform

from bases.FrameworkServices.SimpleService import SimpleService

# Everything except percentages is reported as µ units.
PRECISION = 10 ** 6

# A priority of 90000 places us next to the other PSU related stuff.
PRIORITY = 90000

# We add our charts dynamically when we probe for the device attributes,
# so these are empty by default.
ORDER = []

CHARTS = {}


def get_capacity_chart(syspath):
    # Capacity is measured in percent.  We track one value.
    options = [None, 'Capacity', '%', 'power_supply', 'power_supply.capacity', 'line']
    lines = list()
    attr_now = 'capacity'
    if get_sysfs_value(os.path.join(syspath, attr_now)) is not None:
        lines.append([attr_now, attr_now, 'absolute', 1, 1])
        return {'capacity': {'options': options, 'lines': lines}}, [attr_now]
    else:
        return None, None


def get_generic_chart(syspath, name, unit, maxname, minname):
    # Used to generate charts for energy, charge, and voltage.
    options = [None, name.title(), unit, 'power_supply', 'power_supply.{0}'.format(name), 'line']
    lines = list()
    attrlist = list()
    attr_max_design = '{0}_{1}_design'.format(name, maxname)
    attr_max = '{0}_{1}'.format(name, maxname)
    attr_now = '{0}_now'.format(name)
    attr_min = '{0}_{1}'.format(name, minname)
    attr_min_design = '{0}_{1}_design'.format(name, minname)
    if get_sysfs_value(os.path.join(syspath, attr_now)) is not None:
        lines.append([attr_now, attr_now, 'absolute', 1, PRECISION])
        attrlist.append(attr_now)
    else:
        return None, None
    if get_sysfs_value(os.path.join(syspath, attr_max)) is not None:
        lines.insert(0, [attr_max, attr_max, 'absolute', 1, PRECISION])
        lines.append([attr_min, attr_min, 'absolute', 1, PRECISION])
        attrlist.append(attr_max)
        attrlist.append(attr_min)
    elif get_sysfs_value(os.path.join(syspath, attr_min)) is not None:
        lines.append([attr_min, attr_min, 'absolute', 1, PRECISION])
        attrlist.append(attr_min)
    if get_sysfs_value(os.path.join(syspath, attr_max_design)) is not None:
        lines.insert(0, [attr_max_design, attr_max_design, 'absolute', 1, PRECISION])
        lines.append([attr_min_design, attr_min_design, 'absolute', 1, PRECISION])
        attrlist.append(attr_max_design)
        attrlist.append(attr_min_design)
    elif get_sysfs_value(os.path.join(syspath, attr_min_design)) is not None:
        lines.append([attr_min_design, attr_min_design, 'absolute', 1, PRECISION])
        attrlist.append(attr_min_design)
    return {name: {'options': options, 'lines': lines}}, attrlist


def get_charge_chart(syspath):
    # Charge is measured in microamphours.  We track up to five
    # attributes.
    return get_generic_chart(syspath, 'charge', 'µAh', 'full', 'empty')


def get_energy_chart(syspath):
    # Energy is measured in microwatthours.  We track up to five
    # attributes.
    return get_generic_chart(syspath, 'energy', 'µWh', 'full', 'empty')


def get_voltage_chart(syspath):
    # Voltage is measured in microvolts. We track up to five attributes.
    return get_generic_chart(syspath, 'voltage', 'µV', 'min', 'max')


# This is a list of functions for generating charts.  Used below to save
# a bit of code (and to make it a bit easier to add new charts).
GET_CHART = {
    'capacity': get_capacity_chart,
    'charge': get_charge_chart,
    'energy': get_energy_chart,
    'voltage': get_voltage_chart
}


# This opens the specified file and returns the value in it or None if
# the file doesn't exist.
def get_sysfs_value(filepath):
    try:
        with open(filepath, 'r') as datasource:
            return int(datasource.read())
    except (OSError, IOError):
        return None


class Service(SimpleService):
    def __init__(self, configuration=None, name=None):
        SimpleService.__init__(self, configuration=configuration, name=name)
        self.definitions = dict()
        self.order = list()
        self.attrlist = list()
        self.supply = self.configuration.get('supply', None)
        if self.supply is not None:
            self.syspath = '/sys/class/power_supply/{0}'.format(self.supply)
        self.types = self.configuration.get('charts', 'capacity').split()

    def check(self):
        if platform.system() != 'Linux':
            self.error('Only supported on Linux.')
            return False
        if self.supply is None:
            self.error('No power supply specified for monitoring.')
            return False
        if not self.types:
            self.error('No attributes requested for monitoring.')
            return False
        if not os.access(self.syspath, os.R_OK):
            self.error('Unable to access {0}'.format(self.syspath))
            return False
        return self.create_charts()

    def create_charts(self):
        chartset = set(GET_CHART).intersection(set(self.types))
        if not chartset:
            self.error('No valid attributes requested for monitoring.')
            return False
        charts = dict()
        attrlist = list()
        for item in chartset:
            chart, attrs = GET_CHART[item](self.syspath)
            if chart is not None:
                charts.update(chart)
                attrlist.extend(attrs)
        if len(charts) == 0:
            self.error('No charts can be created.')
            return False
        self.definitions.update(charts)
        self.order.extend(sorted(charts))
        self.attrlist.extend(attrlist)
        return True

    def _get_data(self):
        data = dict()
        for attr in self.attrlist:
            attrpath = os.path.join(self.syspath, attr)
            if attr.endswith(('_min', '_min_design', '_empty', '_empty_design')):
                data[attr] = get_sysfs_value(attrpath) or 0
            else:
                data[attr] = get_sysfs_value(attrpath)
        return data