diff --git a/docs/libblockdev-sections.txt b/docs/libblockdev-sections.txt index 4a6996fa0..5389bf8b3 100644 --- a/docs/libblockdev-sections.txt +++ b/docs/libblockdev-sections.txt @@ -29,6 +29,9 @@ bd_btrfs_subvolume_info_copy BDBtrfsFilesystemInfo bd_btrfs_filesystem_info_free bd_btrfs_filesystem_info_copy +BDBtrfsDeviceStats +bd_btrfs_device_stats_free +bd_btrfs_device_stats_copy bd_btrfs_create_volume bd_btrfs_add_device bd_btrfs_remove_device @@ -45,6 +48,7 @@ bd_btrfs_resize bd_btrfs_check bd_btrfs_repair bd_btrfs_change_label +bd_btrfs_device_stats BDBtrfsTech BDBtrfsTechMode bd_btrfs_is_tech_avail diff --git a/src/lib/plugin_apis/btrfs.api b/src/lib/plugin_apis/btrfs.api index 385ea16ef..89c9b0158 100644 --- a/src/lib/plugin_apis/btrfs.api +++ b/src/lib/plugin_apis/btrfs.api @@ -208,6 +208,78 @@ GType bd_btrfs_filesystem_info_get_type () { return type; } +#define BD_BTRFS_TYPE_DEVICE_STATS (bd_btrfs_device_stats_get_type ()) +GType bd_btrfs_device_stats_get_type(); + +/** + * BDBtrfsDeviceStats: + * @id: ID of the device + * @path: path of the device + * @write_io_errs: number of write I/O errors + * @read_io_errs: number of read I/O errors + * @flush_io_errs: number of flush I/O errors + * @corruption_errs: number of corruption errors + * @generation_errs: number of generation errors + */ +typedef struct BDBtrfsDeviceStats { + guint64 id; + gchar *path; + guint64 write_io_errs; + guint64 read_io_errs; + guint64 flush_io_errs; + guint64 corruption_errs; + guint64 generation_errs; +} BDBtrfsDeviceStats; + +/** + * bd_btrfs_device_stats_copy: (skip) + * @stats: (nullable): %BDBtrfsDeviceStats to copy + * + * Creates a new copy of @stats. + */ +BDBtrfsDeviceStats* bd_btrfs_device_stats_copy (BDBtrfsDeviceStats *stats) { + if (stats == NULL) + return NULL; + + BDBtrfsDeviceStats *new_stats = g_new0 (BDBtrfsDeviceStats, 1); + + new_stats->id = stats->id; + new_stats->path = g_strdup (stats->path); + new_stats->write_io_errs = stats->write_io_errs; + new_stats->read_io_errs = stats->read_io_errs; + new_stats->flush_io_errs = stats->flush_io_errs; + new_stats->corruption_errs = stats->corruption_errs; + new_stats->generation_errs = stats->generation_errs; + + return new_stats; +} + +/** + * bd_btrfs_device_stats_free: (skip) + * @stats: (nullable): %BDBtrfsDeviceStats to free + * + * Frees @stats. + */ +void bd_btrfs_device_stats_free (BDBtrfsDeviceStats *stats) { + if (stats == NULL) + return; + + g_free (stats->path); + g_free (stats); +} + +GType bd_btrfs_device_stats_get_type () { + static GType type = 0; + + if (G_UNLIKELY(type == 0)) { + type = g_boxed_type_register_static("BDBtrfsDeviceStats", + (GBoxedCopyFunc) bd_btrfs_device_stats_copy, + (GBoxedFreeFunc) bd_btrfs_device_stats_free); + } + + return type; +} + typedef enum { BD_BTRFS_TECH_FS = 0, BD_BTRFS_TECH_MULTI_DEV, @@ -461,4 +533,16 @@ gboolean bd_btrfs_repair (const gchar *device, const BDExtraArg **extra, GError */ gboolean bd_btrfs_change_label (const gchar *mountpoint, const gchar *label, GError **error); +/** + * bd_btrfs_device_stats: + * @mountpoint: a mountpoint of the queried btrfs volume + * @error: (out) (optional): place to store error (if any) + * + * Returns: (array zero-terminated=1): device stats for each device that is part of the btrfs volume + * mounted at @mountpoint or %NULL in case of error + * + * Tech category: %BD_BTRFS_TECH_MULTI_DEV-%BD_BTRFS_TECH_MODE_QUERY + */ +BDBtrfsDeviceStats** bd_btrfs_device_stats (const gchar *mountpoint, GError **error); + #endif /* BD_BTRFS_API */ diff --git a/src/plugins/btrfs.c b/src/plugins/btrfs.c index 419088bee..8975b6450 100644 --- a/src/plugins/btrfs.c +++ b/src/plugins/btrfs.c @@ -20,6 +20,10 @@ #include #include #include +#include +#include +#include +#include #include #include @@ -111,6 +115,31 @@ void bd_btrfs_filesystem_info_free (BDBtrfsFilesystemInfo *info) { g_free (info); } +BDBtrfsDeviceStats* bd_btrfs_device_stats_copy (BDBtrfsDeviceStats *stats) { + if (stats == NULL) + return NULL; + + BDBtrfsDeviceStats *new_stats = g_new0 (BDBtrfsDeviceStats, 1); + + new_stats->id = stats->id; + new_stats->path = g_strdup (stats->path); + new_stats->write_io_errs = stats->write_io_errs; + new_stats->read_io_errs = stats->read_io_errs; + new_stats->flush_io_errs = stats->flush_io_errs; + new_stats->corruption_errs = stats->corruption_errs; + new_stats->generation_errs = stats->generation_errs; + + return new_stats; +} + +void bd_btrfs_device_stats_free (BDBtrfsDeviceStats *stats) { + if (stats == NULL) + return; + + g_free (stats->path); + g_free (stats); +} + static volatile guint avail_deps = 0; static volatile guint avail_module_deps = 0; static GMutex deps_check_lock; @@ -926,3 +955,96 @@ gboolean bd_btrfs_change_label (const gchar *mountpoint, const gchar *label, GEr return bd_utils_exec_and_report_error (argv, NULL, error); } + +/** + * bd_btrfs_device_stats: + * @mountpoint: a mountpoint of the queried btrfs volume + * @error: (out) (optional): place to store error (if any) + * + * Returns: (array zero-terminated=1): device stats for each device that is part of the btrfs volume + * mounted at @mountpoint or %NULL in case of error + * + * Tech category: %BD_BTRFS_TECH_MULTI_DEV-%BD_BTRFS_TECH_MODE_QUERY + */ +BDBtrfsDeviceStats** bd_btrfs_device_stats (const gchar *mountpoint, GError **error) { + int fd = -1; + struct btrfs_ioctl_fs_info_args fs_info; + struct btrfs_ioctl_dev_info_args dev_info; + struct btrfs_ioctl_get_dev_stats dev_stats; + GPtrArray *stats_array = NULL; + BDBtrfsDeviceStats *stats = NULL; + + if (!check_module_deps (&avail_module_deps, MODULE_DEPS_BTRFS_MASK, module_deps, MODULE_DEPS_LAST, &deps_check_lock, error)) + return NULL; + + fd = open (mountpoint, O_RDONLY); + if (fd < 0) { + g_set_error (error, BD_BTRFS_ERROR, BD_BTRFS_ERROR_DEVICE, + "Failed to open mountpoint %s: %s", mountpoint, g_strerror (errno)); + return NULL; + } + + memset (&fs_info, 0, sizeof (fs_info)); + if (ioctl (fd, BTRFS_IOC_FS_INFO, &fs_info) < 0) { + g_set_error (error, BD_BTRFS_ERROR, BD_BTRFS_ERROR_DEVICE, + "Failed to get filesystem info for %s: %s", mountpoint, g_strerror (errno)); + close (fd); + return NULL; + } + + stats_array = g_ptr_array_new_with_free_func ((GDestroyNotify) bd_btrfs_device_stats_free); + + for (guint64 devid = 1; devid <= fs_info.max_id; devid++) { + memset (&dev_info, 0, sizeof (dev_info)); + dev_info.devid = devid; + if (ioctl (fd, BTRFS_IOC_DEV_INFO, &dev_info) < 0) { + if (errno == ENODEV) + continue; + g_set_error (error, BD_BTRFS_ERROR, BD_BTRFS_ERROR_DEVICE, + "Failed to get device info for devid %" G_GUINT64_FORMAT " on %s: %s", + devid, mountpoint, g_strerror (errno)); + g_ptr_array_free (stats_array, TRUE); + close (fd); + return NULL; + } + + memset (&dev_stats, 0, sizeof (dev_stats)); + dev_stats.devid = devid; + dev_stats.nr_items = BTRFS_DEV_STAT_VALUES_MAX; + dev_stats.flags = 0; + if (ioctl (fd, BTRFS_IOC_GET_DEV_STATS, &dev_stats) < 0) { + g_set_error (error, BD_BTRFS_ERROR, BD_BTRFS_ERROR_DEVICE, + "Failed to get device stats for devid %" G_GUINT64_FORMAT " on %s: %s", + devid, mountpoint, g_strerror (errno)); + g_ptr_array_free (stats_array, TRUE); + close (fd); + return NULL; + } + + stats = g_new0 (BDBtrfsDeviceStats, 1); + stats->id = devid; + stats->path = g_strdup ((const gchar *) dev_info.path); + stats->write_io_errs = dev_stats.values[BTRFS_DEV_STAT_WRITE_ERRS]; + stats->read_io_errs = dev_stats.values[BTRFS_DEV_STAT_READ_ERRS]; + stats->flush_io_errs = dev_stats.values[BTRFS_DEV_STAT_FLUSH_ERRS]; + stats->corruption_errs = dev_stats.values[BTRFS_DEV_STAT_CORRUPTION_ERRS]; + stats->generation_errs = dev_stats.values[BTRFS_DEV_STAT_GENERATION_ERRS]; + + g_ptr_array_add (stats_array, stats); + + if (stats_array->len == fs_info.num_devices) + break; + } + + close (fd); + + if (stats_array->len == 0) { + g_set_error (error, BD_BTRFS_ERROR, BD_BTRFS_ERROR_DEVICE, + "No devices found for %s", mountpoint); + g_ptr_array_free (stats_array, TRUE); + return NULL; + } + + g_ptr_array_add (stats_array, NULL); + return (BDBtrfsDeviceStats **) g_ptr_array_free (stats_array, FALSE); +} diff --git a/src/plugins/btrfs.h b/src/plugins/btrfs.h index b3d9ea99c..14c31ef3e 100644 --- a/src/plugins/btrfs.h +++ b/src/plugins/btrfs.h @@ -35,6 +35,19 @@ typedef struct BDBtrfsSubvolumeInfo { void bd_btrfs_subvolume_info_free (BDBtrfsSubvolumeInfo *info); BDBtrfsSubvolumeInfo* bd_btrfs_subvolume_info_copy (BDBtrfsSubvolumeInfo *info); +typedef struct BDBtrfsDeviceStats { + guint64 id; + gchar *path; + guint64 write_io_errs; + guint64 read_io_errs; + guint64 flush_io_errs; + guint64 corruption_errs; + guint64 generation_errs; +} BDBtrfsDeviceStats; + +void bd_btrfs_device_stats_free (BDBtrfsDeviceStats *stats); +BDBtrfsDeviceStats* bd_btrfs_device_stats_copy (BDBtrfsDeviceStats *stats); + typedef struct BDBtrfsFilesystemInfo { gchar *label; gchar *uuid; @@ -91,4 +104,6 @@ gboolean bd_btrfs_check (const gchar *device, const BDExtraArg **extra, GError * gboolean bd_btrfs_repair (const gchar *device, const BDExtraArg **extra, GError **error); gboolean bd_btrfs_change_label (const gchar *mountpoint, const gchar *label, GError **error); +BDBtrfsDeviceStats** bd_btrfs_device_stats (const gchar *mountpoint, GError **error); + #endif /* BD_BTRFS */ diff --git a/tests/btrfs_test.py b/tests/btrfs_test.py index da3d1f39a..fb20f41db 100644 --- a/tests/btrfs_test.py +++ b/tests/btrfs_test.py @@ -426,6 +426,29 @@ def test_filesystem_info(self): self.assertEqual(info.num_devices, 1) self.assertGreaterEqual(info.used, 0) +class BtrfsTestDeviceStats(BtrfsMultiTestCase): + def test_device_stats(self): + """Verify that it is possible to get device stats for btrfs volume""" + + succ = BlockDev.btrfs_create_volume([self.loop_dev, self.loop_dev2], None, None, None, None) + self.assertTrue(succ) + + mount(self.loop_dev, TEST_MNT) + + stats = BlockDev.btrfs_device_stats(TEST_MNT) + self.assertEqual(len(stats), 2) + self.assertEqual(stats[0].id, 1) + self.assertEqual(stats[1].id, 2) + self.assertEqual(stats[0].path, self.loop_dev) + self.assertEqual(stats[1].path, self.loop_dev2) + + for s in stats: + self.assertEqual(s.write_io_errs, 0) + self.assertEqual(s.read_io_errs, 0) + self.assertEqual(s.flush_io_errs, 0) + self.assertEqual(s.corruption_errs, 0) + self.assertEqual(s.generation_errs, 0) + class BtrfsTestMkfs(BtrfsTestCase): @tag_test(TestTags.CORE) def test_mkfs(self):