Skip to content

Commit 8f91901

Browse files
shroffniigaw
authored andcommitted
libnvme: add support for retrieving per-path gendisk I/O statistics
Gendisk I/O statistics provide useful insight into disk activity, including read/write/discard/flush operations, as well as information about in-flight I/Os and I/O timing. Parsing these statistics allows users to determine the number of I/Os processed, time spent servicing I/O, number of sectors accessed, and the count of in-flight requests. Add support for retrieving per-path gendisk I/O statistics. Also add support for computing deltas of these statistics between samples, such as I/O ticks, number of sectors, and number of serviced I/Os. These metrics can be used by tools such as nvme-top to display real-time disk activity. Per-path metrics are particularly useful when NVMe native multipath is enabled. Signed-off-by: Nilay Shroff <[email protected]> Link: https://patch.msgid.link/[email protected] Signed-off-by: Daniel Wagner <[email protected]>
1 parent 7e797f4 commit 8f91901

4 files changed

Lines changed: 405 additions & 0 deletions

File tree

libnvme/src/libnvme.ld

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,20 @@ LIBNVME_3 {
138138
libnvme_open;
139139
libnvme_path_get_ana_state;
140140
libnvme_path_get_ctrl;
141+
libnvme_path_get_inflights;
142+
libnvme_path_get_io_ticks;
141143
libnvme_path_get_ns;
142144
libnvme_path_get_numa_nodes;
143145
libnvme_path_get_queue_depth;
146+
libnvme_path_get_read_ios;
147+
libnvme_path_get_read_sectors;
148+
libnvme_path_get_read_ticks;
149+
libnvme_path_get_stat_interval;
150+
libnvme_path_get_write_ios;
151+
libnvme_path_get_write_sectors;
152+
libnvme_path_get_write_ticks;
153+
libnvme_path_reset_stat;
154+
libnvme_path_update_stat;
144155
libnvme_random_uuid;
145156
libnvme_read_config;
146157
libnvme_read_hostid;

libnvme/src/nvme/private.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
#include "nvme/nvme-types.h"
1616
#include "nvme/lib-types.h"
1717

18+
#include <nvme/tree.h>
19+
1820
const char *libnvme_subsys_sysfs_dir(void);
1921
const char *libnvme_ctrl_sysfs_dir(void);
2022
const char *libnvme_ns_sysfs_dir(void);
@@ -170,10 +172,38 @@ struct libnvme_transport_handle {
170172
struct libnvme_log *log;
171173
};
172174

175+
enum libnvme_stat_group {
176+
READ = 0,
177+
WRITE,
178+
DISCARD,
179+
FLUSH,
180+
181+
NR_STAT_GROUPS
182+
};
183+
184+
struct libnvme_stat {
185+
struct {
186+
unsigned long ios;
187+
unsigned long merges;
188+
unsigned long long sectors;
189+
unsigned int ticks; /* in milliseconds */
190+
} group[NR_STAT_GROUPS];
191+
192+
unsigned int inflights;
193+
unsigned int io_ticks; /* in milliseconds */
194+
unsigned int tot_ticks; /* in milliseconds */
195+
196+
double ts_ms; /* timestamp when the stat is updated */
197+
};
198+
173199
struct libnvme_path { // !generate-accessors
174200
struct list_node entry;
175201
struct list_node nentry;
176202

203+
struct libnvme_stat stat[2]; /* gendisk I/O stat */
204+
unsigned int curr_idx; /* current index into the stat[] */
205+
bool diffstat; // !accessors:none
206+
177207
struct libnvme_ctrl *c;
178208
struct libnvme_ns *n;
179209

libnvme/src/nvme/tree.c

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <stdlib.h>
1616
#include <string.h>
1717
#include <unistd.h>
18+
#include <time.h>
1819

1920
#include <sys/stat.h>
2021
#include <sys/types.h>
@@ -884,6 +885,276 @@ __public char *libnvme_path_get_numa_nodes(libnvme_path_t p)
884885
return p->numa_nodes;
885886
}
886887

888+
static libnvme_stat_t libnvme_path_get_stat(libnvme_path_t p, unsigned int idx)
889+
{
890+
if (idx > 1)
891+
return NULL;
892+
893+
return &p->stat[idx];
894+
}
895+
896+
__public void libnvme_path_reset_stat(libnvme_path_t p)
897+
{
898+
libnvme_stat_t stat = &p->stat[0];
899+
900+
memset(stat, 0, 2 * sizeof(struct libnvme_stat));
901+
}
902+
903+
static int libnvme_update_stat(const char *sysfs_stat_path, libnvme_stat_t stat)
904+
{
905+
int n;
906+
struct timespec ts;
907+
unsigned long rd_ios, rd_merges, wr_ios, wr_merges;
908+
unsigned long dc_ios, dc_merges, fl_ios;
909+
unsigned long long rd_sectors, wr_sectors, dc_sectors;
910+
unsigned int rd_ticks, wr_ticks, dc_ticks, fl_ticks;
911+
unsigned int io_ticks, tot_ticks, inflights;
912+
913+
memset(stat, 0, sizeof(struct libnvme_stat));
914+
915+
n = sscanf(sysfs_stat_path,
916+
"%lu %lu %llu %u %lu %lu %llu %u %u %u %u %lu %lu %llu %u %lu %u",
917+
&rd_ios, &rd_merges, &rd_sectors, &rd_ticks,
918+
&wr_ios, &wr_merges, &wr_sectors, &wr_ticks,
919+
&inflights, &io_ticks, &tot_ticks,
920+
&dc_ios, &dc_merges, &dc_sectors, &dc_ticks,
921+
&fl_ios, &fl_ticks);
922+
923+
if (n < 17)
924+
return -EINVAL;
925+
926+
/* update read stat */
927+
stat->group[READ].ios = rd_ios;
928+
stat->group[READ].merges = rd_merges;
929+
stat->group[READ].sectors = rd_sectors;
930+
stat->group[READ].ticks = rd_ticks;
931+
932+
/* update write stat */
933+
stat->group[WRITE].ios = wr_ios;
934+
stat->group[WRITE].merges = wr_merges;
935+
stat->group[WRITE].sectors = wr_sectors;
936+
stat->group[WRITE].ticks = wr_ticks;
937+
938+
/* update inflight counters and ticks */
939+
stat->inflights = inflights;
940+
stat->io_ticks = io_ticks;
941+
stat->tot_ticks = tot_ticks;
942+
943+
/* update discard stat */
944+
stat->group[DISCARD].ios = dc_ios;
945+
stat->group[DISCARD].merges = dc_merges;
946+
stat->group[DISCARD].sectors = dc_sectors;
947+
stat->group[DISCARD].ticks = dc_ticks;
948+
949+
/* update flush stat */
950+
stat->group[FLUSH].ios = fl_ios;
951+
stat->group[FLUSH].ticks = fl_ticks;
952+
953+
clock_gettime(CLOCK_MONOTONIC, &ts);
954+
stat->ts_ms = ts.tv_sec * 1000 + (double)ts.tv_nsec / 1e6;
955+
956+
return 0;
957+
}
958+
959+
__public int libnvme_path_update_stat(libnvme_path_t p, bool diffstat)
960+
{
961+
__cleanup_free char *sysfs_stat_path = NULL;
962+
libnvme_stat_t stat;
963+
964+
p->diffstat = diffstat;
965+
p->curr_idx ^= 1;
966+
stat = libnvme_path_get_stat(p, p->curr_idx);
967+
if (!stat)
968+
return -EINVAL;
969+
970+
sysfs_stat_path = libnvme_get_path_attr(p, "stat");
971+
if (!sysfs_stat_path)
972+
return -EINVAL;
973+
974+
return libnvme_update_stat(sysfs_stat_path, stat);
975+
}
976+
977+
static int libnvme_stat_get_inflights(libnvme_stat_t stat)
978+
{
979+
return stat->inflights;
980+
}
981+
982+
__public unsigned int libnvme_path_get_inflights(libnvme_path_t p)
983+
{
984+
libnvme_stat_t curr;
985+
986+
curr = libnvme_path_get_stat(p, p->curr_idx);
987+
if (!curr)
988+
return 0;
989+
990+
return libnvme_stat_get_inflights(curr);
991+
}
992+
993+
static int libnvme_stat_get_io_ticks(libnvme_stat_t curr, libnvme_stat_t prev,
994+
bool diffstat)
995+
{
996+
unsigned int delta = 0;
997+
998+
if (!diffstat)
999+
return curr->io_ticks;
1000+
1001+
if (curr->io_ticks > prev->io_ticks)
1002+
delta = curr->io_ticks - prev->io_ticks;
1003+
1004+
return delta;
1005+
}
1006+
1007+
__public unsigned int libnvme_path_get_io_ticks(libnvme_path_t p)
1008+
{
1009+
libnvme_stat_t curr, prev;
1010+
1011+
curr = libnvme_path_get_stat(p, p->curr_idx);
1012+
prev = libnvme_path_get_stat(p, !p->curr_idx);
1013+
1014+
if (!curr || !prev)
1015+
return 0;
1016+
1017+
return libnvme_stat_get_io_ticks(curr, prev, p->diffstat);
1018+
}
1019+
1020+
static unsigned int libnvme_stat_get_ticks(libnvme_stat_t curr,
1021+
libnvme_stat_t prev, enum libnvme_stat_group grp, bool diffstat)
1022+
{
1023+
unsigned int delta = 0;
1024+
1025+
if (!diffstat)
1026+
return curr->group[grp].ticks;
1027+
1028+
if (curr->group[grp].ticks > prev->group[grp].ticks)
1029+
delta = curr->group[grp].ticks - prev->group[grp].ticks;
1030+
1031+
return delta;
1032+
}
1033+
1034+
static unsigned int __libnvme_path_get_ticks(libnvme_path_t p,
1035+
enum libnvme_stat_group grp)
1036+
{
1037+
libnvme_stat_t curr, prev;
1038+
1039+
curr = libnvme_path_get_stat(p, p->curr_idx);
1040+
prev = libnvme_path_get_stat(p, !p->curr_idx);
1041+
1042+
if (!curr || !prev)
1043+
return 0;
1044+
1045+
return libnvme_stat_get_ticks(curr, prev, grp, p->diffstat);
1046+
}
1047+
1048+
__public unsigned int libnvme_path_get_read_ticks(libnvme_path_t p)
1049+
{
1050+
return __libnvme_path_get_ticks(p, READ);
1051+
}
1052+
1053+
__public unsigned int libnvme_path_get_write_ticks(libnvme_path_t p)
1054+
{
1055+
return __libnvme_path_get_ticks(p, WRITE);
1056+
}
1057+
1058+
static double libnvme_stat_get_interval(libnvme_stat_t curr,
1059+
libnvme_stat_t prev)
1060+
{
1061+
double delta = 0.0;
1062+
1063+
if (prev->ts_ms && curr->ts_ms > prev->ts_ms)
1064+
delta = curr->ts_ms - prev->ts_ms;
1065+
1066+
return delta;
1067+
}
1068+
1069+
__public double libnvme_path_get_stat_interval(libnvme_path_t p)
1070+
{
1071+
libnvme_stat_t curr, prev;
1072+
1073+
curr = libnvme_path_get_stat(p, p->curr_idx);
1074+
prev = libnvme_path_get_stat(p, !p->curr_idx);
1075+
1076+
if (!curr || !prev)
1077+
return 0;
1078+
1079+
return libnvme_stat_get_interval(curr, prev);
1080+
}
1081+
1082+
static unsigned long libnvme_stat_get_ios(libnvme_stat_t curr,
1083+
libnvme_stat_t prev, enum libnvme_stat_group grp, bool diffstat)
1084+
{
1085+
unsigned long ios = 0;
1086+
1087+
if (!diffstat)
1088+
return curr->group[grp].ios;
1089+
1090+
if (curr->group[grp].ios > prev->group[grp].ios)
1091+
ios = curr->group[grp].ios - prev->group[grp].ios;
1092+
1093+
return ios;
1094+
}
1095+
1096+
static unsigned long __libnvme_path_get_ios(libnvme_path_t p,
1097+
enum libnvme_stat_group grp)
1098+
{
1099+
libnvme_stat_t curr, prev;
1100+
1101+
curr = libnvme_path_get_stat(p, p->curr_idx);
1102+
prev = libnvme_path_get_stat(p, !p->curr_idx);
1103+
1104+
if (!curr || !prev)
1105+
return 0;
1106+
1107+
return libnvme_stat_get_ios(curr, prev, grp, p->diffstat);
1108+
}
1109+
1110+
__public unsigned long libnvme_path_get_read_ios(libnvme_path_t p)
1111+
{
1112+
return __libnvme_path_get_ios(p, READ);
1113+
}
1114+
1115+
__public unsigned long libnvme_path_get_write_ios(libnvme_path_t p)
1116+
{
1117+
return __libnvme_path_get_ios(p, WRITE);
1118+
}
1119+
1120+
static unsigned long long libnvme_stat_get_sectors(libnvme_stat_t curr,
1121+
libnvme_stat_t prev, enum libnvme_stat_group grp, bool diffstat)
1122+
{
1123+
unsigned long long sec = 0;
1124+
1125+
if (!diffstat)
1126+
return curr->group[grp].sectors;
1127+
1128+
if (curr->group[grp].sectors > prev->group[grp].sectors)
1129+
sec = curr->group[grp].sectors - prev->group[grp].sectors;
1130+
1131+
return sec;
1132+
}
1133+
1134+
static unsigned long long __libnvme_path_get_sectors(libnvme_path_t p,
1135+
enum libnvme_stat_group grp)
1136+
{
1137+
libnvme_stat_t curr, prev;
1138+
1139+
curr = libnvme_path_get_stat(p, p->curr_idx);
1140+
prev = libnvme_path_get_stat(p, !p->curr_idx);
1141+
1142+
if (!curr || !prev)
1143+
return 0;
1144+
1145+
return libnvme_stat_get_sectors(curr, prev, grp, p->diffstat);
1146+
}
1147+
1148+
__public unsigned long long libnvme_path_get_read_sectors(libnvme_path_t p)
1149+
{
1150+
return __libnvme_path_get_sectors(p, READ);
1151+
}
1152+
1153+
__public unsigned long long libnvme_path_get_write_sectors(libnvme_path_t p)
1154+
{
1155+
return __libnvme_path_get_sectors(p, WRITE);
1156+
}
1157+
8871158
void nvme_free_path(struct libnvme_path *p)
8881159
{
8891160
if (!p)

0 commit comments

Comments
 (0)