Skip to content

Commit c04c14a

Browse files
feat(nexus_stats): add nexus stats grpc
Signed-off-by: Abhilash Shetty <abhilash.shetty@datacore.com>
1 parent 3623158 commit c04c14a

File tree

4 files changed

+257
-44
lines changed

4 files changed

+257
-44
lines changed

io-engine/src/bdev/nexus/nexus_bdev.rs

+10-2
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ use crate::{
6161
subsys::NvmfSubsystem,
6262
};
6363

64-
use crate::core::IoCompletionStatus;
64+
use crate::core::{BlockDeviceIoStats, CoreError, IoCompletionStatus};
6565
use events_api::event::EventAction;
6666
use spdk_rs::{
6767
BdevIo,
@@ -518,6 +518,14 @@ impl<'n> Nexus<'n> {
518518
unsafe { self.bdev().name().to_string() }
519519
}
520520

521+
/// Returns io stats for underlying Bdev.
522+
pub(crate) async fn bdev_stats(
523+
&self,
524+
) -> Result<BlockDeviceIoStats, CoreError> {
525+
let bdev = unsafe { self.bdev() };
526+
bdev.stats_async().await
527+
}
528+
521529
/// TODO
522530
pub fn req_size(&self) -> u64 {
523531
self.req_size
@@ -538,7 +546,7 @@ impl<'n> Nexus<'n> {
538546
unsafe { self.bdev().num_blocks() }
539547
}
540548

541-
/// Returns the required alignment of the Nexus.
549+
/// Returns the alignment of the Nexus.
542550
pub fn alignment(&self) -> u64 {
543551
unsafe { self.bdev().alignment() }
544552
}

io-engine/src/bin/io-engine-client/v1/stats_cli.rs

+114-3
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,28 @@ pub fn subcommands() -> Command {
1717
.help("Storage pool name"),
1818
);
1919

20+
let nexus = Command::new("nexus").about("Get Nexus IO Stats").arg(
21+
Arg::new("name")
22+
.required(false)
23+
.index(1)
24+
.help("Volume target/nexus name"),
25+
);
26+
2027
let reset = Command::new("reset").about("Reset all resource IO Stats");
2128

2229
Command::new("stats")
2330
.subcommand_required(true)
2431
.arg_required_else_help(true)
2532
.about("Resource IOStats")
2633
.subcommand(pool)
34+
.subcommand(nexus)
2735
.subcommand(reset)
2836
}
2937

3038
pub async fn handler(ctx: Context, matches: &ArgMatches) -> crate::Result<()> {
3139
match matches.subcommand().unwrap() {
3240
("pool", args) => pool(ctx, args).await,
41+
("nexus", args) => nexus(ctx, args).await,
3342
("reset", _) => reset(ctx).await,
3443
(cmd, _) => {
3544
Err(Status::not_found(format!("command {cmd} does not exist")))
@@ -38,13 +47,108 @@ pub async fn handler(ctx: Context, matches: &ArgMatches) -> crate::Result<()> {
3847
}
3948
}
4049

41-
async fn pool(mut ctx: Context, _matches: &ArgMatches) -> crate::Result<()> {
50+
async fn pool(mut ctx: Context, matches: &ArgMatches) -> crate::Result<()> {
4251
ctx.v2("Requesting Pool metrics");
52+
let pool_name = matches.get_one::<String>("name");
4353
let response = ctx
4454
.v1
4555
.stats
4656
.get_pool_io_stats(v1rpc::stats::ListStatsOption {
47-
name: None,
57+
name: pool_name.cloned(),
58+
})
59+
.await
60+
.context(GrpcStatus)?;
61+
match ctx.output {
62+
OutputFormat::Json => {
63+
println!(
64+
"{}",
65+
serde_json::to_string_pretty(response.get_ref())
66+
.unwrap()
67+
.to_colored_json_auto()
68+
.unwrap()
69+
);
70+
}
71+
OutputFormat::Default => {
72+
let stats: &Vec<v1rpc::stats::IoStats> = &response.get_ref().stats;
73+
if stats.is_empty() {
74+
if let Some(name) = pool_name {
75+
ctx.v1(&format!(
76+
"No IoStats found for {}, Check if device exist",
77+
name
78+
));
79+
} else {
80+
ctx.v1("No Pool IoStats found");
81+
}
82+
return Ok(());
83+
}
84+
85+
let table = stats
86+
.iter()
87+
.map(|p| {
88+
let read_latency =
89+
ticks_to_time(p.read_latency_ticks, p.tick_rate);
90+
let write_latency =
91+
ticks_to_time(p.write_latency_ticks, p.tick_rate);
92+
let unmap_latency =
93+
ticks_to_time(p.unmap_latency_ticks, p.tick_rate);
94+
let max_read_latency =
95+
ticks_to_time(p.max_read_latency_ticks, p.tick_rate);
96+
let min_read_latency =
97+
ticks_to_time(p.min_read_latency_ticks, p.tick_rate);
98+
let max_write_latency =
99+
ticks_to_time(p.max_write_latency_ticks, p.tick_rate);
100+
let min_write_latency =
101+
ticks_to_time(p.min_write_latency_ticks, p.tick_rate);
102+
vec![
103+
p.name.clone(),
104+
p.num_read_ops.to_string(),
105+
adjust_bytes(p.bytes_read),
106+
p.num_write_ops.to_string(),
107+
adjust_bytes(p.bytes_written),
108+
p.num_unmap_ops.to_string(),
109+
adjust_bytes(p.bytes_unmapped),
110+
read_latency.to_string(),
111+
write_latency.to_string(),
112+
unmap_latency.to_string(),
113+
max_read_latency.to_string(),
114+
min_read_latency.to_string(),
115+
max_write_latency.to_string(),
116+
min_write_latency.to_string(),
117+
]
118+
})
119+
.collect();
120+
ctx.print_list(
121+
vec![
122+
"NAME",
123+
"NUM_RD_OPS",
124+
"TOTAL_RD",
125+
"NUM_WR_OPS",
126+
"TOTAL_WR",
127+
"NUM_UNMAP_OPS",
128+
"TOTAL_UNMAPPED",
129+
"RD_LAT",
130+
"WR_LAT",
131+
"UNMAP_LATENCY",
132+
"MAX_RD_LAT",
133+
"MIN_RD_LAT",
134+
"MAX_WR_LAT",
135+
"MIN_WR_LAT",
136+
],
137+
table,
138+
);
139+
}
140+
};
141+
Ok(())
142+
}
143+
144+
async fn nexus(mut ctx: Context, matches: &ArgMatches) -> crate::Result<()> {
145+
ctx.v2("Requesting Nexus metrics");
146+
let nexus_name = matches.get_one::<String>("name");
147+
let response = ctx
148+
.v1
149+
.stats
150+
.get_nexus_io_stats(v1rpc::stats::ListStatsOption {
151+
name: nexus_name.cloned(),
48152
})
49153
.await
50154
.context(GrpcStatus)?;
@@ -61,7 +165,14 @@ async fn pool(mut ctx: Context, _matches: &ArgMatches) -> crate::Result<()> {
61165
OutputFormat::Default => {
62166
let stats: &Vec<v1rpc::stats::IoStats> = &response.get_ref().stats;
63167
if stats.is_empty() {
64-
ctx.v1("No Pool IoStats found");
168+
if let Some(name) = nexus_name {
169+
ctx.v1(&format!(
170+
"No IoStats found for {}, Check if device exists",
171+
name
172+
));
173+
} else {
174+
ctx.v1("No Nexus IoStats found");
175+
}
65176
return Ok(());
66177
}
67178

0 commit comments

Comments
 (0)