qga/commands-bsd.c | 120 +++++++++++++++++++++++++++++++++++++++++++ qga/meson.build | 3 ++ qga/qapi-schema.json | 6 +-- 3 files changed, 126 insertions(+), 3 deletions(-)
Implement the 'guest-get-diskstats' for FreeBSD.
This implementation uses the devstat(3) library which is a part
of the FreeBSD base system.
The build system is updated to link qga with `-ldevstat`.
Signed-off-by: Roman Bogorodskiy <bogorodskiy@gmail.com>
Acked-by: Markus Armbruster <armbru@redhat.com> (QAPI schema)
---
Changes since v1:
- Replace the guest_diskstats_append() helper with a simpler guest_diskstats_info_new().
- Drop the unnecessary condition for ios_pgr.
- Drop the unnecessary guest_get_diskstats() wrapper.
- Jump to error on devstat_getdevs() as it does allocations.
qga/commands-bsd.c | 120 +++++++++++++++++++++++++++++++++++++++++++
qga/meson.build | 3 ++
qga/qapi-schema.json | 6 +--
3 files changed, 126 insertions(+), 3 deletions(-)
diff --git a/qga/commands-bsd.c b/qga/commands-bsd.c
index 94ff6fee6a..6a1aadee8d 100644
--- a/qga/commands-bsd.c
+++ b/qga/commands-bsd.c
@@ -14,6 +14,7 @@
#include "qga-qapi-commands.h"
#include "qapi/error.h"
#include "qemu/queue.h"
+#include "block/block-common.h"
#include "commands-common.h"
#include <sys/ioctl.h>
#include <sys/param.h>
@@ -27,6 +28,9 @@
#include <net/ethernet.h>
#endif
#include <paths.h>
+#ifdef CONFIG_FREEBSD
+#include <devstat.h>
+#endif
#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
bool build_fs_mount_list(FsMountList *mounts, Error **errp)
@@ -178,3 +182,119 @@ bool guest_get_hw_addr(struct ifaddrs *ifa, unsigned char *buf,
return true;
}
#endif /* HAVE_GETIFADDRS */
+
+#ifdef CONFIG_FREEBSD
+static uint64_t bintime_to_msec(const struct bintime *bt)
+{
+ return (uint64_t)bt->sec * 1000ULL + (((bt->frac >> 32) * 1000ULL) >> 32);
+}
+
+static GuestDiskStatsInfo *
+guest_diskstats_info_new(const struct devstat *dev)
+{
+ GuestDiskStatsInfo *diskstatinfo;
+ GuestDiskStats *diskstat;
+
+ diskstatinfo = g_new0(GuestDiskStatsInfo, 1);
+ diskstatinfo->name = g_strdup_printf("%s%d", dev->device_name,
+ dev->unit_number);
+ /*
+ * devstat does not expose Linux-style major/minor numbers. Report the
+ * devstat device number and unit number in these mandatory QAPI fields.
+ */
+ diskstatinfo->major = dev->device_number;
+ diskstatinfo->minor = dev->unit_number;
+
+ diskstat = g_new0(GuestDiskStats, 1);
+ diskstat->has_read_ios = true;
+ diskstat->read_ios = dev->operations[DEVSTAT_READ];
+ diskstat->has_read_sectors = true;
+ diskstat->read_sectors = dev->bytes[DEVSTAT_READ] / BDRV_SECTOR_SIZE;
+ diskstat->has_read_ticks = true;
+ diskstat->read_ticks = bintime_to_msec(&dev->duration[DEVSTAT_READ]);
+
+ diskstat->has_write_ios = true;
+ diskstat->write_ios = dev->operations[DEVSTAT_WRITE];
+ diskstat->has_write_sectors = true;
+ diskstat->write_sectors = dev->bytes[DEVSTAT_WRITE] / BDRV_SECTOR_SIZE;
+ diskstat->has_write_ticks = true;
+ diskstat->write_ticks = bintime_to_msec(&dev->duration[DEVSTAT_WRITE]);
+
+ diskstat->has_discard_ios = true;
+ diskstat->discard_ios = dev->operations[DEVSTAT_FREE];
+ diskstat->has_discard_sectors = true;
+ diskstat->discard_sectors = dev->bytes[DEVSTAT_FREE] / BDRV_SECTOR_SIZE;
+ diskstat->has_discard_ticks = true;
+ diskstat->discard_ticks = bintime_to_msec(&dev->duration[DEVSTAT_FREE]);
+
+ diskstat->has_ios_pgr = true;
+ diskstat->ios_pgr = dev->start_count - dev->end_count;
+
+ diskstat->has_total_ticks = true;
+ diskstat->total_ticks = bintime_to_msec(&dev->busy_time);
+
+ diskstatinfo->stats = diskstat;
+ return diskstatinfo;
+}
+
+GuestDiskStatsInfoList *qmp_guest_get_diskstats(Error **errp)
+{
+ GuestDiskStatsInfoList *head = NULL, **tail = &head;
+ struct devinfo dinfo = { 0 };
+ struct statinfo stats = { .dinfo = &dinfo };
+ struct device_selection *dev_select = NULL;
+ struct devstat_match matches[] = {
+ {
+ .match_fields = DEVSTAT_MATCH_TYPE,
+ .device_type = DEVSTAT_TYPE_DIRECT,
+ .num_match_categories = 1,
+ },
+ };
+ int num_selected = 0;
+ int num_selections = 0;
+ long select_generation = 0;
+ int i;
+
+ if (devstat_checkversion(NULL) == -1) {
+ error_setg(errp, "%s", devstat_errbuf);
+ return NULL;
+ }
+
+ if (devstat_getdevs(NULL, &stats) == -1) {
+ error_setg(errp, "%s", devstat_errbuf);
+ goto error;
+ }
+
+ if (devstat_selectdevs(&dev_select, &num_selected, &num_selections,
+ &select_generation, stats.dinfo->generation,
+ stats.dinfo->devices, stats.dinfo->numdevs,
+ matches, ARRAY_SIZE(matches), NULL, 0,
+ DS_SELECT_ONLY,
+ stats.dinfo->numdevs, 0) == -1) {
+ error_setg(errp, "%s", devstat_errbuf);
+ goto error;
+ }
+
+ for (i = 0; i < num_selections; i++) {
+ const struct devstat *dev;
+
+ if (dev_select[i].selected == 0) {
+ continue;
+ }
+
+ dev = &stats.dinfo->devices[dev_select[i].position];
+ QAPI_LIST_APPEND(tail, guest_diskstats_info_new(dev));
+ }
+
+ free(stats.dinfo->mem_ptr);
+ free(dev_select);
+ return head;
+
+error:
+ qapi_free_GuestDiskStatsInfoList(head);
+ free(stats.dinfo->mem_ptr);
+ free(dev_select);
+ return NULL;
+}
+
+#endif /* CONFIG_FREEBSD */
diff --git a/qga/meson.build b/qga/meson.build
index cfa2157efb..a046ebe305 100644
--- a/qga/meson.build
+++ b/qga/meson.build
@@ -93,6 +93,9 @@ qga_ss = qga_ss.apply({})
gen_tlb = []
qga_libs = []
+if host_os == 'freebsd'
+ qga_libs += ['-ldevstat']
+endif
if host_os == 'windows'
qga_libs += ['-lws2_32', '-lwinmm', '-lpowrprof', '-lwtsapi32', '-lwininet', '-liphlpapi', '-lnetapi32',
'-lsetupapi', '-lcfgmgr32', '-luserenv', '-lpdh' ]
diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
index c57bc9a02f..0d1476dcc4 100644
--- a/qga/qapi-schema.json
+++ b/qga/qapi-schema.json
@@ -1714,7 +1714,7 @@
'*total-ticks': 'uint64',
'*weight-ticks': 'uint64'
},
- 'if': 'CONFIG_LINUX' }
+ 'if': { 'any': ['CONFIG_LINUX', 'CONFIG_FREEBSD'] } }
##
# @GuestDiskStatsInfo:
@@ -1732,7 +1732,7 @@
'major': 'uint64',
'minor': 'uint64',
'stats': 'GuestDiskStats' },
- 'if': 'CONFIG_LINUX' }
+ 'if': { 'any': ['CONFIG_LINUX', 'CONFIG_FREEBSD'] } }
##
# @guest-get-diskstats:
@@ -1745,7 +1745,7 @@
##
{ 'command': 'guest-get-diskstats',
'returns': ['GuestDiskStatsInfo'],
- 'if': 'CONFIG_LINUX'
+ 'if': { 'any': ['CONFIG_LINUX', 'CONFIG_FREEBSD'] }
}
##
--
2.55.0
Reviewed-by: Kostiantyn Kostiuk <kkostiuk@redhat.com>
On Tue, Sep 1, 2026 at 10:29 PM Roman Bogorodskiy <bogorodskiy@gmail.com>
wrote:
> Implement the 'guest-get-diskstats' for FreeBSD.
> This implementation uses the devstat(3) library which is a part
> of the FreeBSD base system.
>
> The build system is updated to link qga with `-ldevstat`.
>
> Signed-off-by: Roman Bogorodskiy <bogorodskiy@gmail.com>
> Acked-by: Markus Armbruster <armbru@redhat.com> (QAPI schema)
> ---
> Changes since v1:
> - Replace the guest_diskstats_append() helper with a simpler
> guest_diskstats_info_new().
> - Drop the unnecessary condition for ios_pgr.
> - Drop the unnecessary guest_get_diskstats() wrapper.
> - Jump to error on devstat_getdevs() as it does allocations.
>
> qga/commands-bsd.c | 120 +++++++++++++++++++++++++++++++++++++++++++
> qga/meson.build | 3 ++
> qga/qapi-schema.json | 6 +--
> 3 files changed, 126 insertions(+), 3 deletions(-)
>
> diff --git a/qga/commands-bsd.c b/qga/commands-bsd.c
> index 94ff6fee6a..6a1aadee8d 100644
> --- a/qga/commands-bsd.c
> +++ b/qga/commands-bsd.c
> @@ -14,6 +14,7 @@
> #include "qga-qapi-commands.h"
> #include "qapi/error.h"
> #include "qemu/queue.h"
> +#include "block/block-common.h"
> #include "commands-common.h"
> #include <sys/ioctl.h>
> #include <sys/param.h>
> @@ -27,6 +28,9 @@
> #include <net/ethernet.h>
> #endif
> #include <paths.h>
> +#ifdef CONFIG_FREEBSD
> +#include <devstat.h>
> +#endif
>
> #if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
> bool build_fs_mount_list(FsMountList *mounts, Error **errp)
> @@ -178,3 +182,119 @@ bool guest_get_hw_addr(struct ifaddrs *ifa, unsigned
> char *buf,
> return true;
> }
> #endif /* HAVE_GETIFADDRS */
> +
> +#ifdef CONFIG_FREEBSD
> +static uint64_t bintime_to_msec(const struct bintime *bt)
> +{
> + return (uint64_t)bt->sec * 1000ULL + (((bt->frac >> 32) * 1000ULL) >>
> 32);
> +}
> +
> +static GuestDiskStatsInfo *
> +guest_diskstats_info_new(const struct devstat *dev)
> +{
> + GuestDiskStatsInfo *diskstatinfo;
> + GuestDiskStats *diskstat;
> +
> + diskstatinfo = g_new0(GuestDiskStatsInfo, 1);
> + diskstatinfo->name = g_strdup_printf("%s%d", dev->device_name,
> + dev->unit_number);
> + /*
> + * devstat does not expose Linux-style major/minor numbers. Report
> the
> + * devstat device number and unit number in these mandatory QAPI
> fields.
> + */
> + diskstatinfo->major = dev->device_number;
> + diskstatinfo->minor = dev->unit_number;
> +
> + diskstat = g_new0(GuestDiskStats, 1);
> + diskstat->has_read_ios = true;
> + diskstat->read_ios = dev->operations[DEVSTAT_READ];
> + diskstat->has_read_sectors = true;
> + diskstat->read_sectors = dev->bytes[DEVSTAT_READ] / BDRV_SECTOR_SIZE;
> + diskstat->has_read_ticks = true;
> + diskstat->read_ticks = bintime_to_msec(&dev->duration[DEVSTAT_READ]);
> +
> + diskstat->has_write_ios = true;
> + diskstat->write_ios = dev->operations[DEVSTAT_WRITE];
> + diskstat->has_write_sectors = true;
> + diskstat->write_sectors = dev->bytes[DEVSTAT_WRITE] /
> BDRV_SECTOR_SIZE;
> + diskstat->has_write_ticks = true;
> + diskstat->write_ticks =
> bintime_to_msec(&dev->duration[DEVSTAT_WRITE]);
> +
> + diskstat->has_discard_ios = true;
> + diskstat->discard_ios = dev->operations[DEVSTAT_FREE];
> + diskstat->has_discard_sectors = true;
> + diskstat->discard_sectors = dev->bytes[DEVSTAT_FREE] /
> BDRV_SECTOR_SIZE;
> + diskstat->has_discard_ticks = true;
> + diskstat->discard_ticks =
> bintime_to_msec(&dev->duration[DEVSTAT_FREE]);
> +
> + diskstat->has_ios_pgr = true;
> + diskstat->ios_pgr = dev->start_count - dev->end_count;
> +
> + diskstat->has_total_ticks = true;
> + diskstat->total_ticks = bintime_to_msec(&dev->busy_time);
> +
> + diskstatinfo->stats = diskstat;
> + return diskstatinfo;
> +}
> +
> +GuestDiskStatsInfoList *qmp_guest_get_diskstats(Error **errp)
> +{
> + GuestDiskStatsInfoList *head = NULL, **tail = &head;
> + struct devinfo dinfo = { 0 };
> + struct statinfo stats = { .dinfo = &dinfo };
> + struct device_selection *dev_select = NULL;
> + struct devstat_match matches[] = {
> + {
> + .match_fields = DEVSTAT_MATCH_TYPE,
> + .device_type = DEVSTAT_TYPE_DIRECT,
> + .num_match_categories = 1,
> + },
> + };
> + int num_selected = 0;
> + int num_selections = 0;
> + long select_generation = 0;
> + int i;
> +
> + if (devstat_checkversion(NULL) == -1) {
> + error_setg(errp, "%s", devstat_errbuf);
> + return NULL;
> + }
> +
> + if (devstat_getdevs(NULL, &stats) == -1) {
> + error_setg(errp, "%s", devstat_errbuf);
> + goto error;
> + }
> +
> + if (devstat_selectdevs(&dev_select, &num_selected, &num_selections,
> + &select_generation, stats.dinfo->generation,
> + stats.dinfo->devices, stats.dinfo->numdevs,
> + matches, ARRAY_SIZE(matches), NULL, 0,
> + DS_SELECT_ONLY,
> + stats.dinfo->numdevs, 0) == -1) {
> + error_setg(errp, "%s", devstat_errbuf);
> + goto error;
> + }
> +
> + for (i = 0; i < num_selections; i++) {
> + const struct devstat *dev;
> +
> + if (dev_select[i].selected == 0) {
> + continue;
> + }
> +
> + dev = &stats.dinfo->devices[dev_select[i].position];
> + QAPI_LIST_APPEND(tail, guest_diskstats_info_new(dev));
> + }
> +
> + free(stats.dinfo->mem_ptr);
> + free(dev_select);
> + return head;
> +
> +error:
> + qapi_free_GuestDiskStatsInfoList(head);
> + free(stats.dinfo->mem_ptr);
> + free(dev_select);
> + return NULL;
> +}
> +
> +#endif /* CONFIG_FREEBSD */
> diff --git a/qga/meson.build b/qga/meson.build
> index cfa2157efb..a046ebe305 100644
> --- a/qga/meson.build
> +++ b/qga/meson.build
> @@ -93,6 +93,9 @@ qga_ss = qga_ss.apply({})
>
> gen_tlb = []
> qga_libs = []
> +if host_os == 'freebsd'
> + qga_libs += ['-ldevstat']
> +endif
> if host_os == 'windows'
> qga_libs += ['-lws2_32', '-lwinmm', '-lpowrprof', '-lwtsapi32',
> '-lwininet', '-liphlpapi', '-lnetapi32',
> '-lsetupapi', '-lcfgmgr32', '-luserenv', '-lpdh' ]
> diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
> index c57bc9a02f..0d1476dcc4 100644
> --- a/qga/qapi-schema.json
> +++ b/qga/qapi-schema.json
> @@ -1714,7 +1714,7 @@
> '*total-ticks': 'uint64',
> '*weight-ticks': 'uint64'
> },
> - 'if': 'CONFIG_LINUX' }
> + 'if': { 'any': ['CONFIG_LINUX', 'CONFIG_FREEBSD'] } }
>
> ##
> # @GuestDiskStatsInfo:
> @@ -1732,7 +1732,7 @@
> 'major': 'uint64',
> 'minor': 'uint64',
> 'stats': 'GuestDiskStats' },
> - 'if': 'CONFIG_LINUX' }
> + 'if': { 'any': ['CONFIG_LINUX', 'CONFIG_FREEBSD'] } }
>
> ##
> # @guest-get-diskstats:
> @@ -1745,7 +1745,7 @@
> ##
> { 'command': 'guest-get-diskstats',
> 'returns': ['GuestDiskStatsInfo'],
> - 'if': 'CONFIG_LINUX'
> + 'if': { 'any': ['CONFIG_LINUX', 'CONFIG_FREEBSD'] }
> }
>
> ##
> --
> 2.55.0
>
>
© 2016 - 2026 Red Hat, Inc.