1use bitflags::bitflags;
2
3use crate::{from_iter, ProcResult};
4
5use std::collections::HashMap;
6use std::io::{BufRead, Lines};
7use std::path::PathBuf;
8use std::time::Duration;
9
10#[cfg(feature = "serde1")]
11use serde::{Deserialize, Serialize};
12
13bitflags! {
14 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
15 #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
16 pub struct NFSServerCaps: u32 {
17
18 const NFS_CAP_READDIRPLUS = 1;
19 const NFS_CAP_HARDLINKS = (1 << 1);
20 const NFS_CAP_SYMLINKS = (1 << 2);
21 const NFS_CAP_ACLS = (1 << 3);
22 const NFS_CAP_ATOMIC_OPEN = (1 << 4);
23 const NFS_CAP_LGOPEN = (1 << 5);
24 const NFS_CAP_FILEID = (1 << 6);
25 const NFS_CAP_MODE = (1 << 7);
26 const NFS_CAP_NLINK = (1 << 8);
27 const NFS_CAP_OWNER = (1 << 9);
28 const NFS_CAP_OWNER_GROUP = (1 << 10);
29 const NFS_CAP_ATIME = (1 << 11);
30 const NFS_CAP_CTIME = (1 << 12);
31 const NFS_CAP_MTIME = (1 << 13);
32 const NFS_CAP_POSIX_LOCK = (1 << 14);
33 const NFS_CAP_UIDGID_NOMAP = (1 << 15);
34 const NFS_CAP_STATEID_NFSV41 = (1 << 16);
35 const NFS_CAP_ATOMIC_OPEN_V1 = (1 << 17);
36 const NFS_CAP_SECURITY_LABEL = (1 << 18);
37 const NFS_CAP_SEEK = (1 << 19);
38 const NFS_CAP_ALLOCATE = (1 << 20);
39 const NFS_CAP_DEALLOCATE = (1 << 21);
40 const NFS_CAP_LAYOUTSTATS = (1 << 22);
41 const NFS_CAP_CLONE = (1 << 23);
42 const NFS_CAP_COPY = (1 << 24);
43 const NFS_CAP_OFFLOAD_CANCEL = (1 << 25);
44 }
45}
46
47pub struct MountInfos(pub Vec<MountInfo>);
51
52impl MountInfos {
53 pub fn iter(&self) -> std::slice::Iter<'_, MountInfo> {
55 self.into_iter()
56 }
57}
58
59impl crate::FromBufRead for MountInfos {
60 fn from_buf_read<R: BufRead>(r: R) -> ProcResult<Self> {
61 let lines = r.lines();
62 let mut vec = Vec::new();
63 for line in lines {
64 vec.push(MountInfo::from_line(&line?)?);
65 }
66
67 Ok(MountInfos(vec))
68 }
69}
70
71impl IntoIterator for MountInfos {
72 type IntoIter = std::vec::IntoIter<MountInfo>;
73 type Item = MountInfo;
74
75 fn into_iter(self) -> Self::IntoIter {
76 self.0.into_iter()
77 }
78}
79
80impl<'a> IntoIterator for &'a MountInfos {
81 type IntoIter = std::slice::Iter<'a, MountInfo>;
82 type Item = &'a MountInfo;
83
84 fn into_iter(self) -> Self::IntoIter {
85 self.0.iter()
86 }
87}
88
89#[derive(Debug, Clone)]
97#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
98pub struct MountInfo {
99 pub mnt_id: i32,
101 pub pid: i32,
111 pub majmin: String,
113 pub root: String,
115 pub mount_point: PathBuf,
117 pub mount_options: HashMap<String, Option<String>>,
119 pub opt_fields: Vec<MountOptFields>,
121 pub fs_type: String,
123 pub mount_source: Option<String>,
125 pub super_options: HashMap<String, Option<String>>,
127}
128
129impl MountInfo {
130 pub fn from_line(line: &str) -> ProcResult<MountInfo> {
131 let mut split = line.split_whitespace();
132
133 let mnt_id = expect!(from_iter(&mut split));
134 let pid = expect!(from_iter(&mut split));
135 let majmin: String = expect!(from_iter(&mut split));
136 let root = expect!(from_iter(&mut split));
137 let mount_point = expect!(from_iter(&mut split));
138 let mount_options = {
139 let mut map = HashMap::new();
140 let all_opts = expect!(split.next());
141 for opt in all_opts.split(',') {
142 let mut s = opt.splitn(2, '=');
143 let opt_name = expect!(s.next());
144 map.insert(opt_name.to_owned(), s.next().map(|s| s.to_owned()));
145 }
146 map
147 };
148
149 let mut opt_fields = Vec::new();
150 loop {
151 let f = expect!(split.next());
152 if f == "-" {
153 break;
154 }
155 let mut s = f.split(':');
156 let opt = match expect!(s.next()) {
157 "shared" => {
158 let val = expect!(from_iter(&mut s));
159 MountOptFields::Shared(val)
160 }
161 "master" => {
162 let val = expect!(from_iter(&mut s));
163 MountOptFields::Master(val)
164 }
165 "propagate_from" => {
166 let val = expect!(from_iter(&mut s));
167 MountOptFields::PropagateFrom(val)
168 }
169 "unbindable" => MountOptFields::Unbindable,
170 _ => continue,
171 };
172 opt_fields.push(opt);
173 }
174 let fs_type: String = expect!(from_iter(&mut split));
175 let mount_source = match expect!(split.next()) {
176 "none" => None,
177 x => Some(x.to_owned()),
178 };
179 let super_options = {
180 let mut map = HashMap::new();
181 let all_opts = expect!(split.next());
182 for opt in all_opts.split(',') {
183 let mut s = opt.splitn(2, '=');
184 let opt_name = expect!(s.next());
185 map.insert(opt_name.to_owned(), s.next().map(|s| s.to_owned()));
186 }
187 map
188 };
189
190 Ok(MountInfo {
191 mnt_id,
192 pid,
193 majmin,
194 root,
195 mount_point,
196 mount_options,
197 opt_fields,
198 fs_type,
199 mount_source,
200 super_options,
201 })
202 }
203}
204
205#[derive(Debug, Clone)]
207#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
208pub enum MountOptFields {
209 Shared(u32),
213 Master(u32),
215 PropagateFrom(u32),
217 Unbindable,
219}
220
221#[derive(Debug, Clone)]
223#[cfg_attr(test, derive(PartialEq))]
224#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
225pub struct MountStat {
226 pub device: Option<String>,
228 pub mount_point: PathBuf,
230 pub fs: String,
232 pub statistics: Option<MountNFSStatistics>,
234}
235
236#[derive(Debug, Clone)]
238#[cfg_attr(test, derive(PartialEq))]
239#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
240pub struct MountStats(pub Vec<MountStat>);
241
242impl crate::FromBufRead for MountStats {
243 fn from_buf_read<R: BufRead>(r: R) -> ProcResult<Self> {
245 let mut v = Vec::new();
246 let mut lines = r.lines();
247 while let Some(Ok(line)) = lines.next() {
248 if line.starts_with("device ") {
249 let mut s = line.split_whitespace();
252
253 let device = Some(expect!(s.nth(1)).to_owned());
254 let mount_point = PathBuf::from(expect!(s.nth(2)));
255 let fs = expect!(s.nth(2)).to_owned();
256 let statistics = match s.next() {
257 Some(stats) if stats.starts_with("statvers=") => {
258 Some(MountNFSStatistics::from_lines(&mut lines, &stats[9..])?)
259 }
260 _ => None,
261 };
262
263 v.push(MountStat {
264 device,
265 mount_point,
266 fs,
267 statistics,
268 });
269 }
270 }
271
272 Ok(MountStats(v))
273 }
274}
275
276impl IntoIterator for MountStats {
277 type IntoIter = std::vec::IntoIter<MountStat>;
278 type Item = MountStat;
279
280 fn into_iter(self) -> Self::IntoIter {
281 self.0.into_iter()
282 }
283}
284
285#[derive(Debug, Clone)]
290#[cfg_attr(test, derive(PartialEq))]
291#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
292pub struct MountNFSStatistics {
293 pub version: String,
295 pub opts: Vec<String>,
299 pub age: Duration,
301 pub caps: Vec<String>,
313 pub sec: Vec<String>,
315 pub events: NFSEventCounter,
316 pub bytes: NFSByteCounter,
317 pub per_op_stats: NFSPerOpStats,
321}
322
323impl MountNFSStatistics {
324 fn from_lines<B: BufRead>(r: &mut Lines<B>, statsver: &str) -> ProcResult<MountNFSStatistics> {
326 let mut parsing_per_op = false;
327
328 let mut opts: Option<Vec<String>> = None;
329 let mut age = None;
330 let mut caps = None;
331 let mut sec = None;
332 let mut bytes = None;
333 let mut events = None;
334 let mut per_op = HashMap::new();
335
336 while let Some(Ok(line)) = r.next() {
337 let line = line.trim();
338 if line.trim() == "" {
339 break;
340 }
341 if !parsing_per_op {
342 if let Some(stripped) = line.strip_prefix("opts:") {
343 opts = Some(stripped.trim().split(',').map(|s| s.to_string()).collect());
344 } else if let Some(stripped) = line.strip_prefix("age:") {
345 age = Some(Duration::from_secs(from_str!(u64, stripped.trim())));
346 } else if let Some(stripped) = line.strip_prefix("caps:") {
347 caps = Some(stripped.trim().split(',').map(|s| s.to_string()).collect());
348 } else if let Some(stripped) = line.strip_prefix("sec:") {
349 sec = Some(stripped.trim().split(',').map(|s| s.to_string()).collect());
350 } else if let Some(stripped) = line.strip_prefix("bytes:") {
351 bytes = Some(NFSByteCounter::from_str(stripped.trim())?);
352 } else if let Some(stripped) = line.strip_prefix("events:") {
353 events = Some(NFSEventCounter::from_str(stripped.trim())?);
354 }
355 if line == "per-op statistics" {
356 parsing_per_op = true;
357 }
358 } else {
359 let mut split = line.split(':');
360 let name = expect!(split.next()).to_string();
361 let stats = NFSOperationStat::from_str(expect!(split.next()))?;
362 per_op.insert(name, stats);
363 }
364 }
365
366 Ok(MountNFSStatistics {
367 version: statsver.to_string(),
368 opts: expect!(opts, "Failed to find opts field in nfs stats"),
369 age: expect!(age, "Failed to find age field in nfs stats"),
370 caps: expect!(caps, "Failed to find caps field in nfs stats"),
371 sec: expect!(sec, "Failed to find sec field in nfs stats"),
372 events: expect!(events, "Failed to find events section in nfs stats"),
373 bytes: expect!(bytes, "Failed to find bytes section in nfs stats"),
374 per_op_stats: per_op,
375 })
376 }
377
378 pub fn server_caps(&self) -> ProcResult<Option<NFSServerCaps>> {
380 for data in &self.caps {
381 if let Some(stripped) = data.strip_prefix("caps=0x") {
382 let val = from_str!(u32, stripped, 16);
383 return Ok(NFSServerCaps::from_bits(val));
384 }
385 }
386 Ok(None)
387 }
388}
389
390#[derive(Debug, Copy, Clone)]
396#[cfg_attr(test, derive(PartialEq))]
397#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
398pub struct NFSEventCounter {
399 pub inode_revalidate: u64,
400 pub deny_try_revalidate: u64,
401 pub data_invalidate: u64,
402 pub attr_invalidate: u64,
403 pub vfs_open: u64,
404 pub vfs_lookup: u64,
405 pub vfs_access: u64,
406 pub vfs_update_page: u64,
407 pub vfs_read_page: u64,
408 pub vfs_read_pages: u64,
409 pub vfs_write_page: u64,
410 pub vfs_write_pages: u64,
411 pub vfs_get_dents: u64,
412 pub vfs_set_attr: u64,
413 pub vfs_flush: u64,
414 pub vfs_fs_sync: u64,
415 pub vfs_lock: u64,
416 pub vfs_release: u64,
417 pub congestion_wait: u64,
418 pub set_attr_trunc: u64,
419 pub extend_write: u64,
420 pub silly_rename: u64,
421 pub short_read: u64,
422 pub short_write: u64,
423 pub delay: u64,
424 pub pnfs_read: u64,
425 pub pnfs_write: u64,
426}
427
428impl NFSEventCounter {
429 fn from_str(s: &str) -> ProcResult<NFSEventCounter> {
430 let mut s = s.split_whitespace();
431 Ok(NFSEventCounter {
432 inode_revalidate: from_str!(u64, expect!(s.next())),
433 deny_try_revalidate: from_str!(u64, expect!(s.next())),
434 data_invalidate: from_str!(u64, expect!(s.next())),
435 attr_invalidate: from_str!(u64, expect!(s.next())),
436 vfs_open: from_str!(u64, expect!(s.next())),
437 vfs_lookup: from_str!(u64, expect!(s.next())),
438 vfs_access: from_str!(u64, expect!(s.next())),
439 vfs_update_page: from_str!(u64, expect!(s.next())),
440 vfs_read_page: from_str!(u64, expect!(s.next())),
441 vfs_read_pages: from_str!(u64, expect!(s.next())),
442 vfs_write_page: from_str!(u64, expect!(s.next())),
443 vfs_write_pages: from_str!(u64, expect!(s.next())),
444 vfs_get_dents: from_str!(u64, expect!(s.next())),
445 vfs_set_attr: from_str!(u64, expect!(s.next())),
446 vfs_flush: from_str!(u64, expect!(s.next())),
447 vfs_fs_sync: from_str!(u64, expect!(s.next())),
448 vfs_lock: from_str!(u64, expect!(s.next())),
449 vfs_release: from_str!(u64, expect!(s.next())),
450 congestion_wait: from_str!(u64, expect!(s.next())),
451 set_attr_trunc: from_str!(u64, expect!(s.next())),
452 extend_write: from_str!(u64, expect!(s.next())),
453 silly_rename: from_str!(u64, expect!(s.next())),
454 short_read: from_str!(u64, expect!(s.next())),
455 short_write: from_str!(u64, expect!(s.next())),
456 delay: from_str!(u64, expect!(s.next())),
457 pnfs_read: from_str!(u64, expect!(s.next())),
458 pnfs_write: from_str!(u64, expect!(s.next())),
459 })
460 }
461}
462
463#[derive(Debug, Copy, Clone)]
469#[cfg_attr(test, derive(PartialEq))]
470#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
471pub struct NFSByteCounter {
472 pub normal_read: u64,
473 pub normal_write: u64,
474 pub direct_read: u64,
475 pub direct_write: u64,
476 pub server_read: u64,
477 pub server_write: u64,
478 pub pages_read: u64,
479 pub pages_write: u64,
480}
481
482impl NFSByteCounter {
483 fn from_str(s: &str) -> ProcResult<NFSByteCounter> {
484 let mut s = s.split_whitespace();
485 Ok(NFSByteCounter {
486 normal_read: from_str!(u64, expect!(s.next())),
487 normal_write: from_str!(u64, expect!(s.next())),
488 direct_read: from_str!(u64, expect!(s.next())),
489 direct_write: from_str!(u64, expect!(s.next())),
490 server_read: from_str!(u64, expect!(s.next())),
491 server_write: from_str!(u64, expect!(s.next())),
492 pages_read: from_str!(u64, expect!(s.next())),
493 pages_write: from_str!(u64, expect!(s.next())),
494 })
495 }
496}
497
498#[derive(Debug, Clone)]
528#[cfg_attr(test, derive(PartialEq))]
529#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
530pub struct NFSOperationStat {
531 pub operations: u64,
533 pub transmissions: u64,
535 pub major_timeouts: u64,
537 pub bytes_sent: u64,
539 pub bytes_recv: u64,
541 pub cum_queue_time: Duration,
543 pub cum_resp_time: Duration,
545 pub cum_total_req_time: Duration,
548}
549
550impl NFSOperationStat {
551 fn from_str(s: &str) -> ProcResult<NFSOperationStat> {
552 let mut s = s.split_whitespace();
553
554 let operations = from_str!(u64, expect!(s.next()));
555 let transmissions = from_str!(u64, expect!(s.next()));
556 let major_timeouts = from_str!(u64, expect!(s.next()));
557 let bytes_sent = from_str!(u64, expect!(s.next()));
558 let bytes_recv = from_str!(u64, expect!(s.next()));
559 let cum_queue_time_ms = from_str!(u64, expect!(s.next()));
560 let cum_resp_time_ms = from_str!(u64, expect!(s.next()));
561 let cum_total_req_time_ms = from_str!(u64, expect!(s.next()));
562
563 Ok(NFSOperationStat {
564 operations,
565 transmissions,
566 major_timeouts,
567 bytes_sent,
568 bytes_recv,
569 cum_queue_time: Duration::from_millis(cum_queue_time_ms),
570 cum_resp_time: Duration::from_millis(cum_resp_time_ms),
571 cum_total_req_time: Duration::from_millis(cum_total_req_time_ms),
572 })
573 }
574}
575
576pub type NFSPerOpStats = HashMap<String, NFSOperationStat>;
577
578#[cfg(test)]
579mod tests {
580 use super::*;
581 use crate::FromRead;
582 use std::time::Duration;
583
584 #[test]
585 fn test_mountinfo() {
586 let s = "25 0 8:1 / / rw,relatime shared:1 - ext4 /dev/sda1 rw,errors=remount-ro";
587
588 let stat = MountInfo::from_line(s).unwrap();
589 println!("{:?}", stat);
590 }
591
592 #[test]
593 fn test_proc_mountstats() {
594 let MountStats(simple) = FromRead::from_read(
595 "device /dev/md127 mounted on /boot with fstype ext2
596device /dev/md124 mounted on /home with fstype ext4
597device tmpfs mounted on /run/user/0 with fstype tmpfs
598"
599 .as_bytes(),
600 )
601 .unwrap();
602 let simple_parsed = vec![
603 MountStat {
604 device: Some("/dev/md127".to_string()),
605 mount_point: PathBuf::from("/boot"),
606 fs: "ext2".to_string(),
607 statistics: None,
608 },
609 MountStat {
610 device: Some("/dev/md124".to_string()),
611 mount_point: PathBuf::from("/home"),
612 fs: "ext4".to_string(),
613 statistics: None,
614 },
615 MountStat {
616 device: Some("tmpfs".to_string()),
617 mount_point: PathBuf::from("/run/user/0"),
618 fs: "tmpfs".to_string(),
619 statistics: None,
620 },
621 ];
622 assert_eq!(simple, simple_parsed);
623 let MountStats(mountstats) = FromRead::from_read("device elwe:/space mounted on /srv/elwe/space with fstype nfs4 statvers=1.1
624 opts: rw,vers=4.1,rsize=131072,wsize=131072,namlen=255,acregmin=3,acregmax=60,acdirmin=30,acdirmax=60,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=krb5,clientaddr=10.0.1.77,local_lock=none
625 age: 3542
626 impl_id: name='',domain='',date='0,0'
627 caps: caps=0x3ffdf,wtmult=512,dtsize=32768,bsize=0,namlen=255
628 nfsv4: bm0=0xfdffbfff,bm1=0x40f9be3e,bm2=0x803,acl=0x3,sessions,pnfs=not configured
629 sec: flavor=6,pseudoflavor=390003
630 events: 114 1579 5 3 132 20 3019 1 2 3 4 5 115 1 4 1 2 4 3 4 5 6 7 8 9 0 1
631 bytes: 1 2 3 4 5 6 7 8
632 RPC iostats version: 1.0 p/v: 100003/4 (nfs)
633 xprt: tcp 909 0 1 0 2 294 294 0 294 0 2 0 0
634 per-op statistics
635 NULL: 0 0 0 0 0 0 0 0
636 READ: 1 2 3 4 5 6 7 8
637 WRITE: 0 0 0 0 0 0 0 0
638 COMMIT: 0 0 0 0 0 0 0 0
639 OPEN: 1 1 0 320 420 0 124 124
640 ".as_bytes()).unwrap();
641 let nfs_v4 = &mountstats[0];
642 match &nfs_v4.statistics {
643 Some(stats) => {
644 assert_eq!("1.1".to_string(), stats.version, "mountstats version wrongly parsed.");
645 assert_eq!(Duration::from_secs(3542), stats.age);
646 assert_eq!(1, stats.bytes.normal_read);
647 assert_eq!(114, stats.events.inode_revalidate);
648 assert!(stats.server_caps().unwrap().is_some());
649 }
650 None => {
651 panic!("Failed to retrieve nfs statistics");
652 }
653 }
654 }
655}