1 // Don't throw clippy warnings for manual string stripping.
2 // The suggested fix with `strip_prefix` removes support for Rust 1.33 and 1.38
3 #![allow(clippy::unknown_clippy_lints)]
4 #![allow(clippy::manual_strip)]
5 // Don't throw rustc lint warnings for the deprecated name `intra_doc_link_resolution_failure`.
6 // The suggested rename to `broken_intra_doc_links` removes support for Rust 1.33 and 1.38.
7 #![allow(renamed_and_removed_lints)]
8 #![deny(intra_doc_link_resolution_failure)]
9 //! This crate provides to an interface into the linux `procfs` filesystem, usually mounted at
10 //! `/proc`.
11 //!
12 //! This is a pseudo-filesystem which is available on most every linux system and provides an
13 //! interface to kernel data structures.
14 //!
15 //!
16 //! # Kernel support
17 //!
18 //! Not all fields/data are available in each kernel. Some fields were added in specific kernel
19 //! releases, and other fields are only present in certain kernel configuration options are
20 //! enabled. These are represented as `Option` fields in this crate.
21 //!
22 //! This crate aims to support all 2.6 kernels (and newer). WSL2 is also supported.
23 //!
24 //! # Documentation
25 //!
26 //! In almost all cases, the documentation is taken from the
27 //! [`proc.5`](http://man7.org/linux/man-pages/man5/proc.5.html) manual page. This means that
28 //! sometimes the style of writing is not very "rusty", or may do things like reference related files
29 //! (instead of referencing related structs). Contributions to improve this are welcome.
30 //!
31 //! # Panicing
32 //!
33 //! While previous versions of the library could panic, this current version aims to be panic-free
34 //! in a many situations as possible. Whenever the procfs crate encounters a bug in its own
35 //! parsing code, it will return an [`InternalError`](enum.ProcError.html#variant.InternalError) error. This should be considered a
36 //! bug and should be [reported](https://github.com/eminence/procfs). If you encounter a panic,
37 //! please report that as well.
38 //!
39 //! # Cargo features
40 //!
41 //! The following cargo features are available:
42 //!
43 //! * `chrono` -- Default. Optional. This feature enables a few methods that return values as `DateTime` objects.
44 //! * `backtrace` -- Optional. This feature lets you get a stack trace whenever an `InternalError` is raised.
45 //!
46 //! # Examples
47 //!
48 //! Examples can be found in the various modules shown below, or in the
49 //! [examples](https://github.com/eminence/procfs/tree/master/examples) folder of the code repository.
50 //!
51
52 use bitflags::bitflags;
53 use lazy_static::lazy_static;
54 use libc::pid_t;
55 use libc::sysconf;
56 use libc::{_SC_CLK_TCK, _SC_PAGESIZE};
57
58 use std::ffi::CStr;
59 use std::fmt;
60 use std::fs::File;
61 use std::io::{self, BufRead, BufReader, Read, Write};
62 use std::mem;
63 use std::os::raw::c_char;
64 use std::path::{Path, PathBuf};
65 use std::str::FromStr;
66 use std::{collections::HashMap, time::Duration};
67
68 #[cfg(feature = "chrono")]
69 use chrono::{DateTime, Local};
70
71 const PROC_CONFIG_GZ: &str = "/proc/config.gz";
72 const BOOT_CONFIG: &str = "/boot/config";
73
74 trait IntoOption<T> {
into_option(t: Self) -> Option<T>75 fn into_option(t: Self) -> Option<T>;
76 }
77
78 impl<T> IntoOption<T> for Option<T> {
into_option(t: Option<T>) -> Option<T>79 fn into_option(t: Option<T>) -> Option<T> {
80 t
81 }
82 }
83
84 impl<T, R> IntoOption<T> for Result<T, R> {
into_option(t: Result<T, R>) -> Option<T>85 fn into_option(t: Result<T, R>) -> Option<T> {
86 t.ok()
87 }
88 }
89
90 pub(crate) trait IntoResult<T, E> {
into(t: Self) -> Result<T, E>91 fn into(t: Self) -> Result<T, E>;
92 }
93
94 macro_rules! build_internal_error {
95 ($err: expr) => {
96 crate::ProcError::InternalError(crate::InternalError {
97 msg: format!("Internal Unwrap Error: {}", $err),
98 file: file!(),
99 line: line!(),
100 #[cfg(feature = "backtrace")]
101 backtrace: backtrace::Backtrace::new(),
102 })
103 };
104 ($err: expr, $msg: expr) => {
105 crate::ProcError::InternalError(crate::InternalError {
106 msg: format!("Internal Unwrap Error: {}: {}", $msg, $err),
107 file: file!(),
108 line: line!(),
109 #[cfg(feature = "backtrace")]
110 backtrace: backtrace::Backtrace::new(),
111 })
112 };
113 }
114
115 // custom NoneError, since std::option::NoneError is nightly-only
116 // See https://github.com/rust-lang/rust/issues/42327
117 struct NoneError;
118
119 impl std::fmt::Display for NoneError {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121 write!(f, "NoneError")
122 }
123 }
124
125 impl<T> IntoResult<T, NoneError> for Option<T> {
into(t: Option<T>) -> Result<T, NoneError>126 fn into(t: Option<T>) -> Result<T, NoneError> {
127 t.ok_or(NoneError)
128 }
129 }
130
131 impl<T, E> IntoResult<T, E> for Result<T, E> {
into(t: Result<T, E>) -> Result<T, E>132 fn into(t: Result<T, E>) -> Result<T, E> {
133 t
134 }
135 }
136
137 #[macro_use]
138 #[allow(unused_macros)]
139 macro_rules! proc_panic {
140 ($e:expr) => {
141 crate::IntoOption::into_option($e).unwrap_or_else(|| {
142 panic!(
143 "Failed to unwrap {}. Please report this as a procfs bug.",
144 stringify!($e)
145 )
146 })
147 };
148 ($e:expr, $msg:expr) => {
149 crate::IntoOption::into_option($e).unwrap_or_else(|| {
150 panic!(
151 "Failed to unwrap {} ({}). Please report this as a procfs bug.",
152 stringify!($e),
153 $msg
154 )
155 })
156 };
157 }
158
159 macro_rules! expect {
160 ($e:expr) => {
161 match crate::IntoResult::into($e) {
162 Ok(v) => v,
163 Err(e) => return Err(build_internal_error!(e)),
164 }
165 };
166 ($e:expr, $msg:expr) => {
167 match crate::IntoResult::into($e) {
168 Ok(v) => v,
169 Err(e) => return Err(build_internal_error!(e, $msg)),
170 }
171 };
172 }
173
174 #[macro_use]
175 macro_rules! from_str {
176 ($t:tt, $e:expr) => {{
177 let e = $e;
178 expect!(
179 $t::from_str_radix(e, 10),
180 format!("Failed to parse {} ({:?}) as a {}", stringify!($e), e, stringify!($t),)
181 )
182 }};
183 ($t:tt, $e:expr, $radix:expr) => {{
184 let e = $e;
185 expect!(
186 $t::from_str_radix(e, $radix),
187 format!("Failed to parse {} ({:?}) as a {}", stringify!($e), e, stringify!($t))
188 )
189 }};
190 ($t:tt, $e:expr, $radix:expr, pid:$pid:expr) => {{
191 let e = $e;
192 expect!(
193 $t::from_str_radix(e, $radix),
194 format!(
195 "Failed to parse {} ({:?}) as a {} (pid {})",
196 stringify!($e),
197 e,
198 stringify!($t),
199 $pid
200 )
201 )
202 }};
203 }
204
205 macro_rules! wrap_io_error {
206 ($path:expr, $expr:expr) => {
207 match $expr {
208 Ok(v) => Ok(v),
209 Err(e) => {
210 let kind = e.kind();
211 Err(::std::io::Error::new(
212 kind,
213 crate::IoErrorWrapper {
214 path: $path.to_owned(),
215 inner: e.into_inner(),
216 },
217 ))
218 }
219 }
220 };
221 }
222
read_file<P: AsRef<Path>>(path: P) -> ProcResult<String>223 pub(crate) fn read_file<P: AsRef<Path>>(path: P) -> ProcResult<String> {
224 let mut f = FileWrapper::open(path)?;
225 let mut buf = String::new();
226 f.read_to_string(&mut buf)?;
227 Ok(buf)
228 }
229
write_file<P: AsRef<Path>, T: AsRef<[u8]>>(path: P, buf: T) -> ProcResult<()>230 pub(crate) fn write_file<P: AsRef<Path>, T: AsRef<[u8]>>(path: P, buf: T) -> ProcResult<()> {
231 let mut f = File::open(path)?;
232 f.write_all(buf.as_ref())?;
233 Ok(())
234 }
235
read_value<P, T, E>(path: P) -> ProcResult<T> where P: AsRef<Path>, T: FromStr<Err = E>, ProcError: From<E>,236 pub(crate) fn read_value<P, T, E>(path: P) -> ProcResult<T>
237 where
238 P: AsRef<Path>,
239 T: FromStr<Err = E>,
240 ProcError: From<E>,
241 {
242 let val = read_file(path)?;
243 Ok(<T as FromStr>::from_str(val.trim())?)
244 //Ok(val.trim().parse()?)
245 }
246
write_value<P: AsRef<Path>, T: fmt::Display>(path: P, value: T) -> ProcResult<()>247 pub(crate) fn write_value<P: AsRef<Path>, T: fmt::Display>(path: P, value: T) -> ProcResult<()> {
248 write_file(path, value.to_string().as_bytes())
249 }
250
from_iter<'a, I, U>(i: I) -> ProcResult<U> where I: IntoIterator<Item = &'a str>, U: FromStr,251 pub(crate) fn from_iter<'a, I, U>(i: I) -> ProcResult<U>
252 where
253 I: IntoIterator<Item = &'a str>,
254 U: FromStr,
255 {
256 let mut iter = i.into_iter();
257 let val = expect!(iter.next());
258 match FromStr::from_str(val) {
259 Ok(u) => Ok(u),
260 Err(..) => Err(build_internal_error!("Failed to convert")),
261 }
262 }
263
264 pub mod process;
265
266 mod meminfo;
267 pub use crate::meminfo::*;
268
269 pub mod net;
270
271 mod cpuinfo;
272 pub use crate::cpuinfo::*;
273
274 mod cgroups;
275 pub use crate::cgroups::*;
276
277 pub mod sys;
278 pub use crate::sys::kernel::Version as KernelVersion;
279
280 mod pressure;
281 pub use crate::pressure::*;
282
283 mod diskstats;
284 pub use diskstats::*;
285
286 mod locks;
287 pub use locks::*;
288
289 pub mod keyring;
290
291 lazy_static! {
292 /// The number of clock ticks per second.
293 ///
294 /// This is calculated from `sysconf(_SC_CLK_TCK)`.
295 static ref TICKS_PER_SECOND: i64 = {
296 ticks_per_second().unwrap()
297 };
298 /// The version of the currently running kernel.
299 ///
300 /// This is a lazily constructed static. You can also get this information via
301 /// [KernelVersion::new()].
302 static ref KERNEL: KernelVersion = {
303 KernelVersion::current().unwrap()
304 };
305 /// Memory page size, in bytes.
306 ///
307 /// This is calculated from `sysconf(_SC_PAGESIZE)`.
308 static ref PAGESIZE: i64 = {
309 page_size().unwrap()
310 };
311 }
312
convert_to_kibibytes(num: u64, unit: &str) -> ProcResult<u64>313 fn convert_to_kibibytes(num: u64, unit: &str) -> ProcResult<u64> {
314 match unit {
315 "B" => Ok(num),
316 "KiB" | "kiB" | "kB" | "KB" => Ok(num * 1024),
317 "MiB" | "miB" | "MB" | "mB" => Ok(num * 1024 * 1024),
318 "GiB" | "giB" | "GB" | "gB" => Ok(num * 1024 * 1024 * 1024),
319 unknown => Err(build_internal_error!(format!("Unknown unit type {}", unknown))),
320 }
321 }
322
323 trait FromStrRadix: Sized {
from_str_radix(t: &str, radix: u32) -> Result<Self, std::num::ParseIntError>324 fn from_str_radix(t: &str, radix: u32) -> Result<Self, std::num::ParseIntError>;
325 }
326
327 impl FromStrRadix for u64 {
from_str_radix(s: &str, radix: u32) -> Result<u64, std::num::ParseIntError>328 fn from_str_radix(s: &str, radix: u32) -> Result<u64, std::num::ParseIntError> {
329 u64::from_str_radix(s, radix)
330 }
331 }
332 impl FromStrRadix for i32 {
from_str_radix(s: &str, radix: u32) -> Result<i32, std::num::ParseIntError>333 fn from_str_radix(s: &str, radix: u32) -> Result<i32, std::num::ParseIntError> {
334 i32::from_str_radix(s, radix)
335 }
336 }
337
split_into_num<T: FromStrRadix>(s: &str, sep: char, radix: u32) -> ProcResult<(T, T)>338 fn split_into_num<T: FromStrRadix>(s: &str, sep: char, radix: u32) -> ProcResult<(T, T)> {
339 let mut s = s.split(sep);
340 let a = expect!(FromStrRadix::from_str_radix(expect!(s.next()), radix));
341 let b = expect!(FromStrRadix::from_str_radix(expect!(s.next()), radix));
342 Ok((a, b))
343 }
344
345 /// This is used to hold both an IO error as well as the path of the file that originated the error
346 #[derive(Debug)]
347 struct IoErrorWrapper {
348 path: PathBuf,
349 inner: Option<Box<dyn std::error::Error + Send + Sync>>,
350 }
351
352 impl std::error::Error for IoErrorWrapper {}
353 impl fmt::Display for IoErrorWrapper {
fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>354 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
355 if let Some(inner) = &self.inner {
356 write!(f, "IO Error({}): {}", self.path.display(), inner)
357 } else {
358 write!(f, "IO Error({})", self.path.display())
359 }
360 }
361 }
362
363 /// A wrapper around a `File` that remembers the name of the path
364 struct FileWrapper {
365 inner: File,
366 path: PathBuf,
367 }
368
369 impl FileWrapper {
open<P: AsRef<Path>>(path: P) -> Result<FileWrapper, io::Error>370 fn open<P: AsRef<Path>>(path: P) -> Result<FileWrapper, io::Error> {
371 let p = path.as_ref();
372 match File::open(&p) {
373 Ok(f) => Ok(FileWrapper {
374 inner: f,
375 path: p.to_owned(),
376 }),
377 Err(e) => {
378 let kind = e.kind();
379 Err(io::Error::new(
380 kind,
381 IoErrorWrapper {
382 path: p.to_owned(),
383 inner: e.into_inner(),
384 },
385 ))
386 }
387 }
388 }
389 }
390
391 impl Read for FileWrapper {
read(&mut self, buf: &mut [u8]) -> io::Result<usize>392 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
393 wrap_io_error!(self.path, self.inner.read(buf))
394 }
read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize>395 fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
396 wrap_io_error!(self.path, self.inner.read_to_end(buf))
397 }
read_to_string(&mut self, buf: &mut String) -> io::Result<usize>398 fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
399 wrap_io_error!(self.path, self.inner.read_to_string(buf))
400 }
read_exact(&mut self, buf: &mut [u8]) -> io::Result<()>401 fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
402 wrap_io_error!(self.path, self.inner.read_exact(buf))
403 }
404 }
405
406 /// The main error type for the procfs crate.
407 ///
408 /// For more info, see the [ProcError] type.
409 pub type ProcResult<T> = Result<T, ProcError>;
410
411 /// The various error conditions in the procfs crate.
412 ///
413 /// Most of the variants have an `Option<PathBuf>` component. If the error root cause was related
414 /// to some operation on a file, the path of this file will be stored in this component.
415 #[derive(Debug)]
416 pub enum ProcError {
417 /// A standard permission denied error.
418 ///
419 /// This will be a common error, since some files in the procfs filesystem are only readable by
420 /// the root user.
421 PermissionDenied(Option<PathBuf>),
422 /// This might mean that the process no longer exists, or that your kernel doesn't support the
423 /// feature you are trying to use.
424 NotFound(Option<PathBuf>),
425 /// This might mean that a procfs file has incomplete contents.
426 ///
427 /// If you encounter this error, consider retrying the operation.
428 Incomplete(Option<PathBuf>),
429 /// Any other IO error (rare).
430 Io(std::io::Error, Option<PathBuf>),
431 /// Any other non-IO error (very rare).
432 Other(String),
433 /// This error indicates that some unexpected error occurred. This is a bug. The inner
434 /// [InternalError] struct will contain some more info.
435 ///
436 /// If you ever encounter this error, consider it a bug in the procfs crate and please report
437 /// it on github.
438 InternalError(InternalError),
439 }
440
441 /// An internal error in the procfs crate
442 ///
443 /// If you encounter this error, consider it a bug and please report it on
444 /// [github](https://github.com/eminence/procfs).
445 ///
446 /// If you compile with the optional `backtrace` feature (disabled by default),
447 /// you can gain access to a stack trace of where the error happened.
448 pub struct InternalError {
449 pub msg: String,
450 pub file: &'static str,
451 pub line: u32,
452 #[cfg(feature = "backtrace")]
453 pub backtrace: backtrace::Backtrace,
454 }
455
456 impl std::fmt::Debug for InternalError {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result457 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
458 write!(
459 f,
460 "bug at {}:{} (please report this procfs bug)\n{}",
461 self.file, self.line, self.msg
462 )
463 }
464 }
465
466 impl std::fmt::Display for InternalError {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result467 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
468 write!(
469 f,
470 "bug at {}:{} (please report this procfs bug)\n{}",
471 self.file, self.line, self.msg
472 )
473 }
474 }
475
476 impl From<std::io::Error> for ProcError {
from(io: std::io::Error) -> Self477 fn from(io: std::io::Error) -> Self {
478 use std::io::ErrorKind;
479 let kind = io.kind();
480 let path: Option<PathBuf> = io.get_ref().and_then(|inner| {
481 if let Some(ref inner) = inner.downcast_ref::<IoErrorWrapper>() {
482 Some(inner.path.clone())
483 } else {
484 None
485 }
486 });
487 match kind {
488 ErrorKind::PermissionDenied => ProcError::PermissionDenied(path),
489 ErrorKind::NotFound => ProcError::NotFound(path),
490 _other => ProcError::Io(io, path),
491 }
492 }
493 }
494
495 impl From<&'static str> for ProcError {
from(val: &'static str) -> Self496 fn from(val: &'static str) -> Self {
497 ProcError::Other(val.to_owned())
498 }
499 }
500
501 impl From<std::num::ParseIntError> for ProcError {
from(val: std::num::ParseIntError) -> Self502 fn from(val: std::num::ParseIntError) -> Self {
503 ProcError::Other(format!("ParseIntError: {}", val))
504 }
505 }
506
507 impl From<std::string::ParseError> for ProcError {
from(e: std::string::ParseError) -> Self508 fn from(e: std::string::ParseError) -> Self {
509 match e {}
510 }
511 }
512
513 impl std::fmt::Display for ProcError {
fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error>514 fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
515 match self {
516 // Variants with paths:
517 ProcError::PermissionDenied(Some(p)) => write!(f, "Permission Denied: {}", p.display()),
518 ProcError::NotFound(Some(p)) => write!(f, "File not found: {}", p.display()),
519 ProcError::Incomplete(Some(p)) => write!(f, "Data incomplete: {}", p.display()),
520 ProcError::Io(inner, Some(p)) => {
521 write!(f, "Unexpected IO error({}): {}", p.display(), inner)
522 }
523 // Variants without paths:
524 ProcError::PermissionDenied(None) => write!(f, "Permission Denied"),
525 ProcError::NotFound(None) => write!(f, "File not found"),
526 ProcError::Incomplete(None) => write!(f, "Data incomplete"),
527 ProcError::Io(inner, None) => write!(f, "Unexpected IO error: {}", inner),
528
529 ProcError::Other(s) => write!(f, "Unknown error {}", s),
530 ProcError::InternalError(e) => write!(f, "Internal error: {}", e),
531 }
532 }
533 }
534
535 impl std::error::Error for ProcError {}
536
537 /// Load average figures.
538 ///
539 /// Load averages are calculated as the number of jobs in the run queue (state R) or waiting for
540 /// disk I/O (state D) averaged over 1, 5, and 15 minutes.
541 #[derive(Debug)]
542 pub struct LoadAverage {
543 /// The one-minute load average
544 pub one: f32,
545 /// The five-minute load average
546 pub five: f32,
547 /// The fifteen-minute load average
548 pub fifteen: f32,
549 /// The number of currently runnable kernel scheduling entities (processes, threads).
550 pub cur: u32,
551 /// The number of kernel scheduling entities that currently exist on the system.
552 pub max: u32,
553 /// The fifth field is the PID of the process that was most recently created on the system.
554 pub latest_pid: u32,
555 }
556
557 impl LoadAverage {
558 /// Reads load average info from `/proc/loadavg`
new() -> ProcResult<LoadAverage>559 pub fn new() -> ProcResult<LoadAverage> {
560 let mut f = FileWrapper::open("/proc/loadavg")?;
561 let mut s = String::new();
562 f.read_to_string(&mut s)?;
563 let mut s = s.split_whitespace();
564
565 let one = expect!(f32::from_str(expect!(s.next())));
566 let five = expect!(f32::from_str(expect!(s.next())));
567 let fifteen = expect!(f32::from_str(expect!(s.next())));
568 let curmax = expect!(s.next());
569 let latest_pid = expect!(u32::from_str(expect!(s.next())));
570
571 let mut s = curmax.split('/');
572 let cur = expect!(u32::from_str(expect!(s.next())));
573 let max = expect!(u32::from_str(expect!(s.next())));
574
575 Ok(LoadAverage {
576 one,
577 five,
578 fifteen,
579 cur,
580 max,
581 latest_pid,
582 })
583 }
584 }
585
586 /// Return the number of ticks per second.
587 ///
588 /// This isn't part of the proc file system, but it's a useful thing to have, since several fields
589 /// count in ticks. This is calculated from `sysconf(_SC_CLK_TCK)`.
ticks_per_second() -> std::io::Result<i64>590 pub fn ticks_per_second() -> std::io::Result<i64> {
591 if cfg!(unix) {
592 match unsafe { sysconf(_SC_CLK_TCK) } {
593 -1 => Err(std::io::Error::last_os_error()),
594 x => Ok(x.into()),
595 }
596 } else {
597 panic!("Not supported on non-unix platforms")
598 }
599 }
600
601 /// The boot time of the system, as a `DateTime` object.
602 ///
603 /// This is calculated from `/proc/stat`.
604 ///
605 /// This function requires the "chrono" features to be enabled (which it is by default).
606 #[cfg(feature = "chrono")]
boot_time() -> ProcResult<DateTime<Local>>607 pub fn boot_time() -> ProcResult<DateTime<Local>> {
608 use chrono::TimeZone;
609 let secs = boot_time_secs()?;
610
611 Ok(chrono::Local.timestamp(secs as i64, 0))
612 }
613
614 /// The boottime of the system, in seconds since the epoch
615 ///
616 /// This is calculated from `/proc/stat`.
617 ///
618 #[cfg_attr(
619 not(feature = "chrono"),
620 doc = "If you compile with the optional `chrono` feature, you can use the `boot_time()` method to get the boot time as a `DateTime` object."
621 )]
622 #[cfg_attr(
623 feature = "chrono",
624 doc = "See also [boot_time()] to get the boot time as a `DateTime`"
625 )]
boot_time_secs() -> ProcResult<u64>626 pub fn boot_time_secs() -> ProcResult<u64> {
627 BOOT_TIME.with(|x| {
628 let mut btime = x.borrow_mut();
629 if let Some(btime) = *btime {
630 Ok(btime)
631 } else {
632 let stat = KernelStats::new()?;
633 *btime = Some(stat.btime);
634 Ok(stat.btime)
635 }
636 })
637 }
638
639 thread_local! {
640 static BOOT_TIME : std::cell::RefCell<Option<u64>> = std::cell::RefCell::new(None);
641 }
642
643 /// Memory page size, in bytes.
644 ///
645 /// This is calculated from `sysconf(_SC_PAGESIZE)`.
page_size() -> std::io::Result<i64>646 pub fn page_size() -> std::io::Result<i64> {
647 if cfg!(unix) {
648 match unsafe { sysconf(_SC_PAGESIZE) } {
649 -1 => Err(std::io::Error::last_os_error()),
650 x => Ok(x.into()),
651 }
652 } else {
653 panic!("Not supported on non-unix platforms")
654 }
655 }
656
657 /// Possible values for a kernel config option
658 #[derive(Debug, PartialEq)]
659 pub enum ConfigSetting {
660 Yes,
661 Module,
662 Value(String),
663 }
664 /// Returns a configuration options used to build the currently running kernel
665 ///
666 /// If CONFIG_KCONFIG_PROC is available, the config is read from `/proc/config.gz`.
667 /// Else look in `/boot/config-$(uname -r)` or `/boot/config` (in that order).
kernel_config() -> ProcResult<HashMap<String, ConfigSetting>>668 pub fn kernel_config() -> ProcResult<HashMap<String, ConfigSetting>> {
669 use flate2::read::GzDecoder;
670
671 let reader: Box<dyn BufRead> = if Path::new(PROC_CONFIG_GZ).exists() {
672 let file = FileWrapper::open(PROC_CONFIG_GZ)?;
673 let decoder = GzDecoder::new(file);
674 Box::new(BufReader::new(decoder))
675 } else {
676 let mut kernel: libc::utsname = unsafe { mem::zeroed() };
677
678 if unsafe { libc::uname(&mut kernel) != 0 } {
679 return Err(ProcError::Other("Failed to call uname()".to_string()));
680 }
681
682 let filename = format!(
683 "{}-{}",
684 BOOT_CONFIG,
685 unsafe { CStr::from_ptr(kernel.release.as_ptr() as *const c_char) }.to_string_lossy()
686 );
687
688 if Path::new(&filename).exists() {
689 let file = FileWrapper::open(filename)?;
690 Box::new(BufReader::new(file))
691 } else {
692 let file = FileWrapper::open(BOOT_CONFIG)?;
693 Box::new(BufReader::new(file))
694 }
695 };
696
697 let mut map = HashMap::new();
698
699 for line in reader.lines() {
700 let line = line?;
701 if line.starts_with('#') {
702 continue;
703 }
704 if line.contains('=') {
705 let mut s = line.splitn(2, '=');
706 let name = expect!(s.next()).to_owned();
707 let value = match expect!(s.next()) {
708 "y" => ConfigSetting::Yes,
709 "m" => ConfigSetting::Module,
710 s => ConfigSetting::Value(s.to_owned()),
711 };
712 map.insert(name, value);
713 }
714 }
715
716 Ok(map)
717 }
718
719 /// The amount of time, measured in ticks, the CPU has been in specific states
720 ///
721 /// These fields are measured in ticks because the underlying data from the kernel is measured in ticks.
722 /// The number of ticks per second can be returned by [`ticks_per_second()`](crate::ticks_per_second)
723 /// and is generally 100 on most systems.
724
725 /// To convert this value to seconds, you can divide by the tps. There are also convenience methods
726 /// that you can use too.
727 #[derive(Debug)]
728 pub struct CpuTime {
729 /// Ticks spent in user mode
730 pub user: u64,
731 /// Ticks spent in user mode with low priority (nice)
732 pub nice: u64,
733 /// Ticks spent in system mode
734 pub system: u64,
735 /// Ticks spent in the idle state
736 pub idle: u64,
737 /// Ticks waiting for I/O to complete
738 ///
739 /// This value is not reliable, for the following reasons:
740 ///
741 /// 1. The CPU will not wait for I/O to complete; iowait is the time that a
742 /// task is waiting for I/O to complete. When a CPU goes into idle state
743 /// for outstanding task I/O, another task will be scheduled on this CPU.
744 ///
745 /// 2. On a multi-core CPU, this task waiting for I/O to complete is not running
746 /// on any CPU, so the iowait for each CPU is difficult to calculate.
747 ///
748 /// 3. The value in this field may *decrease* in certain conditions.
749 ///
750 /// (Since Linux 2.5.41)
751 pub iowait: Option<u64>,
752 /// Ticks servicing interrupts
753 ///
754 /// (Since Linux 2.6.0)
755 pub irq: Option<u64>,
756 /// Ticks servicing softirqs
757 ///
758 /// (Since Linux 2.6.0)
759 pub softirq: Option<u64>,
760 /// Ticks of stolen time.
761 ///
762 /// Stolen time is the time spent in other operating systems when running in
763 /// a virtualized environment.
764 ///
765 /// (Since Linux 2.6.11)
766 pub steal: Option<u64>,
767 /// Ticks spent running a virtual CPU for guest operating systems under control
768 /// of the linux kernel
769 ///
770 /// (Since Linux 2.6.24)
771 pub guest: Option<u64>,
772 /// Ticks spent running a niced guest
773 ///
774 /// (Since Linux 2.6.33)
775 pub guest_nice: Option<u64>,
776
777 tps: u64,
778 }
779
780 impl CpuTime {
from_str(s: &str) -> ProcResult<CpuTime>781 fn from_str(s: &str) -> ProcResult<CpuTime> {
782 let mut s = s.split_whitespace();
783
784 // Store this field in the struct so we don't have to attempt to unwrap ticks_per_second() when we convert
785 // from ticks into other time units
786 let tps = crate::ticks_per_second()? as u64;
787
788 s.next();
789 let user = from_str!(u64, expect!(s.next()));
790 let nice = from_str!(u64, expect!(s.next()));
791 let system = from_str!(u64, expect!(s.next()));
792 let idle = from_str!(u64, expect!(s.next()));
793
794 let iowait = s.next().map(|s| Ok(from_str!(u64, s))).transpose()?;
795 let irq = s.next().map(|s| Ok(from_str!(u64, s))).transpose()?;
796 let softirq = s.next().map(|s| Ok(from_str!(u64, s))).transpose()?;
797 let steal = s.next().map(|s| Ok(from_str!(u64, s))).transpose()?;
798 let guest = s.next().map(|s| Ok(from_str!(u64, s))).transpose()?;
799 let guest_nice = s.next().map(|s| Ok(from_str!(u64, s))).transpose()?;
800
801 Ok(CpuTime {
802 user,
803 nice,
804 system,
805 idle,
806 iowait,
807 irq,
808 softirq,
809 steal,
810 guest,
811 guest_nice,
812 tps,
813 })
814 }
815
816 /// Milliseconds spent in user mode
user_ms(&self) -> u64817 pub fn user_ms(&self) -> u64 {
818 let ms_per_tick = 1000 / self.tps;
819 self.user * ms_per_tick
820 }
821
822 /// Time spent in user mode
user_duration(&self) -> Duration823 pub fn user_duration(&self) -> Duration {
824 Duration::from_millis(self.user_ms())
825 }
826
827 /// Milliseconds spent in user mode with low priority (nice)
nice_ms(&self) -> u64828 pub fn nice_ms(&self) -> u64 {
829 let ms_per_tick = 1000 / self.tps;
830 self.nice * ms_per_tick
831 }
832
833 /// Time spent in user mode with low priority (nice)
nice_duration(&self) -> Duration834 pub fn nice_duration(&self) -> Duration {
835 Duration::from_millis(self.nice_ms())
836 }
837
838 /// Milliseconds spent in system mode
system_ms(&self) -> u64839 pub fn system_ms(&self) -> u64 {
840 let ms_per_tick = 1000 / self.tps;
841 self.system * ms_per_tick
842 }
843
844 /// Time spent in system mode
system_duration(&self) -> Duration845 pub fn system_duration(&self) -> Duration {
846 Duration::from_millis(self.system_ms())
847 }
848
849 /// Milliseconds spent in the idle state
idle_ms(&self) -> u64850 pub fn idle_ms(&self) -> u64 {
851 let ms_per_tick = 1000 / self.tps;
852 self.idle * ms_per_tick
853 }
854
855 /// Time spent in the idle state
idle_duration(&self) -> Duration856 pub fn idle_duration(&self) -> Duration {
857 Duration::from_millis(self.idle_ms())
858 }
859
860 /// Milliseconds spent waiting for I/O to complete
iowait_ms(&self) -> Option<u64>861 pub fn iowait_ms(&self) -> Option<u64> {
862 let ms_per_tick = 1000 / self.tps;
863 self.iowait.map(|io| io * ms_per_tick)
864 }
865
866 /// Time spent waiting for I/O to complete
iowait_duration(&self) -> Option<Duration>867 pub fn iowait_duration(&self) -> Option<Duration> {
868 self.iowait_ms().map(|io| Duration::from_millis(io))
869 }
870
871 /// Milliseconds spent servicing interrupts
irq_ms(&self) -> Option<u64>872 pub fn irq_ms(&self) -> Option<u64> {
873 let ms_per_tick = 1000 / self.tps;
874 self.irq.map(|ms| ms * ms_per_tick)
875 }
876
877 /// Time spent servicing interrupts
irq_duration(&self) -> Option<Duration>878 pub fn irq_duration(&self) -> Option<Duration> {
879 self.irq_ms().map(|ms| Duration::from_millis(ms))
880 }
881
882 /// Milliseconds spent servicing softirqs
softirq_ms(&self) -> Option<u64>883 pub fn softirq_ms(&self) -> Option<u64> {
884 let ms_per_tick = 1000 / self.tps;
885 self.softirq.map(|ms| ms * ms_per_tick)
886 }
887
888 /// Time spent servicing softirqs
softirq_duration(&self) -> Option<Duration>889 pub fn softirq_duration(&self) -> Option<Duration> {
890 self.softirq_ms().map(|ms| Duration::from_millis(ms))
891 }
892
893 /// Milliseconds of stolen time
steal_ms(&self) -> Option<u64>894 pub fn steal_ms(&self) -> Option<u64> {
895 let ms_per_tick = 1000 / self.tps;
896 self.steal.map(|ms| ms * ms_per_tick)
897 }
898
899 /// Amount of stolen time
steal_duration(&self) -> Option<Duration>900 pub fn steal_duration(&self) -> Option<Duration> {
901 self.steal_ms().map(|ms| Duration::from_millis(ms))
902 }
903
904 /// Milliseconds spent running a virtual CPU for guest operating systems under control of the linux kernel
guest_ms(&self) -> Option<u64>905 pub fn guest_ms(&self) -> Option<u64> {
906 let ms_per_tick = 1000 / self.tps;
907 self.guest.map(|ms| ms * ms_per_tick)
908 }
909
910 /// Time spent running a virtual CPU for guest operating systems under control of the linux kernel
guest_duration(&self) -> Option<Duration>911 pub fn guest_duration(&self) -> Option<Duration> {
912 self.guest_ms().map(|ms| Duration::from_millis(ms))
913 }
914
915 /// Milliseconds spent running a niced guest
guest_nice_ms(&self) -> Option<u64>916 pub fn guest_nice_ms(&self) -> Option<u64> {
917 let ms_per_tick = 1000 / self.tps;
918 self.guest_nice.map(|ms| ms * ms_per_tick)
919 }
920
921 /// Time spent running a niced guest
guest_nice_duration(&self) -> Option<Duration>922 pub fn guest_nice_duration(&self) -> Option<Duration> {
923 self.guest_nice_ms().map(|ms| Duration::from_millis(ms))
924 }
925 }
926
927 /// Kernel/system statistics, from `/proc/stat`
928 #[derive(Debug)]
929 pub struct KernelStats {
930 /// The amount of time the system spent in various states
931 pub total: CpuTime,
932 /// The amount of time that specific CPUs spent in various states
933 pub cpu_time: Vec<CpuTime>,
934
935 /// The number of context switches that the system underwent
936 pub ctxt: u64,
937
938 /// Boot time, in number of seconds since the Epoch
939 pub btime: u64,
940
941 /// Number of forks since boot
942 pub processes: u64,
943
944 /// Number of processes in runnable state
945 ///
946 /// (Since Linux 2.5.45)
947 pub procs_running: Option<u32>,
948
949 /// Number of processes blocked waiting for I/O
950 ///
951 /// (Since Linux 2.5.45)
952 pub procs_blocked: Option<u32>,
953 }
954
955 impl KernelStats {
new() -> ProcResult<KernelStats>956 pub fn new() -> ProcResult<KernelStats> {
957 KernelStats::from_reader(FileWrapper::open("/proc/stat")?)
958 }
959
from_reader<R: io::Read>(r: R) -> ProcResult<KernelStats>960 fn from_reader<R: io::Read>(r: R) -> ProcResult<KernelStats> {
961 let bufread = BufReader::new(r);
962 let lines = bufread.lines();
963
964 let mut total_cpu = None;
965 let mut cpus = Vec::new();
966 let mut ctxt = None;
967 let mut btime = None;
968 let mut processes = None;
969 let mut procs_running = None;
970 let mut procs_blocked = None;
971
972 for line in lines {
973 let line = line?;
974 if line.starts_with("cpu ") {
975 total_cpu = Some(CpuTime::from_str(&line)?);
976 } else if line.starts_with("cpu") {
977 cpus.push(CpuTime::from_str(&line)?);
978 } else if line.starts_with("ctxt ") {
979 ctxt = Some(from_str!(u64, &line[5..]));
980 } else if line.starts_with("btime ") {
981 btime = Some(from_str!(u64, &line[6..]));
982 } else if line.starts_with("processes ") {
983 processes = Some(from_str!(u64, &line[10..]));
984 } else if line.starts_with("procs_running ") {
985 procs_running = Some(from_str!(u32, &line[14..]));
986 } else if line.starts_with("procs_blocked ") {
987 procs_blocked = Some(from_str!(u32, &line[14..]));
988 }
989 }
990
991 Ok(KernelStats {
992 total: expect!(total_cpu),
993 cpu_time: cpus,
994 ctxt: expect!(ctxt),
995 btime: expect!(btime),
996 processes: expect!(processes),
997 procs_running,
998 procs_blocked,
999 })
1000 }
1001 }
1002
1003 /// Get various virtual memory statistics
1004 ///
1005 /// Since the exact set of statistics will vary from kernel to kernel,
1006 /// and because most of them are not well documented, this function
1007 /// returns a HashMap instead of a struct. Consult the kernel source
1008 /// code for more details of this data.
1009 ///
1010 /// This data is taken from the `/proc/vmstat` file.
1011 ///
1012 /// (since Linux 2.6.0)
vmstat() -> ProcResult<HashMap<String, i64>>1013 pub fn vmstat() -> ProcResult<HashMap<String, i64>> {
1014 let file = FileWrapper::open("/proc/vmstat")?;
1015 let reader = BufReader::new(file);
1016 let mut map = HashMap::new();
1017 for line in reader.lines() {
1018 let line = line?;
1019 let mut split = line.split_whitespace();
1020 let name = expect!(split.next());
1021 let val = from_str!(i64, expect!(split.next()));
1022 map.insert(name.to_owned(), val);
1023 }
1024
1025 Ok(map)
1026 }
1027
1028 /// Details about a loaded kernel module
1029 ///
1030 /// For an example, see the [lsmod.rs](https://github.com/eminence/procfs/tree/master/examples)
1031 /// example in the source repo.
1032 #[derive(Debug)]
1033 pub struct KernelModule {
1034 /// The name of the module
1035 pub name: String,
1036
1037 /// The size of the module
1038 pub size: u32,
1039
1040 /// The number of references in the kernel to this module. This can be -1 if the module is unloading
1041 pub refcount: i32,
1042
1043 /// A list of modules that depend on this module.
1044 pub used_by: Vec<String>,
1045
1046 /// The module state
1047 ///
1048 /// This will probably always be "Live", but it could also be either "Unloading" or "Loading"
1049 pub state: String,
1050 }
1051
1052 /// Get a list of loaded kernel modules
1053 ///
1054 /// This corresponds to the data in `/proc/modules`.
modules() -> ProcResult<HashMap<String, KernelModule>>1055 pub fn modules() -> ProcResult<HashMap<String, KernelModule>> {
1056 // kernel reference: kernel/module.c m_show()
1057
1058 let mut map = HashMap::new();
1059 let file = FileWrapper::open("/proc/modules")?;
1060 let reader = BufReader::new(file);
1061 for line in reader.lines() {
1062 let line: String = line?;
1063 let mut s = line.split_whitespace();
1064 let name = expect!(s.next());
1065 let size = from_str!(u32, expect!(s.next()));
1066 let refcount = from_str!(i32, expect!(s.next()));
1067 let used_by: &str = expect!(s.next());
1068 let state = expect!(s.next());
1069
1070 map.insert(
1071 name.to_string(),
1072 KernelModule {
1073 name: name.to_string(),
1074 size,
1075 refcount,
1076 used_by: if used_by == "-" {
1077 Vec::new()
1078 } else {
1079 used_by
1080 .split(',')
1081 .filter(|s| !s.is_empty())
1082 .map(|s| s.to_string())
1083 .collect()
1084 },
1085 state: state.to_string(),
1086 },
1087 );
1088 }
1089
1090 Ok(map)
1091 }
1092
1093 #[cfg(test)]
1094 mod tests {
1095 use super::*;
1096
1097 #[test]
test_statics()1098 fn test_statics() {
1099 println!("{:?}", *TICKS_PER_SECOND);
1100 println!("{:?}", *KERNEL);
1101 println!("{:?}", *PAGESIZE);
1102 }
1103
1104 #[test]
test_kernel_from_str()1105 fn test_kernel_from_str() {
1106 let k = KernelVersion::from_str("1.2.3").unwrap();
1107 assert_eq!(k.major, 1);
1108 assert_eq!(k.minor, 2);
1109 assert_eq!(k.patch, 3);
1110
1111 let k = KernelVersion::from_str("4.9.16-gentoo").unwrap();
1112 assert_eq!(k.major, 4);
1113 assert_eq!(k.minor, 9);
1114 assert_eq!(k.patch, 16);
1115 }
1116
1117 #[test]
test_kernel_cmp()1118 fn test_kernel_cmp() {
1119 let a = KernelVersion::from_str("1.2.3").unwrap();
1120 let b = KernelVersion::from_str("1.2.3").unwrap();
1121 let c = KernelVersion::from_str("1.2.4").unwrap();
1122 let d = KernelVersion::from_str("1.5.4").unwrap();
1123 let e = KernelVersion::from_str("2.5.4").unwrap();
1124
1125 assert_eq!(a, b);
1126 assert!(a < c);
1127 assert!(a < d);
1128 assert!(a < e);
1129 assert!(e > d);
1130 assert!(e > c);
1131 assert!(e > b);
1132 }
1133
1134 #[test]
test_loadavg()1135 fn test_loadavg() {
1136 let load = LoadAverage::new().unwrap();
1137 println!("{:?}", load);
1138 }
1139
1140 #[test]
test_from_str() -> ProcResult<()>1141 fn test_from_str() -> ProcResult<()> {
1142 assert_eq!(from_str!(u8, "12"), 12);
1143 assert_eq!(from_str!(u8, "A", 16), 10);
1144 Ok(())
1145 }
1146
1147 #[test]
test_from_str_fail()1148 fn test_from_str_fail() {
1149 fn inner() -> ProcResult<()> {
1150 let s = "four";
1151 from_str!(u8, s);
1152 unreachable!()
1153 }
1154
1155 assert!(inner().is_err())
1156 }
1157
1158 #[test]
test_kernel_config()1159 fn test_kernel_config() {
1160 // TRAVIS
1161 // we don't have access to the kernel_config on travis, so skip that test there
1162 match std::env::var("TRAVIS") {
1163 Ok(ref s) if s == "true" => return,
1164 _ => {}
1165 }
1166 if !Path::new(PROC_CONFIG_GZ).exists() && !Path::new(BOOT_CONFIG).exists() {
1167 return;
1168 }
1169
1170 let config = kernel_config().unwrap();
1171 println!("{:#?}", config);
1172 }
1173
1174 #[test]
test_file_io_errors()1175 fn test_file_io_errors() {
1176 fn inner<P: AsRef<Path>>(p: P) -> Result<(), ProcError> {
1177 let mut file = FileWrapper::open(p)?;
1178
1179 let mut buf = [0; 128];
1180 file.read_exact(&mut buf[0..128])?;
1181
1182 Ok(())
1183 }
1184
1185 let err = inner("/this_should_not_exist").unwrap_err();
1186 println!("{}", err);
1187
1188 match err {
1189 ProcError::NotFound(Some(p)) => {
1190 assert_eq!(p, Path::new("/this_should_not_exist"));
1191 }
1192 x => panic!("Unexpected return value: {:?}", x),
1193 }
1194
1195 match inner("/proc/loadavg") {
1196 Err(ProcError::Io(_, Some(p))) => {
1197 assert_eq!(p, Path::new("/proc/loadavg"));
1198 }
1199 x => panic!("Unexpected return value: {:?}", x),
1200 }
1201 }
1202
1203 #[test]
test_nopanic()1204 fn test_nopanic() {
1205 fn _inner() -> ProcResult<bool> {
1206 let x: Option<bool> = None;
1207 let y: bool = expect!(x);
1208 Ok(y)
1209 }
1210
1211 let r = _inner();
1212 println!("{:?}", r);
1213 assert!(r.is_err());
1214
1215 fn _inner2() -> ProcResult<bool> {
1216 let _f: std::fs::File = expect!(std::fs::File::open("/doesnotexist"));
1217 Ok(true)
1218 }
1219
1220 let r = _inner2();
1221 println!("{:?}", r);
1222 assert!(r.is_err());
1223 }
1224
1225 #[cfg(feature = "backtrace")]
1226 #[test]
test_backtrace()1227 fn test_backtrace() {
1228 fn _inner() -> ProcResult<bool> {
1229 let _f: std::fs::File = expect!(std::fs::File::open("/doesnotexist"));
1230 Ok(true)
1231 }
1232
1233 let r = _inner();
1234 println!("{:?}", r);
1235 }
1236
1237 #[test]
test_kernel_stat()1238 fn test_kernel_stat() {
1239 let stat = KernelStats::new().unwrap();
1240 println!("{:#?}", stat);
1241
1242 // the boottime from KernelStats should match the boottime from /proc/uptime
1243 let boottime = boot_time_secs().unwrap();
1244
1245 let diff = (boottime as i32 - stat.btime as i32).abs();
1246 assert!(diff <= 1);
1247
1248 let cpuinfo = CpuInfo::new().unwrap();
1249 assert_eq!(cpuinfo.num_cores(), stat.cpu_time.len());
1250
1251 // the sum of each individual CPU should be equal to the total cpu entry
1252 // note: on big machines with 128 cores, it seems that the differences can be rather high,
1253 // especially when heavily loaded. So this test tolerates a 6000-tick discrepancy
1254 // (60 seconds in a 100-tick-per-second kernel)
1255
1256 let user: u64 = stat.cpu_time.iter().map(|i| i.user).sum();
1257 let nice: u64 = stat.cpu_time.iter().map(|i| i.nice).sum();
1258 let system: u64 = stat.cpu_time.iter().map(|i| i.system).sum();
1259 assert!(
1260 (stat.total.user as i64 - user as i64).abs() < 6000,
1261 "sum:{} total:{} diff:{}",
1262 stat.total.user,
1263 user,
1264 stat.total.user - user
1265 );
1266 assert!(
1267 (stat.total.nice as i64 - nice as i64).abs() < 6000,
1268 "sum:{} total:{} diff:{}",
1269 stat.total.nice,
1270 nice,
1271 stat.total.nice - nice
1272 );
1273 assert!(
1274 (stat.total.system as i64 - system as i64).abs() < 6000,
1275 "sum:{} total:{} diff:{}",
1276 stat.total.system,
1277 system,
1278 stat.total.system - system
1279 );
1280
1281 let diff = stat.total.idle as i64 - (stat.cpu_time.iter().map(|i| i.idle).sum::<u64>() as i64).abs();
1282 assert!(diff < 1000, "idle time difference too high: {}", diff);
1283 }
1284
1285 #[test]
test_vmstat()1286 fn test_vmstat() {
1287 let stat = vmstat().unwrap();
1288 println!("{:?}", stat);
1289 }
1290
1291 #[test]
test_modules()1292 fn test_modules() {
1293 let mods = modules().unwrap();
1294 for module in mods.values() {
1295 println!("{:?}", module);
1296 }
1297 }
1298
1299 #[test]
tests_tps()1300 fn tests_tps() {
1301 let tps = ticks_per_second().unwrap();
1302 println!("{} ticks per second", tps);
1303 }
1304 }
1305