Skip to content

fix(smart): keep healthy disks when pySMART rejects a device type - #3807

Open
flyingnobita wants to merge 1 commit into
nicolargo:developfrom
flyingnobita:fix/3806-smart-unknown-interface
Open

flyingnobita wants to merge 1 commit into
nicolargo:developfrom
flyingnobita:fix/3806-smart-unknown-interface

Conversation

@flyingnobita

Copy link
Copy Markdown

Description

Fixes #3806: a disk whose smartctl --scan type pySMART does not accept (USB bridges such as -d usbsunplus, usbjmicron) no longer takes out SMART data for every disk.

On develop this is not a crash: the per-plugin guard in GlancesPluginBase.update() catches the ValueError, but get_smart_data() then returns nothing at all, healthy disks included, and logs Plugin smart update failed every cycle. On v4 (4.5.8, as reported in the issue) there is no such guard and Glances exits at startup. A backport to support/glancesv4 will follow if you want it.

 get_smart_data(hide_attributes)
+  attach _SkippedDeviceFilter to the root logger
   try
-    DeviceList()
+    DeviceList(catch_errors=True)   # pySMART skips the bad device, keeps the others
-  except (TypeError, FileNotFoundError)
+  except (TypeError, FileNotFoundError, ValueError)
     import_error_tag = True
+  finally
+    detach the filter

Why the filter: with catch_errors=True, pySMART calls logging.exception("Error parsing device ...") on the root logger (it has no named logger) on every refresh. In the Docker image (LOG_CFG=/app/docker-logger.json) that is a full traceback in docker logs every few seconds. The filter turns the first such record per device into one WARNING without traceback and drops the repeats. It only matches pySMART's Error parsing device record (by pathname and raw message), never formats other records, and is attached only around the DeviceList() call.

Alternative, your call: drop the filter and keep only the 2-line catch_errors=True change. The plugin then works, at the cost of the repeated tracebacks shown below.

Evidence

Real hardware: Sunplus USB bridge (1bcf:0c31) reported as /dev/sda -d usbsunplus, plus an NVMe. Image nicolargo/glances:dev (5.0.0b1, pySMART 1.4.3, smartctl 7.5), patched data.py bind-mounted, --enable-plugin smart.

Before catch_errors only This PR
/api/5/smart null nvme0 (23 attributes), no sda nvme0 (23 attributes), no sda
Plugin smart update failed 17 in 68 s 0 0
Error parsing device tracebacks 0 18 in 68 s 0 in 96 s
Smart plugin - Skipping - - 1

docker logs with this PR:

2026-10-21 15:44:27,169 -- WARNING -- Smart plugin - Skipping /dev/sda: Unknown interface: usbsunplus specified for /dev/sda

Tests in tests/test_smart_data.py (written first, red on develop):

  • test_unknown_interface_skips_device_keeps_others: drives the real pySMART.DeviceList.initialize() with an nvme + usbsunplus scan; returns the NVMe, plugin stays enabled.
  • test_device_list_value_error_disables_plugin: whole-list ValueError returns [] and sets import_error_tag.
  • test_skipped_device_logs_one_warning_without_traceback: two refreshes, one WARNING per device, no traceback.
  • test_root_logger_filters_restored, test_unrelated_root_errors_not_suppressed, test_filter_never_raises: the filter is scoped and cannot break other logging.
  • test_missing_smartctl_returns_no_device: its fake now accepts **kwargs, otherwise it would pass through the TypeError branch instead of FileNotFoundError.

make test: 4162 passed, 20 skipped. ruff check, ruff format --check and pre-commit run --all-files pass.

Notes

  • catch_errors exists since pySMART 1.2.3. On older versions the call raises TypeError, which is caught, so SMART is disabled rather than crashing. pyproject.toml does not pin pySMART; pinning pySMART>=1.2.3 is left to you.
  • A per-device error of any type now skips that device instead of disabling the plugin for all of them.
  • Out of scope, already present: a scan line without -d <type> makes pySMART raise AttributeError outside its try.

Resume

When smartctl --scan reports a device type pySMART does not accept,
such as a USB bridge (-d usbsunplus), pySMART raises ValueError from
DeviceList(). On v4 this kills Glances at startup; on develop the
per-plugin guard catches it, but the smart plugin then returns no
data at all, healthy disks included, and logs a warning every cycle.

Call DeviceList(catch_errors=True) so pySMART skips the device it
cannot parse and keeps the others, and add ValueError to the
whole-list fallback.

With catch_errors, pySMART logs the skipped device with
logging.exception() on the root logger on every refresh. In the
Docker image that is a full traceback in the console every few
seconds. A root-logger filter, attached only around the DeviceList()
call, turns the first such record per device into one WARNING
without traceback and drops the repeats. It matches only pySMART's
"Error parsing device" record and never formats other records.

Fixes nicolargo#3806

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Smart plugin: a USB-bridged disk (e.g. -d usbsunplus) crashes the web server at startup

1 participant