From 89531bbab8f1e486e085510ea2a1cee64095287e Mon Sep 17 00:00:00 2001 From: MacRimi Date: Fri, 30 Oct 2026 18:51:55 +0100 Subject: [PATCH] fix(monitor): record kernel I/O errors on the right disk --- AppImage/scripts/notification_events.py | 6 ++-- .../tests/test_disk_io_observation_device.py | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 AppImage/scripts/tests/test_disk_io_observation_device.py diff --git a/AppImage/scripts/notification_events.py b/AppImage/scripts/notification_events.py index 620a569c..9a6ff5fb 100644 --- a/AppImage/scripts/notification_events.py +++ b/AppImage/scripts/notification_events.py @@ -1402,9 +1402,11 @@ class JournalWatcher: if syslog_id != 'kernel' and priority > 3: return + # The kernel writes "dev sdf1, logical block ...": the name stops + # at the comma, and a partition is recorded on its disk. io_patterns = [ - r'blk_update_request: I/O error.*dev (\S+)', - r'Buffer I/O error on device (\S+)', + r'blk_update_request: I/O error.*dev ([^\s,]+)', + r'Buffer I/O error on dev(?:ice)? ([^\s,]+)', r'SCSI error.*sd(\w)', r'(ata\d+)[\.\d]*:.*error', ] diff --git a/AppImage/scripts/tests/test_disk_io_observation_device.py b/AppImage/scripts/tests/test_disk_io_observation_device.py new file mode 100644 index 00000000..3d782196 --- /dev/null +++ b/AppImage/scripts/tests/test_disk_io_observation_device.py @@ -0,0 +1,35 @@ +import sys +import unittest +from pathlib import Path +from queue import Queue +from unittest.mock import patch + +SCRIPTS_DIR = Path(__file__).resolve().parents[1] +if str(SCRIPTS_DIR) not in sys.path: + sys.path.insert(0, str(SCRIPTS_DIR)) + +import notification_events # noqa: E402 + + +class DiskIoObservationDevice(unittest.TestCase): + """A kernel I/O error is recorded on its disk, by name alone.""" + + def setUp(self): + self.watcher = notification_events.JournalWatcher(Queue()) + + def recorded(self, message): + with patch.object(self.watcher, '_record_disk_io_observation') as record, \ + patch.object(self.watcher, '_quick_smart_health', return_value='PASSED'): + self.watcher._check_disk_io(message, 'kernel', 3) + return [call.args[0] for call in record.call_args_list] + + def test_the_comma_after_the_device_is_not_part_of_its_name(self): + self.assertEqual(self.recorded('Buffer I/O error on dev sdf1, logical block 0, async page read'), ['sdf']) + self.assertEqual(self.recorded('blk_update_request: I/O error, dev sdc, sector 2048 op 0x0:(READ)'), ['sdc']) + + def test_the_older_wording_is_still_read(self): + self.assertEqual(self.recorded('Buffer I/O error on device sdb2, logical block 8'), ['sdb']) + + +if __name__ == '__main__': + unittest.main()