Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/libblockdev-sections.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
84 changes: 84 additions & 0 deletions src/lib/plugin_apis/btrfs.api
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Comment thread
vojtechtrefny marked this conversation as resolved.

#endif /* BD_BTRFS_API */
122 changes: 122 additions & 0 deletions src/plugins/btrfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
#include <glib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <linux/btrfs.h>
#include <blockdev/utils.h>
#include <bs_size.h>

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Comment thread
vojtechtrefny marked this conversation as resolved.

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);
}
15 changes: 15 additions & 0 deletions src/plugins/btrfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 */
23 changes: 23 additions & 0 deletions tests/btrfs_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Loading