diff --git a/include/sys/spa.h b/include/sys/spa.h index aa66d489ef..6f3b6a2da4 100644 --- a/include/sys/spa.h +++ b/include/sys/spa.h @@ -911,6 +911,7 @@ typedef struct spa_stats { spa_history_kstat_t state; /* pool state */ spa_history_kstat_t guid; /* pool guid */ spa_history_kstat_t iostats; + spa_history_kstat_t fragmentation[5]; } spa_stats_t; typedef enum txg_state { diff --git a/module/zfs/spa_stats.c b/module/zfs/spa_stats.c index 17ed2a620b..46a797c3cf 100644 --- a/module/zfs/spa_stats.c +++ b/module/zfs/spa_stats.c @@ -24,6 +24,7 @@ #include #include #include +#include /* * Keeps stats on last N reads per spa_t, disabled by default. @@ -302,6 +303,105 @@ spa_txg_history_init(spa_t *spa) offsetof(spa_txg_history_t, sth_node)); } +typedef struct { + spa_t *spa; + int metaclass; +} freespace_histogram_stat_t; + +static void * +spa_frag_addr(kstat_t *ksp, loff_t n) +{ + if (n == 0) { + freespace_histogram_stat_t *stat = ksp->ks_private; + switch (stat->metaclass) { + case 0: + return (spa_normal_class(stat->spa)); + case 1: + return (spa_log_class(stat->spa)); + case 2: + return (spa_embedded_log_class(stat->spa)); + case 3: + return (spa_special_class(stat->spa)); + case 4: + return (spa_dedup_class(stat->spa)); + } + } + return (NULL); +} + +static int +spa_frag_data(char *buf, size_t size, void *data) +{ + metaslab_class_t *mc = data; + size_t offset = 0; + int res; + // without this, it becomes a data-leak, and un-initialized strings + // can be returned if all buckets are 0 + buf[0] = 0; + + for (int i = 0; i < RANGE_TREE_HISTOGRAM_SIZE; i++) { + if (mc->mc_histogram[i] > 0) { + res = snprintf(buf + offset, size, "%d %llu\n", i, + (u_longlong_t)mc->mc_histogram[i]); + offset += res; + size -= res; + } + } + + return (0); +} + +static void +spa_fragmentation_init(spa_t *spa) +{ + char *dirname; + const char *statnames[] = { + "fragmentation_normal", + "fragmentation_log", + "fragmentation_embedded_log", + "fragmentation_special", + "fragmentation_dedup" + }; + for (int i = 0; i < 5; i++) { + spa_history_kstat_t *shk = &spa->spa_stats.fragmentation[i]; + kstat_t *ksp; + + dirname = kmem_asprintf("zfs/%s", spa_name(spa)); + ksp = kstat_create(dirname, 0, statnames[i], "misc", + KSTAT_TYPE_RAW, 0, KSTAT_FLAG_VIRTUAL); + kmem_strfree(dirname); + + shk->kstat = ksp; + if (ksp) { + freespace_histogram_stat_t *stat = kmem_zalloc( + sizeof (freespace_histogram_stat_t), KM_SLEEP); + stat->spa = spa; + stat->metaclass = i; + ksp->ks_private = stat; + ksp->ks_flags |= KSTAT_FLAG_NO_HEADERS; + kstat_set_raw_ops(ksp, NULL, spa_frag_data, + spa_frag_addr); + kstat_install(ksp); + } + } +} + +static void +spa_fragmentation_destroy(spa_t *spa) +{ + for (int i = 0; i < 5; i++) { + spa_history_kstat_t *shk = &spa->spa_stats.fragmentation[i]; + kstat_t *ksp = shk->kstat; + if (ksp) { + if (ksp->ks_private) { + kmem_free(ksp->ks_private, + sizeof (freespace_histogram_stat_t)); + } + kstat_delete(ksp); + } + } +} + static void spa_txg_history_destroy(spa_t *spa) { @@ -855,7 +955,7 @@ spa_guid_init(spa_t *spa) } static void -spa_health_destroy(spa_t *spa) +spa_state_destroy(spa_t *spa) { spa_history_kstat_t *shk = &spa->spa_stats.state; kstat_t *ksp = shk->kstat; @@ -998,18 +1098,20 @@ spa_stats_init(spa_t *spa) spa_state_init(spa); spa_guid_init(spa); spa_iostats_init(spa); + spa_fragmentation_init(spa); } void spa_stats_destroy(spa_t *spa) { + spa_fragmentation_destroy(spa); spa_iostats_destroy(spa); - spa_health_destroy(spa); + spa_guid_destroy(spa); + spa_state_destroy(spa); + spa_mmp_history_destroy(spa); spa_tx_assign_destroy(spa); spa_txg_history_destroy(spa); spa_read_history_destroy(spa); - spa_mmp_history_destroy(spa); - spa_guid_destroy(spa); } ZFS_MODULE_PARAM(zfs, zfs_, read_history, UINT, ZMOD_RW,