Completed
Push — master ( 793552...8b5b19 )
by Nicolas
05:48 queued 01:54
created

glances.plugins.glances_smart.Plugin.msg_curse()   B

Complexity

Conditions 5

Size

Total Lines 32
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 21
nop 3
dl 0
loc 32
rs 8.9093
c 0
b 0
f 0
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
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (107/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
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.
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (95/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
45
"""
46
47
from glances.plugins.glances_plugin import GlancesPlugin
0 ignored issues
show
introduced by
import missing from __future__ import absolute_import
Loading history...
48
from glances.logger import logger
0 ignored issues
show
introduced by
import missing from __future__ import absolute_import
Loading history...
49
from glances.main import disable
0 ignored issues
show
introduced by
import missing from __future__ import absolute_import
Loading history...
50
from glances.compat import is_admin
0 ignored issues
show
introduced by
import missing from __future__ import absolute_import
Loading history...
51
52
# Import plugin specific dependency
53
try:
54
    from pySMART import DeviceList
0 ignored issues
show
introduced by
import missing from __future__ import absolute_import
Loading history...
55
except ImportError as e:
0 ignored issues
show
Coding Style Naming introduced by
The name e does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
56
    import_error_tag = True
0 ignored issues
show
Coding Style Naming introduced by
The name import_error_tag does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
57
    logger.warning("Missing Python Lib ({}), HDD Smart plugin is disabled".format(e))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (85/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
introduced by
Use formatting in logging functions and pass the parameters as arguments
Loading history...
58
else:
59
    import_error_tag = False
0 ignored issues
show
Coding Style Naming introduced by
The name import_error_tag does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
60
61
62
def convert_attribute_to_dict(attr):
0 ignored issues
show
Coding Style introduced by
This function should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
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
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (95/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
82
             also has keys of the SMART attribute id, with value of another dict of the attributes
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (98/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
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
    devlist = DeviceList()
99
100
    for dev in devlist.devices:
101
        stats.append({
102
            'DeviceName': '{} {}'.format(dev.name, dev.model),
103
        })
104
        for attribute in dev.attributes:
105
            if attribute is None:
106
                pass
107
            else:
108
                attribdict = convert_attribute_to_dict(attribute)
109
110
                # we will use the attribute number as the key
111
                num = attribdict.pop('num', None)
112
                try:
113
                    assert num is not None
114
                except Exception as e:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
Coding Style Naming introduced by
The name e does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Unused Code introduced by
The variable e seems to be unused.
Loading history...
115
                    # we should never get here, but if we do, continue to next iteration and skip this attribute
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (112/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
116
                    continue
117
118
                stats[-1][num] = attribdict
119
    return stats
120
121
122
class Plugin(GlancesPlugin):
123
    """
124
    Glances' HDD SMART plugin.
125
126
    stats is a list of dicts
127
    """
128
129
    def __init__(self,
0 ignored issues
show
Bug Best Practice introduced by
The default value [] might cause unintended side-effects.

Objects as default values are only created once in Python and not on each invocation of the function. If the default object is modified, this modification is carried over to the next invocation of the method.

# Bad:
# If array_param is modified inside the function, the next invocation will
# receive the modified object.
def some_function(array_param=[]):
    # ...

# Better: Create an array on each invocation
def some_function(array_param=None):
    array_param = array_param or []
    # ...
Loading history...
130
                 args=None,
131
                 config=None,
132
                 stats_init_value=[]):
133
        """Init the plugin."""
134
        # check if user is admin
135
        if not is_admin():
136
            disable(args, "smart")
137
            logger.debug("Current user is not admin, HDD SMART plugin disabled.")
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (81/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
138
139
        super(Plugin, self).__init__(args=args, config=config)
140
141
        # We want to display the stat in the curse interface
142
        self.display_curse = True
143
144
    @GlancesPlugin._check_decorator
145
    @GlancesPlugin._log_result_decorator
146
    def update(self):
147
        """Update SMART stats using the input method."""
148
        # Init new stats
149
        stats = self.get_init_value()
150
151
        if import_error_tag:
152
            return self.stats
153
154
        if self.input_method == 'local':
155
            stats = get_smart_data()
156
        elif self.input_method == 'snmp':
157
            pass
158
159
        # Update the stats
160
        self.stats = stats
161
162
        return self.stats
163
164
    def get_key(self):
165
        """Return the key of the list."""
166
        return 'DeviceName'
167
168
    def msg_curse(self, args=None, max_width=None):
169
        """Return the dict to display in the curse interface."""
170
        # Init the return message
171
        ret = []
172
173
        # Only process if stats exist...
174
        if not self.stats or self.is_disable():
175
            return ret
176
177
        # Max size for the interface name
178
        name_max_width = max_width - 6
179
180
        # Header
181
        msg = '{:{width}}'.format('SMART disks',
182
                                  width=name_max_width)
183
        ret.append(self.curse_add_line(msg, "TITLE"))
184
        # Data
185
        for device_stat in self.stats:
186
            # New line
187
            ret.append(self.curse_new_line())
188
            msg = '{:{width}}'.format(device_stat['DeviceName'][:max_width],
189
                                      width=max_width)
190
            ret.append(self.curse_add_line(msg))
191
            for smart_stat in sorted([i for i in device_stat.keys() if i != 'DeviceName'], key=int):
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (100/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
192
                ret.append(self.curse_new_line())
193
                msg = ' {:{width}}'.format(device_stat[smart_stat]['name'][:name_max_width-1].replace('_', ' '),
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (112/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
194
                                          width=name_max_width-1)
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (add 1 space).
Loading history...
195
                ret.append(self.curse_add_line(msg))
196
                msg = '{:>8}'.format(device_stat[smart_stat]['raw'])
197
                ret.append(self.curse_add_line(msg))
198
199
        return ret
200