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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
|
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# Copyright (C) 2018 Tim Nibert <docz2a@gmail.com>
#
# Glances is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Glances is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Hard disk SMART attributes plugin.
Depends on pySMART and smartmontools
Must execute as root
"usermod -a -G disk USERNAME" is not sufficient unfortunately
SmartCTL (/usr/sbin/smartctl) must be in system path for python2.
Regular PySMART is a python2 library.
We are using the pySMART.smartx updated library to support both python 2 and 3.
If we only have disk group access (no root):
$ smartctl -i /dev/sda
smartctl 6.6 2016-05-31 r4324 [x86_64-linux-4.15.0-30-generic] (local build)
Copyright (C) 2002-16, Bruce Allen, Christian Franke, www.smartmontools.org
Probable ATA device behind a SAT layer
Try an additional '-d ata' or '-d sat' argument.
This is not very hopeful: https://medium.com/opsops/why-smartctl-could-not-be-run-without-root-7ea0583b1323
So, here is what we are going to do:
Check for admin access. If no admin access, disable SMART plugin.
If smartmontools is not installed, we should catch the error upstream in plugin initialization.
"""
from glances.plugins.glances_plugin import GlancesPlugin
from glances.logger import logger
from glances.main import disable
from glances.compat import is_admin
# Import plugin specific dependency
try:
from pySMART import DeviceList
except ImportError as e:
import_error_tag = True
logger.warning("Missing Python Lib ({}), HDD Smart plugin is disabled".format(e))
else:
import_error_tag = False
def convert_attribute_to_dict(attr):
return {
'name': attr.name,
'num': attr.num,
'flags': attr.flags,
'raw': attr.raw,
'value': attr.value,
'worst': attr.worst,
'threshold': attr.thresh,
'type': attr.type,
'updated': attr.updated,
'when_failed': attr.when_failed,
}
def get_smart_data():
"""
Get SMART attribute data
:return: list of multi leveled dictionaries
each dict has a key "DeviceName" with the identification of the device in smartctl
also has keys of the SMART attribute id, with value of another dict of the attributes
[
{
"DeviceName": "/dev/sda blahblah",
"1":
{
"flags": "..",
"raw": "..",
etc,
}
...
}
]
"""
stats = []
# get all devices
devlist = DeviceList()
for dev in devlist.devices:
stats.append({
'DeviceName': '{} {}'.format(dev.name, dev.model),
})
for attribute in dev.attributes:
if attribute is None:
pass
else:
attribdict = convert_attribute_to_dict(attribute)
# we will use the attribute number as the key
num = attribdict.pop('num', None)
try:
assert num is not None
except Exception as e:
# we should never get here, but if we do, continue to next iteration and skip this attribute
continue
stats[-1][num] = attribdict
return stats
class Plugin(GlancesPlugin):
"""
Glances' HDD SMART plugin.
stats is a list of dicts
"""
def __init__(self,
args=None,
config=None,
stats_init_value=[]):
"""Init the plugin."""
# check if user is admin
if not is_admin():
disable(args, "smart")
logger.debug("Current user is not admin, HDD SMART plugin disabled.")
super(Plugin, self).__init__(args=args, config=config)
# We want to display the stat in the curse interface
self.display_curse = True
@GlancesPlugin._check_decorator
@GlancesPlugin._log_result_decorator
def update(self):
"""Update SMART stats using the input method."""
# Init new stats
stats = self.get_init_value()
if import_error_tag:
return self.stats
if self.input_method == 'local':
stats = get_smart_data()
elif self.input_method == 'snmp':
pass
# Update the stats
self.stats = stats
return self.stats
def get_key(self):
"""Return the key of the list."""
return 'DeviceName'
def msg_curse(self, args=None, max_width=None):
"""Return the dict to display in the curse interface."""
# Init the return message
ret = []
# Only process if stats exist...
if not self.stats or self.is_disable():
return ret
# Max size for the interface name
name_max_width = max_width - 6
# Header
msg = '{:{width}}'.format('SMART disks',
width=name_max_width)
ret.append(self.curse_add_line(msg, "TITLE"))
# Data
for device_stat in self.stats:
# New line
ret.append(self.curse_new_line())
msg = '{:{width}}'.format(device_stat['DeviceName'][:max_width],
width=max_width)
ret.append(self.curse_add_line(msg))
for smart_stat in sorted([i for i in device_stat.keys() if i != 'DeviceName'], key=int):
ret.append(self.curse_new_line())
msg = ' {:{width}}'.format(device_stat[smart_stat]['name'][:name_max_width-1].replace('_', ' '),
width=name_max_width-1)
ret.append(self.curse_add_line(msg))
msg = '{:>8}'.format(device_stat[smart_stat]['raw'])
ret.append(self.curse_add_line(msg))
return ret
|