Test Failed
Push — master ( aea994...69b639 )
by Nicolas
03:44 queued 10s
created

glances.plugins.glances_smart.get_smart_data()   B

Complexity

Conditions 6

Size

Total Lines 52
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 25
nop 0
dl 0
loc 52
rs 8.3466
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2018 Tim Nibert <[email protected]>
6
#
7
# Glances is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU Lesser General Public License as published by
9
# the Free Software Foundation, either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Glances is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU Lesser General Public License for more details.
16
#
17
# You should have received a copy of the GNU Lesser General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20
"""
21
Hard disk SMART attributes plugin.
22
Depends on pySMART and smartmontools
23
Must execute as root
24
"usermod -a -G disk USERNAME" is not sufficient unfortunately
25
SmartCTL (/usr/sbin/smartctl) must be in system path for python2.
26
27
Regular PySMART is a python2 library.
28
We are using the pySMART.smartx updated library to support both python 2 and 3.
29
30
If we only have disk group access (no root):
31
$ smartctl -i /dev/sda
32
smartctl 6.6 2016-05-31 r4324 [x86_64-linux-4.15.0-30-generic] (local build)
33
Copyright (C) 2002-16, Bruce Allen, Christian Franke, www.smartmontools.org
34
35
36
Probable ATA device behind a SAT layer
37
Try an additional '-d ata' or '-d sat' argument.
38
39
This is not very hopeful: https://medium.com/opsops/why-smartctl-could-not-be-run-without-root-7ea0583b1323
40
41
So, here is what we are going to do:
42
Check for admin access.  If no admin access, disable SMART plugin.
43
44
If smartmontools is not installed, we should catch the error upstream in plugin initialization.
45
"""
46
47
from glances.plugins.glances_plugin import GlancesPlugin
48
from glances.logger import logger
49
from glances.main import disable
50
from glances.compat import is_admin
51
52
# Import plugin specific dependency
53
try:
54
    from pySMART import DeviceList
55
except ImportError as e:
56
    import_error_tag = True
57
    logger.warning("Missing Python Lib ({}), HDD Smart plugin is disabled".format(e))
58
else:
59
    import_error_tag = False
60
61
62
def convert_attribute_to_dict(attr):
63
    return {
64
        'name': attr.name,
65
        'num': attr.num,
66
        'flags': attr.flags,
67
        'raw': attr.raw,
68
        'value': attr.value,
69
        'worst': attr.worst,
70
        'threshold': attr.thresh,
71
        'type': attr.type,
72
        'updated': attr.updated,
73
        'when_failed': attr.when_failed,
74
    }
75
76
77
def get_smart_data():
78
    """
79
    Get SMART attribute data
80
    :return: list of multi leveled dictionaries
81
             each dict has a key "DeviceName" with the identification of the device in smartctl
82
             also has keys of the SMART attribute id, with value of another dict of the attributes
83
             [
84
                {
85
                    "DeviceName": "/dev/sda blahblah",
86
                    "1":
87
                    {
88
                        "flags": "..",
89
                        "raw": "..",
90
                        etc,
91
                    }
92
                    ...
93
                }
94
             ]
95
    """
96
    stats = []
97
    # get all devices
98
    try:
99
        devlist = DeviceList()
100
    except TypeError as e:
101
        # Catch error  (see #1806)
102
        logger.debug(
103
            'Smart plugin error - Can not grab device list ({})'.format(e))
104
        global import_error_tag
105
        import_error_tag = True
106
        return stats
107
108
    for dev in devlist.devices:
109
        stats.append({
110
            'DeviceName': '{} {}'.format(dev.name, dev.model),
111
        })
112
        for attribute in dev.attributes:
113
            if attribute is None:
114
                pass
115
            else:
116
                attribdict = convert_attribute_to_dict(attribute)
117
118
                # we will use the attribute number as the key
119
                num = attribdict.pop('num', None)
120
                try:
121
                    assert num is not None
122
                except Exception as e:
123
                    # we should never get here, but if we do, continue to next iteration and skip this attribute
124
                    logger.debug('Smart plugin error - Skip the attribute {} ({})'.format(attribute, e))
125
                    continue
126
127
                stats[-1][num] = attribdict
128
    return stats
129
130
131
class Plugin(GlancesPlugin):
132
    """
133
    Glances' HDD SMART plugin.
134
135
    stats is a list of dicts
136
    """
137
138
    def __init__(self,
139
                 args=None,
140
                 config=None,
141
                 stats_init_value=[]):
142
        """Init the plugin."""
143
        # check if user is admin
144
        if not is_admin():
145
            disable(args, "smart")
146
            logger.debug("Current user is not admin, HDD SMART plugin disabled.")
147
148
        super(Plugin, self).__init__(args=args, config=config)
149
150
        # We want to display the stat in the curse interface
151
        self.display_curse = True
152
153
    @GlancesPlugin._check_decorator
154
    @GlancesPlugin._log_result_decorator
155
    def update(self):
156
        """Update SMART stats using the input method."""
157
        # Init new stats
158
        stats = self.get_init_value()
159
160
        if import_error_tag:
161
            return self.stats
162
163
        if self.input_method == 'local':
164
            stats = get_smart_data()
165
        elif self.input_method == 'snmp':
166
            pass
167
168
        # Update the stats
169
        self.stats = stats
170
171
        return self.stats
172
173
    def get_key(self):
174
        """Return the key of the list."""
175
        return 'DeviceName'
176
177
    def msg_curse(self, args=None, max_width=None):
178
        """Return the dict to display in the curse interface."""
179
        # Init the return message
180
        ret = []
181
182
        # Only process if stats exist...
183
        if import_error_tag or not self.stats or self.is_disable():
184
            return ret
185
186
        # Max size for the interface name
187
        name_max_width = max_width - 6
188
189
        # Header
190
        msg = '{:{width}}'.format('SMART disks',
191
                                  width=name_max_width)
192
        ret.append(self.curse_add_line(msg, "TITLE"))
193
        # Data
194
        for device_stat in self.stats:
195
            # New line
196
            ret.append(self.curse_new_line())
197
            msg = '{:{width}}'.format(device_stat['DeviceName'][:max_width],
198
                                      width=max_width)
199
            ret.append(self.curse_add_line(msg))
200
            for smart_stat in sorted([i for i in device_stat.keys() if i != 'DeviceName'], key=int):
201
                ret.append(self.curse_new_line())
202
                msg = ' {:{width}}'.format(device_stat[smart_stat]['name'][:name_max_width-1].replace('_', ' '),
203
                                          width=name_max_width-1)
204
                ret.append(self.curse_add_line(msg))
205
                msg = '{:>8}'.format(device_stat[smart_stat]['raw'])
206
                ret.append(self.curse_add_line(msg))
207
208
        return ret
209