Skip to content

Commit

Permalink
Record skipped MMP writes in multihost_history
Browse files Browse the repository at this point in the history
Once per pass through the MMP thread's loop, the vdev tree is walked to
find a suitable leaf to write the next MMP block to.  If no such leaf is
found, the thread sleeps for a while and resumes at the top of the loop.

Add an entry to multihost_history when no leaf can be found, and record
the reason in the error column.  The errors are:

MMP_TREE_RO (120)         No vdevs were writeable.

MMP_WRITE_PENDING (121)   At least one writeable leaf vdev was found,
                          but it had a pending MMP write.

MMP_WRITE_OTHER (122)     An unknown error occurred.

Other fields with different than usual meaning:

timestamp = the time in seconds since the epoch when no leaf could be
found originally.

duration = the time (in ns) during which no MMP block was written for
this reason.  This does not include the preceeding inter-write period
nor the following inter-write period.

vdev_guid = the number of sequential cycles of the MMP thread looop when
this occurred.

Sample output, truncated to fit:

id   txg  timestamp   error  duration    mmp_delay   vdev_guid             ...
152  360  1519694711  0      171169      943979470   8026554115788855270   ...
153  360  1519694712  0      169235      939206561   8026554115788855270   ...
154  360  1519694712  0      5005999224  927254856   17691982913947744211  ...
155  360  1519694712  0      5009048602  922635599   9220496304014385470   ...
156  360  1519694713  0      3004119061  922635599   8026554115788855270   ...
157  360  1519694713  121    2664070025  922635599   9                     ...
158  360  1519694716  0      3009996115  3656036502  8026554115788855270   ...
159  360  1519694716  121    665978835   3656036502  3                     ...
160  360  1519694717  0      5005954499  3637910364  17691982913947744211  ...
161  360  1519694718  0      5009012869  3612114592  9220496304014385470   ...
162  360  1519694718  121    998937783   3612114592  4                     ...
163  360  1519694719  0      3008000079  3596894279  8026554115788855270   ...

Signed-off-by: Olaf Faaland <faaland1@llnl.gov>
  • Loading branch information
ofaaland committed Feb 27, 2018
1 parent 1c25956 commit 9c01b92
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 34 deletions.
15 changes: 15 additions & 0 deletions include/sys/mmp.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ typedef struct mmp_thread {
uberblock_t mmp_ub; /* last ub written by sync */
zio_t *mmp_zio_root; /* root of mmp write zios */
uint64_t mmp_kstat_id; /* unique id for next MMP write kstat */
int mmp_skip_error; /* reason for last skipped write */
} mmp_thread_t;


Expand All @@ -58,6 +59,20 @@ extern ulong_t zfs_multihost_interval;
extern uint_t zfs_multihost_fail_intervals;
extern uint_t zfs_multihost_import_intervals;

/*
* If MMP is unable to find a leaf vdev to write an MMP block to,
* it will set *error to one of the below error codes.
*
* MMP_WRITE_PENDING At least one writeable leaf vdev was found, but it had a
* pending MMP write.
* MMP_TREE_RO At least one leaf vdev was found, but none were writeable.
*/
typedef enum mmp_error {
MMP_WRITE_TREE_RO = ENETDOWN,
MMP_WRITE_PENDING,
MMP_WRITE_OTHER
} mmp_error_t;

#ifdef __cplusplus
}
#endif
Expand Down
5 changes: 3 additions & 2 deletions include/sys/spa.h
Original file line number Diff line number Diff line change
Expand Up @@ -887,10 +887,11 @@ extern txg_stat_t *spa_txg_history_init_io(spa_t *, uint64_t,
struct dsl_pool *);
extern void spa_txg_history_fini_io(spa_t *, txg_stat_t *);
extern void spa_tx_assign_add_nsecs(spa_t *spa, uint64_t nsecs);
extern int spa_mmp_history_set_skip(spa_t *spa, uint64_t mmp_kstat_id);
extern int spa_mmp_history_set(spa_t *spa, uint64_t mmp_kstat_id, int io_error,
hrtime_t duration);
extern void spa_mmp_history_add(uint64_t txg, uint64_t timestamp,
uint64_t mmp_delay, vdev_t *vd, int label, uint64_t mmp_kstat_id);
extern void *spa_mmp_history_add(spa_t *spa, uint64_t txg, uint64_t timestamp,
uint64_t mmp_delay, vdev_t *vd, int label, uint64_t mmp_kstat_id, int error);

/* Pool configuration locks */
extern int spa_config_tryenter(spa_t *spa, int locks, void *tag, krw_t rw);
Expand Down
95 changes: 74 additions & 21 deletions module/zfs/mmp.c
Original file line number Diff line number Diff line change
Expand Up @@ -201,42 +201,82 @@ mmp_thread_stop(spa_t *spa)
}

/*
* Choose a leaf vdev to write an MMP block to. It must not have an
* outstanding mmp write (if so then there is a problem, and a new write will
* also block). If there is no usable leaf in this subtree return NULL,
* otherwise return a pointer to the leaf.
*
* When walking the subtree, a random child is chosen as the starting point so
* that when the tree is healthy, the leaf chosen will be random with even
* distribution. If there are unhealthy vdevs in the tree, the distribution
* will be really poor only if a large proportion of the vdevs are unhealthy,
* in which case there are other more pressing problems.
* Flags for recording the state of the vdev tree with regards to MMP.
*/
typedef enum mmp_vdev_state_flag {
MMP_FAIL_NOT_WRITABLE = (1 << 0),
MMP_FAIL_WRITE_PENDING = (1 << 1),
} mmp_vdev_state_flag_t;

static vdev_t *
mmp_random_leaf(vdev_t *vd)
mmp_random_leaf_impl(vdev_t *vd, int *fail_mask)
{
int child_idx;

if (!vdev_writeable(vd))
if (!vdev_writeable(vd)) {
*fail_mask |= MMP_FAIL_NOT_WRITABLE;
return (NULL);
}

if (vd->vdev_ops->vdev_op_leaf) {
vdev_t *ret;

if (vd->vdev_ops->vdev_op_leaf)
return (vd->vdev_mmp_pending == 0 ? vd : NULL);
if (vd->vdev_mmp_pending != 0) {
*fail_mask |= MMP_FAIL_WRITE_PENDING;
ret = NULL;
} else {
ret = vd;
}

return (ret);
}

child_idx = spa_get_random(vd->vdev_children);
for (int offset = vd->vdev_children; offset > 0; offset--) {
vdev_t *leaf;
vdev_t *child = vd->vdev_child[(child_idx + offset) %
vd->vdev_children];

leaf = mmp_random_leaf(child);
leaf = mmp_random_leaf_impl(child, fail_mask);
if (leaf)
return (leaf);
}

return (NULL);
}

/*
* Choose a leaf vdev to write an MMP block to. It must not have an
* outstanding mmp write (if so then there is a problem, and a new write will
* also block). If there is no usable leaf in this subtree return NULL,
* otherwise return a pointer to the leaf.
*
* When walking the subtree, a random child is chosen as the starting point so
* that when the tree is healthy, the leaf chosen will be random with even
* distribution. If there are unhealthy vdevs in the tree, the distribution
* will be really poor only if a large proportion of the vdevs are unhealthy,
* in which case there are other more pressing problems.
*/

static vdev_t *
mmp_random_leaf(vdev_t *in_vd, int *error)
{
int fail_mask = 0;
vdev_t *out_vd = mmp_random_leaf_impl(in_vd, &fail_mask);

*error = 0;
if (out_vd == NULL) {
if (fail_mask && MMP_FAIL_WRITE_PENDING)
*error = MMP_WRITE_PENDING;
else if (fail_mask && MMP_FAIL_WRITE_PENDING)
*error = MMP_WRITE_TREE_RO;
else
*error = MMP_WRITE_OTHER;
}

return (out_vd);
}

static void
mmp_write_done(zio_t *zio)
{
Expand Down Expand Up @@ -320,7 +360,7 @@ mmp_write_uberblock(spa_t *spa)
mmp_thread_t *mmp = &spa->spa_mmp;
uberblock_t *ub;
vdev_t *vd;
int label;
int label, error;
uint64_t offset;

hrtime_t lock_acquire_time = gethrtime();
Expand All @@ -330,13 +370,25 @@ mmp_write_uberblock(spa_t *spa)
zfs_dbgmsg("SCL_STATE acquisition took %llu ns\n",
(u_longlong_t)lock_acquire_time);

vd = mmp_random_leaf(spa->spa_root_vdev);
vd = mmp_random_leaf(spa->spa_root_vdev, &error);

mutex_enter(&mmp->mmp_io_lock);

if (vd == NULL) {
if (mmp->mmp_skip_error == error) {
spa_mmp_history_set_skip(spa, mmp->mmp_kstat_id - 1);
} else {
mmp->mmp_skip_error = error;
spa_mmp_history_add(spa, mmp->mmp_ub.ub_txg,
gethrestime_sec(), mmp->mmp_delay, NULL, 0,
mmp->mmp_kstat_id++, error);
}
mutex_exit(&mmp->mmp_io_lock);
spa_config_exit(spa, SCL_STATE, FTAG);
return;
}

mutex_enter(&mmp->mmp_io_lock);
mmp->mmp_skip_error = 0;

if (mmp->mmp_zio_root == NULL)
mmp->mmp_zio_root = zio_root(spa, NULL, NULL,
Expand All @@ -347,13 +399,14 @@ mmp_write_uberblock(spa_t *spa)
ub->ub_mmp_magic = MMP_MAGIC;
ub->ub_mmp_delay = mmp->mmp_delay;
vd->vdev_mmp_pending = gethrtime();
vd->vdev_mmp_kstat_id = mmp->mmp_kstat_id++;
vd->vdev_mmp_kstat_id = mmp->mmp_kstat_id;

zio_t *zio = zio_null(mmp->mmp_zio_root, spa, NULL, NULL, NULL, flags);
abd_t *ub_abd = abd_alloc_for_io(VDEV_UBERBLOCK_SIZE(vd), B_TRUE);
abd_zero(ub_abd, VDEV_UBERBLOCK_SIZE(vd));
abd_copy_from_buf(ub_abd, ub, sizeof (uberblock_t));

mmp->mmp_kstat_id++;
mutex_exit(&mmp->mmp_io_lock);

offset = VDEV_UBERBLOCK_OFFSET(vd, VDEV_UBERBLOCK_COUNT(vd) -
Expand All @@ -364,8 +417,8 @@ mmp_write_uberblock(spa_t *spa)
VDEV_UBERBLOCK_SIZE(vd), mmp_write_done, mmp,
flags | ZIO_FLAG_DONT_PROPAGATE);

spa_mmp_history_add(ub->ub_txg, ub->ub_timestamp, ub->ub_mmp_delay, vd,
label, vd->vdev_mmp_kstat_id);
(void) spa_mmp_history_add(spa, ub->ub_txg, ub->ub_timestamp,
ub->ub_mmp_delay, vd, label, vd->vdev_mmp_kstat_id, 0);

zio_nowait(zio);
}
Expand Down
65 changes: 54 additions & 11 deletions module/zfs/spa_stats.c
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,7 @@ typedef struct spa_mmp_history {
char *vdev_path;
uint64_t vdev_label; /* vdev label */
int io_error; /* error status of MMP write */
uint64_t error_start; /* hrtime of start of error period */
hrtime_t duration; /* time from submission to completion */
list_node_t smh_link;
} spa_mmp_history_t;
Expand All @@ -748,13 +749,13 @@ spa_mmp_history_data(char *buf, size_t size, void *data)
{
spa_mmp_history_t *smh = (spa_mmp_history_t *)data;

(void) snprintf(buf, size, "%-10llu %-10llu %-10llu %-6lld %-10lld "
"%-12llu %-24llu %-10llu %s\n",
(void) snprintf(buf, size, "%-10llu %-10llu %10llu %6lld %10lld "
"%12llu %-24llu %-10llu %s\n",
(u_longlong_t)smh->mmp_kstat_id, (u_longlong_t)smh->txg,
(u_longlong_t)smh->timestamp, (longlong_t)smh->io_error,
(longlong_t)smh->duration, (u_longlong_t)smh->mmp_delay,
(u_longlong_t)smh->vdev_guid, (u_longlong_t)smh->vdev_label,
(smh->vdev_path ? smh->vdev_path : "-"));
(smh->vdev_path ? smh->vdev_path : "-"));

return (0);
}
Expand Down Expand Up @@ -869,6 +870,39 @@ spa_mmp_history_destroy(spa_t *spa)
mutex_destroy(&ssh->lock);
}

/*
* Set MMP delay in existing "skip" record.
*
* Also need to switch from starting with list_head() to starting with
* list_tail() both here and in spa_mmp_history_set() so the search is O(1)
* unless something has gone horribly wrong.
*/
int
spa_mmp_history_set_skip(spa_t *spa, uint64_t mmp_kstat_id)
{
spa_stats_history_t *ssh = &spa->spa_stats.mmp_history;
spa_mmp_history_t *smh;
int error = ENOENT;

if (zfs_multihost_history == 0 && ssh->size == 0)
return (0);

mutex_enter(&ssh->lock);
for (smh = list_head(&ssh->list); smh != NULL;
smh = list_next(&ssh->list, smh)) {
if (smh->mmp_kstat_id == mmp_kstat_id) {
ASSERT(smh->io_error != 0);
smh->duration = gethrtime() - smh->error_start;
smh->vdev_guid++;
error = 0;
break;
}
}
mutex_exit(&ssh->lock);

return (error);
}

/*
* Set MMP write duration and error status in existing record.
*/
Expand All @@ -887,6 +921,7 @@ spa_mmp_history_set(spa_t *spa, uint64_t mmp_kstat_id, int io_error,
for (smh = list_head(&ssh->list); smh != NULL;
smh = list_next(&ssh->list, smh)) {
if (smh->mmp_kstat_id == mmp_kstat_id) {
ASSERT(smh->io_error != MMP_ENOMEDIUM);
smh->io_error = io_error;
smh->duration = duration;
error = 0;
Expand All @@ -901,27 +936,34 @@ spa_mmp_history_set(spa_t *spa, uint64_t mmp_kstat_id, int io_error,
/*
* Add a new MMP write to historical record.
*/
void
spa_mmp_history_add(uint64_t txg, uint64_t timestamp, uint64_t mmp_delay,
vdev_t *vd, int label, uint64_t mmp_kstat_id)
void *
spa_mmp_history_add(spa_t *spa, uint64_t txg, uint64_t timestamp, uint64_t mmp_delay,
vdev_t *vd, int label, uint64_t mmp_kstat_id, int error)
{
spa_t *spa = vd->vdev_spa;
spa_stats_history_t *ssh = &spa->spa_stats.mmp_history;
spa_mmp_history_t *smh, *rm;

if (zfs_multihost_history == 0 && ssh->size == 0)
return;
return (NULL);

smh = kmem_zalloc(sizeof (spa_mmp_history_t), KM_SLEEP);
smh->txg = txg;
smh->timestamp = timestamp;
smh->mmp_delay = mmp_delay;
smh->vdev_guid = vd->vdev_guid;
if (vd->vdev_path)
smh->vdev_path = strdup(vd->vdev_path);
if (vd) {
smh->vdev_guid = vd->vdev_guid;
if (vd->vdev_path)
smh->vdev_path = strdup(vd->vdev_path);
}
smh->vdev_label = label;
smh->mmp_kstat_id = mmp_kstat_id;

if (error) {
smh->io_error = error;
smh->error_start = gethrtime();
smh->vdev_guid = 1;
}

mutex_enter(&ssh->lock);

list_insert_head(&ssh->list, smh);
Expand All @@ -936,6 +978,7 @@ spa_mmp_history_add(uint64_t txg, uint64_t timestamp, uint64_t mmp_delay,
}

mutex_exit(&ssh->lock);
return ((void *)smh);
}

void
Expand Down

0 comments on commit 9c01b92

Please sign in to comment.