Commit 5eb4cb18 authored by Darrick J. Wong's avatar Darrick J. Wong
Browse files

xfs: convey metadata health events to the health monitor



Connect the filesystem metadata health event collection system to the
health monitor so that xfs can send events to xfs_healer as it collects
information.

Signed-off-by: default avatar"Darrick J. Wong" <djwong@kernel.org>
Reviewed-by: default avatarChristoph Hellwig <hch@lst.de>
parent 25ca57fa
Loading
Loading
Loading
Loading
+35 −0
Original line number Diff line number Diff line
@@ -1008,6 +1008,12 @@ struct xfs_rtgroup_geometry {
/* affects the whole fs */
#define XFS_HEALTH_MONITOR_DOMAIN_MOUNT		(0)

/* metadata health events */
#define XFS_HEALTH_MONITOR_DOMAIN_FS		(1)
#define XFS_HEALTH_MONITOR_DOMAIN_AG		(2)
#define XFS_HEALTH_MONITOR_DOMAIN_INODE		(3)
#define XFS_HEALTH_MONITOR_DOMAIN_RTGROUP	(4)

/* Health monitor event types */

/* status of the monitor itself */
@@ -1017,11 +1023,37 @@ struct xfs_rtgroup_geometry {
/* filesystem was unmounted */
#define XFS_HEALTH_MONITOR_TYPE_UNMOUNT		(2)

/* metadata health events */
#define XFS_HEALTH_MONITOR_TYPE_SICK		(3)
#define XFS_HEALTH_MONITOR_TYPE_CORRUPT		(4)
#define XFS_HEALTH_MONITOR_TYPE_HEALTHY		(5)

/* lost events */
struct xfs_health_monitor_lost {
	__u64	count;
};

/* fs/rt metadata */
struct xfs_health_monitor_fs {
	/* XFS_FSOP_GEOM_SICK_* flags */
	__u32	mask;
};

/* ag/rtgroup metadata */
struct xfs_health_monitor_group {
	/* XFS_{AG,RTGROUP}_SICK_* flags */
	__u32	mask;
	__u32	gno;
};

/* inode metadata */
struct xfs_health_monitor_inode {
	/* XFS_BS_SICK_* flags */
	__u32	mask;
	__u32	gen;
	__u64	ino;
};

struct xfs_health_monitor_event {
	/* XFS_HEALTH_MONITOR_DOMAIN_* */
	__u32	domain;
@@ -1039,6 +1071,9 @@ struct xfs_health_monitor_event {
	 */
	union {
		struct xfs_health_monitor_lost lost;
		struct xfs_health_monitor_fs fs;
		struct xfs_health_monitor_group group;
		struct xfs_health_monitor_inode inode;
	} e;

	/* zeroes */
+5 −0
Original line number Diff line number Diff line
@@ -289,4 +289,9 @@ void xfs_bulkstat_health(struct xfs_inode *ip, struct xfs_bulkstat *bs);
#define xfs_metadata_is_sick(error) \
	(unlikely((error) == -EFSCORRUPTED || (error) == -EFSBADCRC))

unsigned int xfs_healthmon_inode_mask(unsigned int sick_mask);
unsigned int xfs_healthmon_rtgroup_mask(unsigned int sick_mask);
unsigned int xfs_healthmon_perag_mask(unsigned int sick_mask);
unsigned int xfs_healthmon_fs_mask(unsigned int sick_mask);

#endif	/* __XFS_HEALTH_H__ */
+123 −0
Original line number Diff line number Diff line
@@ -108,14 +108,19 @@ xfs_fs_mark_sick(
	struct xfs_mount	*mp,
	unsigned int		mask)
{
	unsigned int		old_mask;

	ASSERT(!(mask & ~XFS_SICK_FS_ALL));
	trace_xfs_fs_mark_sick(mp, mask);

	spin_lock(&mp->m_sb_lock);
	old_mask = mp->m_fs_sick;
	mp->m_fs_sick |= mask;
	spin_unlock(&mp->m_sb_lock);

	fserror_report_metadata(mp->m_super, -EFSCORRUPTED, GFP_NOFS);
	if (mask)
		xfs_healthmon_report_fs(mp, XFS_HEALTHMON_SICK, old_mask, mask);
}

/* Mark per-fs metadata as having been checked and found unhealthy by fsck. */
@@ -124,15 +129,21 @@ xfs_fs_mark_corrupt(
	struct xfs_mount	*mp,
	unsigned int		mask)
{
	unsigned int		old_mask;

	ASSERT(!(mask & ~XFS_SICK_FS_ALL));
	trace_xfs_fs_mark_corrupt(mp, mask);

	spin_lock(&mp->m_sb_lock);
	old_mask = mp->m_fs_sick;
	mp->m_fs_sick |= mask;
	mp->m_fs_checked |= mask;
	spin_unlock(&mp->m_sb_lock);

	fserror_report_metadata(mp->m_super, -EFSCORRUPTED, GFP_NOFS);
	if (mask)
		xfs_healthmon_report_fs(mp, XFS_HEALTHMON_CORRUPT, old_mask,
				mask);
}

/* Mark a per-fs metadata healed. */
@@ -141,15 +152,22 @@ xfs_fs_mark_healthy(
	struct xfs_mount	*mp,
	unsigned int		mask)
{
	unsigned int		old_mask;

	ASSERT(!(mask & ~XFS_SICK_FS_ALL));
	trace_xfs_fs_mark_healthy(mp, mask);

	spin_lock(&mp->m_sb_lock);
	old_mask = mp->m_fs_sick;
	mp->m_fs_sick &= ~mask;
	if (!(mp->m_fs_sick & XFS_SICK_FS_PRIMARY))
		mp->m_fs_sick &= ~XFS_SICK_FS_SECONDARY;
	mp->m_fs_checked |= mask;
	spin_unlock(&mp->m_sb_lock);

	if (mask)
		xfs_healthmon_report_fs(mp, XFS_HEALTHMON_HEALTHY, old_mask,
				mask);
}

/* Sample which per-fs metadata are unhealthy. */
@@ -199,14 +217,20 @@ xfs_group_mark_sick(
	struct xfs_group	*xg,
	unsigned int		mask)
{
	unsigned int		old_mask;

	xfs_group_check_mask(xg, mask);
	trace_xfs_group_mark_sick(xg, mask);

	spin_lock(&xg->xg_state_lock);
	old_mask = xg->xg_sick;
	xg->xg_sick |= mask;
	spin_unlock(&xg->xg_state_lock);

	fserror_report_metadata(xg->xg_mount->m_super, -EFSCORRUPTED, GFP_NOFS);
	if (mask)
		xfs_healthmon_report_group(xg, XFS_HEALTHMON_SICK, old_mask,
				mask);
}

/*
@@ -217,15 +241,21 @@ xfs_group_mark_corrupt(
	struct xfs_group	*xg,
	unsigned int		mask)
{
	unsigned int		old_mask;

	xfs_group_check_mask(xg, mask);
	trace_xfs_group_mark_corrupt(xg, mask);

	spin_lock(&xg->xg_state_lock);
	old_mask = xg->xg_sick;
	xg->xg_sick |= mask;
	xg->xg_checked |= mask;
	spin_unlock(&xg->xg_state_lock);

	fserror_report_metadata(xg->xg_mount->m_super, -EFSCORRUPTED, GFP_NOFS);
	if (mask)
		xfs_healthmon_report_group(xg, XFS_HEALTHMON_CORRUPT, old_mask,
				mask);
}

/*
@@ -236,15 +266,22 @@ xfs_group_mark_healthy(
	struct xfs_group	*xg,
	unsigned int		mask)
{
	unsigned int		old_mask;

	xfs_group_check_mask(xg, mask);
	trace_xfs_group_mark_healthy(xg, mask);

	spin_lock(&xg->xg_state_lock);
	old_mask = xg->xg_sick;
	xg->xg_sick &= ~mask;
	if (!(xg->xg_sick & XFS_SICK_AG_PRIMARY))
		xg->xg_sick &= ~XFS_SICK_AG_SECONDARY;
	xg->xg_checked |= mask;
	spin_unlock(&xg->xg_state_lock);

	if (mask)
		xfs_healthmon_report_group(xg, XFS_HEALTHMON_HEALTHY, old_mask,
				mask);
}

/* Sample which per-ag metadata are unhealthy. */
@@ -283,10 +320,13 @@ xfs_inode_mark_sick(
	struct xfs_inode	*ip,
	unsigned int		mask)
{
	unsigned int		old_mask;

	ASSERT(!(mask & ~XFS_SICK_INO_ALL));
	trace_xfs_inode_mark_sick(ip, mask);

	spin_lock(&ip->i_flags_lock);
	old_mask = ip->i_sick;
	ip->i_sick |= mask;
	spin_unlock(&ip->i_flags_lock);

@@ -300,6 +340,9 @@ xfs_inode_mark_sick(
	spin_unlock(&VFS_I(ip)->i_lock);

	fserror_report_file_metadata(VFS_I(ip), -EFSCORRUPTED, GFP_NOFS);
	if (mask)
		xfs_healthmon_report_inode(ip, XFS_HEALTHMON_SICK, old_mask,
				mask);
}

/* Mark inode metadata as having been checked and found unhealthy by fsck. */
@@ -308,10 +351,13 @@ xfs_inode_mark_corrupt(
	struct xfs_inode	*ip,
	unsigned int		mask)
{
	unsigned int		old_mask;

	ASSERT(!(mask & ~XFS_SICK_INO_ALL));
	trace_xfs_inode_mark_corrupt(ip, mask);

	spin_lock(&ip->i_flags_lock);
	old_mask = ip->i_sick;
	ip->i_sick |= mask;
	ip->i_checked |= mask;
	spin_unlock(&ip->i_flags_lock);
@@ -326,6 +372,9 @@ xfs_inode_mark_corrupt(
	spin_unlock(&VFS_I(ip)->i_lock);

	fserror_report_file_metadata(VFS_I(ip), -EFSCORRUPTED, GFP_NOFS);
	if (mask)
		xfs_healthmon_report_inode(ip, XFS_HEALTHMON_CORRUPT, old_mask,
				mask);
}

/* Mark parts of an inode healed. */
@@ -334,15 +383,22 @@ xfs_inode_mark_healthy(
	struct xfs_inode	*ip,
	unsigned int		mask)
{
	unsigned int		old_mask;

	ASSERT(!(mask & ~XFS_SICK_INO_ALL));
	trace_xfs_inode_mark_healthy(ip, mask);

	spin_lock(&ip->i_flags_lock);
	old_mask = ip->i_sick;
	ip->i_sick &= ~mask;
	if (!(ip->i_sick & XFS_SICK_INO_PRIMARY))
		ip->i_sick &= ~XFS_SICK_INO_SECONDARY;
	ip->i_checked |= mask;
	spin_unlock(&ip->i_flags_lock);

	if (mask)
		xfs_healthmon_report_inode(ip, XFS_HEALTHMON_HEALTHY, old_mask,
				mask);
}

/* Sample which parts of an inode are unhealthy. */
@@ -422,6 +478,25 @@ xfs_fsop_geom_health(
	}
}

/*
 * Translate XFS_SICK_FS_* into XFS_FSOP_GEOM_SICK_* except for the rt free
 * space codes, which are sent via the rtgroup events.
 */
unsigned int
xfs_healthmon_fs_mask(
	unsigned int			sick_mask)
{
	const struct ioctl_sick_map	*m;
	unsigned int			ioctl_mask = 0;

	for_each_sick_map(fs_map, m) {
		if (sick_mask & m->sick_mask)
			ioctl_mask |= m->ioctl_mask;
	}

	return ioctl_mask;
}

static const struct ioctl_sick_map ag_map[] = {
	{ XFS_SICK_AG_SB,	XFS_AG_GEOM_SICK_SB },
	{ XFS_SICK_AG_AGF,	XFS_AG_GEOM_SICK_AGF },
@@ -458,6 +533,22 @@ xfs_ag_geom_health(
	}
}

/* Translate XFS_SICK_AG_* into XFS_AG_GEOM_SICK_*. */
unsigned int
xfs_healthmon_perag_mask(
	unsigned int			sick_mask)
{
	const struct ioctl_sick_map	*m;
	unsigned int			ioctl_mask = 0;

	for_each_sick_map(ag_map, m) {
		if (sick_mask & m->sick_mask)
			ioctl_mask |= m->ioctl_mask;
	}

	return ioctl_mask;
}

static const struct ioctl_sick_map rtgroup_map[] = {
	{ XFS_SICK_RG_SUPER,	XFS_RTGROUP_GEOM_SICK_SUPER },
	{ XFS_SICK_RG_BITMAP,	XFS_RTGROUP_GEOM_SICK_BITMAP },
@@ -488,6 +579,22 @@ xfs_rtgroup_geom_health(
	}
}

/* Translate XFS_SICK_RG_* into XFS_RTGROUP_GEOM_SICK_*. */
unsigned int
xfs_healthmon_rtgroup_mask(
	unsigned int			sick_mask)
{
	const struct ioctl_sick_map	*m;
	unsigned int			ioctl_mask = 0;

	for_each_sick_map(rtgroup_map, m) {
		if (sick_mask & m->sick_mask)
			ioctl_mask |= m->ioctl_mask;
	}

	return ioctl_mask;
}

static const struct ioctl_sick_map ino_map[] = {
	{ XFS_SICK_INO_CORE,	XFS_BS_SICK_INODE },
	{ XFS_SICK_INO_BMBTD,	XFS_BS_SICK_BMBTD },
@@ -526,6 +633,22 @@ xfs_bulkstat_health(
	}
}

/* Translate XFS_SICK_INO_* into XFS_BS_SICK_*. */
unsigned int
xfs_healthmon_inode_mask(
	unsigned int			sick_mask)
{
	const struct ioctl_sick_map	*m;
	unsigned int			ioctl_mask = 0;

	for_each_sick_map(ino_map, m) {
		if (sick_mask & m->sick_mask)
			ioctl_mask |= m->ioctl_mask;
	}

	return ioctl_mask;
}

/* Mark a block mapping sick. */
void
xfs_bmap_mark_sick(
+181 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@
#include "xfs_da_btree.h"
#include "xfs_quota_defs.h"
#include "xfs_rtgroup.h"
#include "xfs_health.h"
#include "xfs_healthmon.h"

#include <linux/anon_inodes.h>
@@ -174,6 +175,33 @@ xfs_healthmon_merge_events(
	case XFS_HEALTHMON_LOST:
		existing->lostcount += new->lostcount;
		return true;

	case XFS_HEALTHMON_SICK:
	case XFS_HEALTHMON_CORRUPT:
	case XFS_HEALTHMON_HEALTHY:
		switch (existing->domain) {
		case XFS_HEALTHMON_FS:
			existing->fsmask |= new->fsmask;
			return true;
		case XFS_HEALTHMON_AG:
		case XFS_HEALTHMON_RTGROUP:
			if (existing->group == new->group){
				existing->grpmask |= new->grpmask;
				return true;
			}
			return false;
		case XFS_HEALTHMON_INODE:
			if (existing->ino == new->ino &&
			    existing->gen == new->gen) {
				existing->imask |= new->imask;
				return true;
			}
			return false;
		default:
			ASSERT(0);
			return false;
		}
		return false;
	}

	return false;
@@ -337,6 +365,135 @@ xfs_healthmon_unmount(
	xfs_healthmon_put(hm);
}

/* Compute the reporting mask for non-unmount metadata health events. */
static inline unsigned int
metadata_event_mask(
	struct xfs_healthmon		*hm,
	enum xfs_healthmon_type		type,
	unsigned int			old_mask,
	unsigned int			new_mask)
{
	/* If we want all events, return all events. */
	if (hm->verbose)
		return new_mask;

	switch (type) {
	case XFS_HEALTHMON_SICK:
		/* Always report runtime corruptions */
		return new_mask;
	case XFS_HEALTHMON_CORRUPT:
		/* Only report new fsck errors */
		return new_mask & ~old_mask;
	case XFS_HEALTHMON_HEALTHY:
		/* Only report healthy metadata that got fixed */
		return new_mask & old_mask;
	default:
		ASSERT(0);
		break;
	}

	return 0;
}

/* Report XFS_FS_SICK_* events to healthmon */
void
xfs_healthmon_report_fs(
	struct xfs_mount		*mp,
	enum xfs_healthmon_type		type,
	unsigned int			old_mask,
	unsigned int			new_mask)
{
	struct xfs_healthmon_event	event = {
		.type			= type,
		.domain			= XFS_HEALTHMON_FS,
	};
	struct xfs_healthmon		*hm = xfs_healthmon_get(mp);

	if (!hm)
		return;

	event.fsmask = metadata_event_mask(hm, type, old_mask, new_mask) &
			~XFS_SICK_FS_SECONDARY;
	trace_xfs_healthmon_report_fs(hm, old_mask, new_mask, &event);

	if (event.fsmask)
		xfs_healthmon_push(hm, &event);

	xfs_healthmon_put(hm);
}

/* Report XFS_SICK_(AG|RG)* flags to healthmon */
void
xfs_healthmon_report_group(
	struct xfs_group		*xg,
	enum xfs_healthmon_type		type,
	unsigned int			old_mask,
	unsigned int			new_mask)
{
	struct xfs_healthmon_event	event = {
		.type			= type,
		.group			= xg->xg_gno,
	};
	struct xfs_healthmon		*hm = xfs_healthmon_get(xg->xg_mount);

	if (!hm)
		return;

	switch (xg->xg_type) {
	case XG_TYPE_RTG:
		event.domain = XFS_HEALTHMON_RTGROUP;
		event.grpmask = metadata_event_mask(hm, type, old_mask,
						    new_mask) &
				~XFS_SICK_RG_SECONDARY;
		break;
	case XG_TYPE_AG:
		event.domain = XFS_HEALTHMON_AG;
		event.grpmask = metadata_event_mask(hm, type, old_mask,
						    new_mask) &
				~XFS_SICK_AG_SECONDARY;
		break;
	default:
		ASSERT(0);
		break;
	}

	trace_xfs_healthmon_report_group(hm, old_mask, new_mask, &event);

	if (event.grpmask)
		xfs_healthmon_push(hm, &event);

	xfs_healthmon_put(hm);
}

/* Report XFS_SICK_INO_* flags to healthmon */
void
xfs_healthmon_report_inode(
	struct xfs_inode		*ip,
	enum xfs_healthmon_type		type,
	unsigned int			old_mask,
	unsigned int			new_mask)
{
	struct xfs_healthmon_event	event = {
		.type			= type,
		.domain			= XFS_HEALTHMON_INODE,
		.ino			= ip->i_ino,
		.gen			= VFS_I(ip)->i_generation,
	};
	struct xfs_healthmon		*hm = xfs_healthmon_get(ip->i_mount);

	if (!hm)
		return;

	event.imask = metadata_event_mask(hm, type, old_mask, new_mask) &
			~XFS_SICK_INO_SECONDARY;
	trace_xfs_healthmon_report_inode(hm, old_mask, event.imask, &event);

	if (event.imask)
		xfs_healthmon_push(hm, &event);

	xfs_healthmon_put(hm);
}

static inline void
xfs_healthmon_reset_outbuf(
	struct xfs_healthmon		*hm)
@@ -347,11 +504,19 @@ xfs_healthmon_reset_outbuf(

static const unsigned int domain_map[] = {
	[XFS_HEALTHMON_MOUNT]		= XFS_HEALTH_MONITOR_DOMAIN_MOUNT,
	[XFS_HEALTHMON_FS]		= XFS_HEALTH_MONITOR_DOMAIN_FS,
	[XFS_HEALTHMON_AG]		= XFS_HEALTH_MONITOR_DOMAIN_AG,
	[XFS_HEALTHMON_INODE]		= XFS_HEALTH_MONITOR_DOMAIN_INODE,
	[XFS_HEALTHMON_RTGROUP]		= XFS_HEALTH_MONITOR_DOMAIN_RTGROUP,
};

static const unsigned int type_map[] = {
	[XFS_HEALTHMON_RUNNING]		= XFS_HEALTH_MONITOR_TYPE_RUNNING,
	[XFS_HEALTHMON_LOST]		= XFS_HEALTH_MONITOR_TYPE_LOST,
	[XFS_HEALTHMON_SICK]		= XFS_HEALTH_MONITOR_TYPE_SICK,
	[XFS_HEALTHMON_CORRUPT]		= XFS_HEALTH_MONITOR_TYPE_CORRUPT,
	[XFS_HEALTHMON_HEALTHY]		= XFS_HEALTH_MONITOR_TYPE_HEALTHY,
	[XFS_HEALTHMON_UNMOUNT]		= XFS_HEALTH_MONITOR_TYPE_UNMOUNT,
};

/* Render event as a V0 structure */
@@ -384,6 +549,22 @@ xfs_healthmon_format_v0(
			break;
		}
		break;
	case XFS_HEALTHMON_FS:
		hme.e.fs.mask = xfs_healthmon_fs_mask(event->fsmask);
		break;
	case XFS_HEALTHMON_RTGROUP:
		hme.e.group.mask = xfs_healthmon_rtgroup_mask(event->grpmask);
		hme.e.group.gno = event->group;
		break;
	case XFS_HEALTHMON_AG:
		hme.e.group.mask = xfs_healthmon_perag_mask(event->grpmask);
		hme.e.group.gno = event->group;
		break;
	case XFS_HEALTHMON_INODE:
		hme.e.inode.mask = xfs_healthmon_inode_mask(event->imask);
		hme.e.inode.ino = event->ino;
		hme.e.inode.gen = event->gen;
		break;
	default:
		break;
	}
+39 −0
Original line number Diff line number Diff line
@@ -71,10 +71,21 @@ enum xfs_healthmon_type {
	XFS_HEALTHMON_RUNNING,	/* monitor running */
	XFS_HEALTHMON_LOST,	/* message lost */
	XFS_HEALTHMON_UNMOUNT,	/* filesystem is unmounting */

	/* metadata health events */
	XFS_HEALTHMON_SICK,	/* runtime corruption observed */
	XFS_HEALTHMON_CORRUPT,	/* fsck reported corruption */
	XFS_HEALTHMON_HEALTHY,	/* fsck reported healthy structure */
};

enum xfs_healthmon_domain {
	XFS_HEALTHMON_MOUNT,	/* affects the whole fs */

	/* metadata health events */
	XFS_HEALTHMON_FS,	/* main filesystem metadata */
	XFS_HEALTHMON_AG,	/* allocation group metadata */
	XFS_HEALTHMON_INODE,	/* inode metadata */
	XFS_HEALTHMON_RTGROUP,	/* realtime group metadata */
};

struct xfs_healthmon_event {
@@ -90,8 +101,36 @@ struct xfs_healthmon_event {
		struct {
			uint64_t	lostcount;
		};
		/* fs/rt metadata */
		struct {
			/* XFS_SICK_* flags */
			unsigned int	fsmask;
		};
		/* ag/rtgroup metadata */
		struct {
			/* XFS_SICK_(AG|RG)* flags */
			unsigned int	grpmask;
			unsigned int	group;
		};
		/* inode metadata */
		struct {
			/* XFS_SICK_INO_* flags */
			unsigned int	imask;
			uint32_t	gen;
			xfs_ino_t	ino;
		};
	};
};

void xfs_healthmon_report_fs(struct xfs_mount *mp,
		enum xfs_healthmon_type type, unsigned int old_mask,
		unsigned int new_mask);
void xfs_healthmon_report_group(struct xfs_group *xg,
		enum xfs_healthmon_type type, unsigned int old_mask,
		unsigned int new_mask);
void xfs_healthmon_report_inode(struct xfs_inode *ip,
		enum xfs_healthmon_type type, unsigned int old_mask,
		unsigned int new_mask);

long xfs_ioc_health_monitor(struct file *file,
		struct xfs_health_monitor __user *arg);
Loading