mirror of
https://github.com/nicolargo/glances.git
synced 2024-11-28 14:12:21 +03:00
Merge pull request #1307 from tnibert/smartplugin
Hard disk SMART monitoring plugin
This commit is contained in:
commit
8d8e15a732
176
glances/plugins/glances_smart.py
Normal file
176
glances/plugins/glances_smart.py
Normal file
@ -0,0 +1,176 @@
|
||||
# -*- 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
|
||||
import os
|
||||
from pySMART import DeviceList
|
||||
|
||||
DEVKEY = "DeviceName"
|
||||
|
||||
|
||||
def is_admin():
|
||||
"""
|
||||
https://stackoverflow.com/a/19719292
|
||||
@return: True if the current user is an 'Admin' whatever that
|
||||
means (root on Unix), otherwise False.
|
||||
Warning: The inner function fails unless you have Windows XP SP2 or
|
||||
higher. The failure causes a traceback to be printed and this
|
||||
function to return False.
|
||||
"""
|
||||
|
||||
if os.name == 'nt':
|
||||
import ctypes, traceback
|
||||
# WARNING: requires Windows XP SP2 or higher!
|
||||
try:
|
||||
return ctypes.windll.shell32.IsUserAnAdmin()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
logger.info("Admin check failed, assuming not an admin.")
|
||||
return False
|
||||
else:
|
||||
# Check for root on Posix
|
||||
return os.getuid() == 0
|
||||
|
||||
|
||||
def convert_attribute_to_dict(attr):
|
||||
return {
|
||||
'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({
|
||||
DEVKEY: str(dev)
|
||||
})
|
||||
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):
|
||||
"""Init the plugin."""
|
||||
# check if user is admin
|
||||
if not is_admin():
|
||||
disable(args, "smart")
|
||||
logger.info("Not admin user, SMART plugin disabled.")
|
||||
|
||||
super(Plugin, self).__init__(args=args)
|
||||
|
||||
# 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 = []
|
||||
|
||||
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 DEVKEY
|
@ -1 +1,2 @@
|
||||
psutil==5.4.3
|
||||
pySMART.smartx
|
19
unitest.py
19
unitest.py
@ -254,6 +254,25 @@ class TestGlances(unittest.TestCase):
|
||||
l_subsample = subsample(l[0], l[1])
|
||||
self.assertLessEqual(len(l_subsample), l[1])
|
||||
|
||||
def test_016_hddsmart(self):
|
||||
"""Check hard disk SMART data plugin."""
|
||||
try:
|
||||
from glances.plugins.glances_smart import is_admin
|
||||
except ImportError:
|
||||
print("INFO: [TEST_016] pySMART not found, not running SMART plugin test")
|
||||
return
|
||||
|
||||
stat = 'DeviceName'
|
||||
print('INFO: [TEST_016] Check SMART stats: {}'.format(stat))
|
||||
stats_grab = stats.get_plugin('smart').get_raw()
|
||||
if not is_admin():
|
||||
print("INFO: Not admin, SMART list should be empty")
|
||||
assert len(stats_grab) == 0
|
||||
else:
|
||||
self.assertTrue(stat in stats_grab[0].keys(), msg='Cannot find key: %s' % stat)
|
||||
|
||||
print('INFO: SMART stats: %s' % stats_grab)
|
||||
|
||||
def test_094_thresholds(self):
|
||||
"""Test thresholds classes"""
|
||||
print('INFO: [TEST_094] Thresholds')
|
||||
|
Loading…
Reference in New Issue
Block a user