├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.rst ├── appveyor.yml ├── examples ├── json.rs └── simple.rs ├── src ├── debug.rs ├── error.rs ├── lib.rs ├── meter.rs ├── report.rs ├── scan.rs └── serialize.rs └── vagga.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | /.vagga 4 | /tmp 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | matrix: 3 | include: 4 | - os: linux 5 | rust: stable 6 | - os: linux 7 | rust: beta 8 | - os: linux 9 | rust: nightly 10 | - os: osx 11 | rust: stable 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "self-meter" 3 | description = """ 4 | A tiny library to measure resource usage of the process it's used in. 5 | """ 6 | license = "MIT/Apache-2.0" 7 | readme = "README.rst" 8 | keywords = ["linux", "monitoring", "meter"] 9 | homepage = "https://github.com/tailhook/self-meter" 10 | documentation = "https://docs.rs/self-meter" 11 | version = "0.6.0" 12 | authors = ["paul@colomiets.name"] 13 | 14 | [dependencies] 15 | serde = "1.0.0" 16 | serde_derive = "1.0.0" 17 | quick-error = "1.1.0" 18 | libc = "0.2.16" 19 | num_cpus = "1.1.0" 20 | 21 | [dev-dependencies] 22 | serde_json = "1.0.0" 23 | 24 | [lib] 25 | name = "self_meter" 26 | path = "src/lib.rs" 27 | 28 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2016 The self-meter Developers 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Self-Meter 2 | ========== 3 | 4 | :Status: beta 5 | :Documentation: https://docs.rs/self-meter/ 6 | 7 | A tiny library to measure resource usage of the process it's used in. 8 | Currently it measures: 9 | 10 | * Memory Usage 11 | * CPU Usage with breakdown by each thread 12 | * Disk Usage 13 | 14 | More metrics might be added later. Currently, library supports only linux, 15 | but pull requests for other platforms are welcome. 16 | 17 | 18 | ======= 19 | License 20 | ======= 21 | 22 | Licensed under either of 23 | 24 | * Apache License, Version 2.0, (./LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) 25 | * MIT license (./LICENSE-MIT or http://opensource.org/licenses/MIT) 26 | 27 | at your option. 28 | 29 | ------------ 30 | Contribution 31 | ------------ 32 | 33 | Unless you explicitly state otherwise, any contribution intentionally 34 | submitted for inclusion in the work by you, as defined in the Apache-2.0 35 | license, shall be dual licensed as above, without any additional terms or 36 | conditions. 37 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | os: Visual Studio 2015 2 | 3 | environment: 4 | matrix: 5 | 6 | ### MSVC Toolchains ### 7 | 8 | # Stable 64-bit MSVC 9 | - channel: stable 10 | target: x86_64-pc-windows-msvc 11 | # Beta 64-bit MSVC 12 | - channel: beta 13 | target: x86_64-pc-windows-msvc 14 | # Nightly 64-bit MSVC 15 | - channel: nightly 16 | target: x86_64-pc-windows-msvc 17 | 18 | ### GNU Toolchains ### 19 | 20 | # Stable 64-bit GNU 21 | - channel: stable 22 | target: x86_64-pc-windows-gnu 23 | 24 | ### Allowed failures ### 25 | # 26 | matrix: 27 | allow_failures: 28 | - channel: nightly 29 | 30 | ## Install Script ## 31 | 32 | install: 33 | - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe 34 | - rustup-init -yv --default-toolchain %channel% --default-host %target% 35 | - set PATH=%PATH%;%USERPROFILE%\.cargo\bin 36 | - rustc -vV 37 | - cargo -vV 38 | 39 | build: false 40 | 41 | test_script: 42 | - cargo test --verbose %cargoflags% 43 | -------------------------------------------------------------------------------- /examples/json.rs: -------------------------------------------------------------------------------- 1 | extern crate self_meter; 2 | extern crate serde_json; 3 | 4 | use std::io::{Write, stderr}; 5 | use std::time::Duration; 6 | use std::thread::sleep; 7 | use std::collections::BTreeMap; 8 | 9 | 10 | fn main() { 11 | let mut meter = self_meter::Meter::new(Duration::new(1, 0)).unwrap(); 12 | meter.track_current_thread("main"); 13 | loop { 14 | meter.scan() 15 | .map_err(|e| writeln!(&mut stderr(), "Scan error: {}", e)).ok(); 16 | println!("Report: {}", 17 | serde_json::ser::to_string_pretty(&meter.report()).unwrap()); 18 | println!("Threads: {}", 19 | serde_json::ser::to_string_pretty( 20 | &meter.thread_report().map(|x| x.collect::>()) 21 | ).unwrap()); 22 | let mut x = 0; 23 | for _ in 0..10000000 { 24 | x = u64::wrapping_mul(x, 7); 25 | } 26 | sleep(Duration::new(1, 0)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | extern crate self_meter; 2 | 3 | use std::io::{Write, stderr}; 4 | use std::time::Duration; 5 | use std::thread::sleep; 6 | use std::collections::BTreeMap; 7 | 8 | 9 | fn main() { 10 | let mut meter = self_meter::Meter::new(Duration::new(1, 0)).unwrap(); 11 | meter.track_current_thread("main"); 12 | loop { 13 | meter.scan() 14 | .map_err(|e| writeln!(&mut stderr(), "Scan error: {}", e)).ok(); 15 | println!("Report: {:#?}", meter.report()); 16 | println!("Threads: {:#?}", 17 | meter.thread_report().map(|x| x.collect::>())); 18 | let mut x = 0; 19 | for _ in 0..10000000 { 20 | x = u64::wrapping_mul(x, 7); 21 | } 22 | sleep(Duration::new(1, 0)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/debug.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use {Meter, ThreadReportIter}; 4 | 5 | impl fmt::Debug for Meter { 6 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 7 | f.debug_struct("Meter") 8 | .field("scan_interval", &self.scan_interval) 9 | .field("snapshots", &self.snapshots.len()) 10 | .field("threads", &self.thread_names.len()) 11 | .finish() 12 | } 13 | } 14 | 15 | impl<'a> fmt::Debug for ThreadReportIter<'a> { 16 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 17 | f.debug_struct("ThreadReportIter") 18 | .finish() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::num::ParseIntError; 3 | 4 | use Pid; 5 | 6 | 7 | quick_error! { 8 | #[derive(Debug)] 9 | /// Error reading or parsing /proc/uptime 10 | pub enum UptimeError { 11 | Io(err: io::Error) { 12 | description("IO error") 13 | display("{}", err) 14 | from() 15 | } 16 | ParseInt(e: ParseIntError) { 17 | description("error parsing int") 18 | display("error parsing int: {}", e) 19 | from() 20 | } 21 | BadFormat { 22 | description("bad format") 23 | } 24 | } 25 | } 26 | 27 | quick_error! { 28 | #[derive(Debug)] 29 | /// Error reading or parsing /proc/self/stat or /proc/self/task//stat 30 | pub enum StatError { 31 | Io(err: io::Error) { 32 | description("IO error") 33 | display("{}", err) 34 | from() 35 | } 36 | ParseInt(e: ParseIntError) { 37 | description("error parsing int") 38 | display("error parsing int: {}", e) 39 | from() 40 | } 41 | BadFormat { 42 | description("bad format") 43 | } 44 | } 45 | } 46 | 47 | quick_error! { 48 | #[derive(Debug)] 49 | /// Error reading or parsing /proc/self/io 50 | pub enum IoStatError { 51 | Io(err: io::Error) { 52 | description("IO error") 53 | display("{}", err) 54 | from() 55 | } 56 | ParseInt(e: ParseIntError) { 57 | description("error parsing int") 58 | display("error parsing int: {}", e) 59 | from() 60 | } 61 | } 62 | } 63 | 64 | quick_error! { 65 | #[derive(Debug)] 66 | /// Error reading or parsing /proc/self/status 67 | pub enum StatusError { 68 | Io(err: io::Error) { 69 | description("IO error") 70 | display("{}", err) 71 | from() 72 | } 73 | ParseInt(e: ParseIntError) { 74 | description("error parsing int") 75 | display("error parsing int: {}", e) 76 | from() 77 | } 78 | BadUnit { 79 | description("bad unit in memory data") 80 | } 81 | BadFormat { 82 | description("bad format") 83 | } 84 | } 85 | } 86 | 87 | 88 | quick_error! { 89 | #[derive(Debug)] 90 | /// Error scanning process info in /proc 91 | pub enum Error { 92 | /// Error reading uptime value 93 | Uptime(err: UptimeError) { 94 | description("Error reading /proc/uptime") 95 | display("Error reading /proc/uptime: {}", err) 96 | from() 97 | } 98 | /// Error reading /proc/self/status 99 | Status(err: StatusError) { 100 | description("Error reading /proc/self/status") 101 | display("Error reading /proc/self/status: {}", err) 102 | from() 103 | } 104 | /// Error reading /proc/self/stat 105 | Stat(err: StatError) { 106 | description("Error reading /proc/self/stat") 107 | display("Error reading /proc/self/stat: {}", err) 108 | } 109 | /// Error reading thread status 110 | ThreadStat(tid: Pid, err: StatError) { 111 | description("Error reading /proc/self/task//stat") 112 | display("Error reading /proc/self/task/{}/stat: {}", tid, err) 113 | } 114 | /// Error reading IO stats 115 | IoStat(err: IoStatError) { 116 | description("Error reading /proc/self/io") 117 | display("Error reading /proc/self/io: {}", err) 118 | from() 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(missing_docs)] 2 | #![warn(missing_debug_implementations)] 3 | //! A tiny library to measure resource usage of the process it's used in. 4 | //! Currently it measures: 5 | //! 6 | //! * Memory Usage 7 | //! * CPU Usage with breakdown by each thread 8 | //! * Disk Usage 9 | //! 10 | //! More metrics might be added later. Currently, library supports only linux, 11 | //! but pull requests for other platforms are welcome. 12 | //! 13 | //! # Example 14 | //! 15 | //! ```rust,no_run 16 | //! 17 | //! # use std::io::{Write, stderr}; 18 | //! # use std::time::Duration; 19 | //! # use std::thread::sleep; 20 | //! # use std::collections::BTreeMap; 21 | //! 22 | //! fn main() { 23 | //! let mut meter = self_meter::Meter::new(Duration::new(1, 0)).unwrap(); 24 | //! meter.track_current_thread("main"); 25 | //! loop { 26 | //! meter.scan() 27 | //! .map_err(|e| writeln!(&mut stderr(), "Scan error: {}", e)).ok(); 28 | //! println!("Report: {:#?}", meter.report()); 29 | //! println!("Threads: {:#?}", 30 | //! meter.thread_report().map(|x| x.collect::>())); 31 | //! // Put your task here 32 | //! // ... 33 | //! // 34 | //! sleep(Duration::new(1, 0)); 35 | //! } 36 | //! } 37 | //! ``` 38 | extern crate libc; 39 | extern crate num_cpus; 40 | extern crate serde; 41 | 42 | #[macro_use] extern crate quick_error; 43 | #[macro_use] extern crate serde_derive; 44 | 45 | use std::fs::File; 46 | use std::time::{SystemTime, Instant, Duration}; 47 | use std::collections::{VecDeque, HashMap}; 48 | 49 | mod meter; 50 | mod scan; 51 | mod error; 52 | mod report; 53 | mod serialize; 54 | mod debug; 55 | 56 | pub use error::Error; 57 | pub use report::ThreadReportIter; 58 | /// A Pid type used to identify processes and threads 59 | pub type Pid = u32; 60 | 61 | struct ThreadInfo { 62 | user_time: u64, 63 | system_time: u64, 64 | child_user_time: u64, 65 | child_system_time: u64, 66 | } 67 | 68 | struct Snapshot { 69 | timestamp: SystemTime, 70 | instant: Instant, 71 | /// System uptime in centisecs 72 | uptime: u64, 73 | /// System idle time in centisecs 74 | idle_time: u64, 75 | process: ThreadInfo, 76 | memory_rss: u64, 77 | memory_virtual: u64, 78 | memory_virtual_peak: u64, 79 | memory_swap: u64, 80 | read_bytes: u64, 81 | write_bytes: u64, 82 | read_ops: u64, 83 | write_ops: u64, 84 | read_disk_bytes: u64, 85 | write_disk_bytes: u64, 86 | write_cancelled_bytes: u64, 87 | threads: HashMap, 88 | } 89 | 90 | 91 | /// CPU usage of a single thread 92 | #[derive(Debug)] 93 | pub struct ThreadUsage { 94 | /// Thread's own CPU usage. 100% is a single core 95 | pub cpu_usage: f32, 96 | /// Thread's CPU usage with its awaited children. 100% is a single core 97 | pub cpu_usage_with_children: f32, 98 | } 99 | 100 | /// Report returned by `Meter::report` 101 | /// 102 | /// Note: this structure implements `serde::Serialize`, and all timestamps and 103 | /// durations are stored as integers in milliseconds. 104 | #[derive(Debug, Serialize)] 105 | pub struct Report { 106 | /// Timestamp 107 | #[serde(serialize_with="serialize::serialize_timestamp")] 108 | pub timestamp: SystemTime, 109 | 110 | /// The interval time this data has averaged over in milliseconds 111 | #[serde(serialize_with="serialize::serialize_duration")] 112 | pub duration: Duration, 113 | 114 | /// Start time 115 | #[serde(serialize_with="serialize::serialize_timestamp")] 116 | pub start_time: SystemTime, 117 | 118 | /// The uptime of the system 119 | /// 120 | /// Note this value can be smaller than time since `start_time` 121 | /// because this value doesn't include time when system was sleeping 122 | #[serde(serialize_with="serialize::serialize_duration")] 123 | pub system_uptime: Duration, 124 | /// Whole system CPU usage. 100% is all cores 125 | pub global_cpu_usage: f32, 126 | /// Process' own CPU usage. 100% is a single core 127 | pub process_cpu_usage: f32, 128 | /// Process' CPU usage with its awaited children. 100% is a single core 129 | pub gross_cpu_usage: f32, 130 | /// Process' memory usage 131 | pub memory_rss: u64, 132 | /// Process' virtual memory usage 133 | pub memory_virtual: u64, 134 | /// Process' swap usage 135 | pub memory_swap: u64, 136 | /// Process' peak memory usage (not precise) 137 | pub memory_rss_peak: u64, 138 | /// Process' peak virtual memory usage (tracked by OS) 139 | pub memory_virtual_peak: u64, 140 | /// Process' swap usage (not precise) 141 | pub memory_swap_peak: u64, 142 | /// Bytes read per second from block-backed filesystems 143 | pub disk_read: f32, 144 | /// Bytes written per second from block-backed filesystems 145 | pub disk_write: f32, 146 | /// Bytes per second of cancelled writes (i.e. removed temporary files) 147 | pub disk_cancelled: f32, 148 | /// Bytes read per second (total) 149 | pub io_read: f32, 150 | /// Bytes written per second (total) 151 | pub io_write: f32, 152 | /// Read operations (syscalls) per second (total) 153 | pub io_read_ops: f32, 154 | /// Write operations (syscalls) per second (total) 155 | pub io_write_ops: f32, 156 | } 157 | 158 | /// Report of CPU usage by single thread 159 | #[derive(Debug, Serialize)] 160 | pub struct ThreadReport { 161 | /// Threads' own CPU usage. 100% is a single core 162 | pub cpu_usage: f32, 163 | /// Threads' own CPU usage in kernel space. 100% is a single core 164 | pub system_cpu: f32, 165 | /// Threads' own CPU usage in user space. 100% is a single core 166 | pub user_cpu: f32, 167 | } 168 | 169 | /// The main structure that makes mesurements and reports values 170 | /// 171 | /// Create it with `new()` then add threads that you want to track in a thread 172 | /// breakdown information with `meter.track_thread()` and 173 | /// `meter.untrack_thread()`. 174 | /// 175 | /// Then add `meter.scan()` with a timer to scan the process info. It's 176 | /// recommended to call it on the interval of one second. 177 | /// 178 | /// Method `report()` may be used to get structure with stats. `report_json()` 179 | /// can return a `rustc_serialize::Json` and `report_json_str()` returns that 180 | /// serialized. 181 | /// 182 | /// Note that the structure returned with `report()` can be changed when we 183 | /// bump **major version** of the library. And while `report_json()` and 184 | /// `report_json_str()` will never break the type system, their format will 185 | /// always reflect that of `report()` call. 186 | /// 187 | /// We don't track all the threads separately because thread ids are useless 188 | /// without names, and we can fine-tune performance in the case we have known 189 | /// number of threads. Obviously, process-wide info accounts all the threads. 190 | pub struct Meter { 191 | #[allow(dead_code)] 192 | scan_interval: Duration, 193 | num_cpus: usize, 194 | num_snapshots: usize, 195 | start_time: SystemTime, 196 | snapshots: VecDeque, 197 | thread_names: HashMap, 198 | /// This is a buffer for reading some text data from /proc/anything. 199 | /// We use it to avoid memory allocations. This makes code a little bit 200 | /// more complex, but we want to avoid overhead as much as possible 201 | text_buf: String, 202 | /// This is a smaller buffer for formatting paths, similar to `text_buf` 203 | path_buf: String, 204 | 205 | /// This file is always open because if we drop privileges and then 206 | /// try to open a file we can't open it back again 207 | #[cfg(target_os="linux")] 208 | io_file: File, 209 | 210 | memory_rss_peak: u64, 211 | memory_swap_peak: u64, 212 | } 213 | -------------------------------------------------------------------------------- /src/meter.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::time::{Duration, SystemTime}; 3 | use std::collections::{VecDeque, HashMap}; 4 | 5 | use num_cpus; 6 | 7 | use {Meter, Error, Pid}; 8 | use error::IoStatError; 9 | 10 | 11 | impl Meter { 12 | /// Create a new meter with scan_interval 13 | /// 14 | /// Note: meter will not scan by itself, you are expected to call `scan()` 15 | /// with interval. 16 | /// 17 | /// You don't have to guarantee the interval exactly, but it influences 18 | /// the accuracy of your measurements. 19 | /// 20 | /// When creating a `Meter` object we are trying to discover the number 21 | /// of processes on the system. If that fails, we return error. 22 | pub fn new(scan_interval: Duration) -> Result { 23 | Meter::_new(scan_interval) 24 | } 25 | #[cfg(target_os="linux")] 26 | fn _new(scan_interval: Duration) -> Result { 27 | let io_file = File::open("/proc/self/io").map_err(IoStatError::Io)?; 28 | Ok(Meter { 29 | scan_interval: scan_interval, 30 | num_cpus: num_cpus::get(), 31 | num_snapshots: 10, 32 | start_time: SystemTime::now(), 33 | snapshots: VecDeque::with_capacity(10), 34 | thread_names: HashMap::new(), 35 | text_buf: String::with_capacity(1024), 36 | path_buf: String::with_capacity(100), 37 | io_file: io_file, 38 | 39 | memory_swap_peak: 0, 40 | memory_rss_peak: 0, 41 | }) 42 | } 43 | 44 | #[cfg(not(target_os="linux"))] 45 | fn _new(scan_interval: Duration) -> Result { 46 | Ok(Meter { 47 | scan_interval: scan_interval, 48 | num_cpus: num_cpus::get(), 49 | num_snapshots: 10, 50 | start_time: SystemTime::now(), 51 | snapshots: VecDeque::with_capacity(10), 52 | thread_names: HashMap::new(), 53 | text_buf: String::with_capacity(1024), 54 | path_buf: String::with_capacity(100), 55 | 56 | memory_swap_peak: 0, 57 | memory_rss_peak: 0, 58 | }) 59 | } 60 | 61 | /// Start tracking specified thread 62 | /// 63 | /// Note you must add main thread here manually 64 | pub fn track_thread(&mut self, tid: Pid, name: &str) { 65 | self.thread_names.insert(tid, name.to_string()); 66 | } 67 | /// Stop tracking specified thread (for example if it's dead) 68 | pub fn untrack_thread(&mut self, tid: Pid) { 69 | self.thread_names.remove(&tid); 70 | for s in &mut self.snapshots { 71 | s.threads.remove(&tid); 72 | } 73 | } 74 | /// Add current thread using `track_thread`, returns thread id 75 | #[cfg(target_os="linux")] 76 | pub fn track_current_thread(&mut self, name: &str) -> Pid { 77 | use libc::{syscall, SYS_gettid}; 78 | let tid = unsafe { syscall(SYS_gettid) } as Pid; 79 | self.track_thread(tid, name); 80 | return tid; 81 | } 82 | /// Add current thread using `track_thread`, returns thread id 83 | /// 84 | /// Non-linux is not supported yet (no-op) 85 | #[cfg(not(target_os="linux"))] 86 | pub fn track_current_thread(&mut self, _name: &str) -> Pid { 87 | // TODO(tailhook) OS X and windows 88 | 0 89 | } 90 | /// Remove current thread using `untrack_thread` 91 | #[cfg(target_os="linux")] 92 | pub fn untrack_current_thread(&mut self) { 93 | use libc::{syscall, SYS_gettid}; 94 | let tid = unsafe { syscall(SYS_gettid) } as Pid; 95 | self.untrack_thread(tid); 96 | } 97 | /// Remove current thread using `untrack_thread` 98 | /// 99 | /// Non-linux is not supported yet (no-op) 100 | #[cfg(not(target_os="linux"))] 101 | pub fn untrack_current_thread(&mut self) { 102 | // TODO 103 | } 104 | /// Returns interval value configured in constructor 105 | pub fn get_scan_interval(&self) -> Duration { 106 | self.scan_interval 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/report.rs: -------------------------------------------------------------------------------- 1 | use std::time::{Duration}; 2 | use std::collections::hash_map::Iter; 3 | 4 | use {Pid, Meter, Report, Snapshot, ThreadReport}; 5 | 6 | 7 | /// Iterator over thread reports returned by ``Meter::thread_report`` 8 | pub struct ThreadReportIter<'a> { 9 | threads: Iter<'a,Pid, String>, 10 | last: &'a Snapshot, 11 | prev: &'a Snapshot, 12 | centisecs: f32, 13 | } 14 | 15 | fn duration_from_ms(ms: u64) -> Duration { 16 | Duration::new(ms / 1000, ((ms % 1000) * 1000_000) as u32) 17 | } 18 | 19 | 20 | impl Meter { 21 | /// Get report of the last scan interval 22 | /// 23 | /// We need at least two scans to measure CPU usage, so this method 24 | /// returns None if less than two scans were done ever in the past. 25 | pub fn report(&self) -> Option { 26 | if self.snapshots.len() < 2 { 27 | return None; 28 | } 29 | let n = self.snapshots.len(); 30 | let last = &self.snapshots[n-1]; 31 | let prev = &self.snapshots[n-2]; 32 | let lpro = &last.process; 33 | let ppro = &prev.process; 34 | let centisecs = (last.uptime - prev.uptime) as f32; 35 | let secs = centisecs / 100.0; 36 | let mut cpu_usage = 100.0 * (1.0 - 37 | (last.idle_time - prev.idle_time) as f32 / 38 | (centisecs * self.num_cpus as f32)); 39 | if cpu_usage < 0. { // sometimes we get inaccuracy 40 | cpu_usage = 0.; 41 | } 42 | Some(Report { 43 | timestamp: last.timestamp, 44 | duration: last.instant - prev.instant, 45 | start_time: self.start_time, 46 | system_uptime: duration_from_ms(last.uptime * 10), // centisecs 47 | global_cpu_usage: cpu_usage, 48 | process_cpu_usage: 100.0 * 49 | (lpro.user_time + lpro.system_time - 50 | (ppro.user_time + ppro.system_time)) as f32 / centisecs, 51 | gross_cpu_usage: 100.0 * 52 | ((lpro.user_time + lpro.system_time + 53 | lpro.child_user_time + lpro.child_system_time) - 54 | (ppro.user_time + ppro.system_time + 55 | ppro.child_user_time + ppro.child_system_time)) as f32 / 56 | centisecs, 57 | memory_rss: last.memory_rss, 58 | memory_virtual: last.memory_virtual, 59 | memory_swap: last.memory_swap, 60 | memory_rss_peak: self.memory_rss_peak, 61 | memory_virtual_peak: last.memory_virtual_peak, 62 | memory_swap_peak: self.memory_swap_peak, 63 | disk_read: (last.read_disk_bytes - prev.read_disk_bytes) as f32 64 | / secs, 65 | disk_write: (last.write_disk_bytes - prev.write_disk_bytes) as f32 66 | / secs, 67 | disk_cancelled: (last.write_cancelled_bytes - 68 | prev.write_cancelled_bytes) as f32 / secs, 69 | io_read: (last.read_bytes - prev.read_bytes) as f32 / secs, 70 | io_write: (last.write_bytes - prev.write_bytes) as f32 / secs, 71 | io_read_ops: (last.read_ops - prev.read_ops) as f32 / secs, 72 | io_write_ops: (last.write_ops - prev.write_ops) as f32 / secs, 73 | }) 74 | } 75 | /// Returns iterator over reports for threads 76 | /// 77 | /// Note: each thread must be registered with `Meter::track_thread` or 78 | /// `Meter::track_current_thread` to be tracked here. 79 | /// 80 | /// We need at least two scans to measure CPU usage, so this method 81 | /// returns None if less than two scans were done ever in the past. 82 | pub fn thread_report(&self) -> Option { 83 | if self.snapshots.len() < 2 { 84 | return None; 85 | } 86 | let n = self.snapshots.len(); 87 | let last = &self.snapshots[n-1]; 88 | let prev = &self.snapshots[n-2]; 89 | let centisecs = (last.uptime - prev.uptime) as f32; 90 | Some(ThreadReportIter { 91 | threads: self.thread_names.iter(), 92 | last: last, 93 | prev: prev, 94 | centisecs: centisecs, 95 | }) 96 | } 97 | } 98 | 99 | impl<'a> Iterator for ThreadReportIter<'a> { 100 | type Item = (&'a str, ThreadReport); 101 | fn next(&mut self) -> Option<(&'a str, ThreadReport)> { 102 | while let Some((&pid, name)) = self.threads.next() { 103 | let lth = if let Some(thread) = self.last.threads.get(&pid) { 104 | thread 105 | } else { 106 | continue; // not enough stats for a thread yet 107 | }; 108 | let pth = if let Some(thread) = self.prev.threads.get(&pid) { 109 | thread 110 | } else { 111 | continue; // not enough stats for a thread yet 112 | }; 113 | let udelta = lth.user_time - pth.user_time; 114 | let sdelta = lth.system_time - pth.system_time; 115 | return Some((&name[..], ThreadReport { 116 | cpu_usage: 100.0 * (udelta + sdelta) as f32 / self.centisecs, 117 | system_cpu: 100.0 * sdelta as f32 / self.centisecs, 118 | user_cpu: 100.0 * udelta as f32 / self.centisecs, 119 | })) 120 | } 121 | None 122 | } 123 | fn size_hint(&self) -> (usize, Option) { 124 | let (_min, max) = self.threads.size_hint(); 125 | // unfortunately we skip non-scanned threads, so we must assume that 126 | // minimum size is zero 127 | return (0, max); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/scan.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Seek, SeekFrom}; 2 | use std::fs::File; 3 | use std::fmt::Write; 4 | use std::num::ParseIntError; 5 | use std::time::{Instant, SystemTime}; 6 | use std::collections::HashMap; 7 | 8 | use {Meter, Snapshot, ThreadInfo, Pid, Error}; 9 | use error::{UptimeError, StatError, StatusError, IoStatError}; 10 | 11 | 12 | impl Meter { 13 | /// Scan system for metrics 14 | /// 15 | /// This method must be called regularly at intervals specified 16 | /// in constructor. 17 | pub fn scan(&mut self) -> Result<(), Error> { 18 | // We reuse Snapshot structure (mostly becasuse of threads hash map) 19 | // to have smaller allocations on the fast path 20 | let mut snap = if self.snapshots.len() >= self.num_snapshots { 21 | self.snapshots.pop_front().unwrap() 22 | } else { 23 | Snapshot::new(&self.thread_names) 24 | }; 25 | snap.timestamp = SystemTime::now(); 26 | snap.instant = Instant::now(); 27 | 28 | // First scan everything that relates to cpu_time to have as accurate 29 | // CPU usage measurements as possible 30 | try!(self.read_cpu_times(&mut snap.process, 31 | &mut snap.threads, 32 | &mut snap.uptime, &mut snap.idle_time)); 33 | 34 | try!(self.read_memory(&mut snap)); 35 | try!(self.read_io(&mut snap)); 36 | 37 | if snap.memory_rss > self.memory_rss_peak { 38 | self.memory_rss_peak = snap.memory_rss; 39 | } 40 | if snap.memory_swap > self.memory_swap_peak { 41 | self.memory_swap_peak = snap.memory_swap; 42 | } 43 | 44 | self.snapshots.push_back(snap); 45 | Ok(()) 46 | } 47 | 48 | #[cfg(target_os="linux")] 49 | fn read_cpu_times(&mut self, process: &mut ThreadInfo, 50 | threads: &mut HashMap, 51 | uptime: &mut u64, idle_time: &mut u64) 52 | -> Result<(), Error> 53 | { 54 | self.text_buf.truncate(0); 55 | try!(File::open("/proc/uptime") 56 | .and_then(|mut f| f.read_to_string(&mut self.text_buf)) 57 | .map_err(|e| Error::Uptime(e.into()))); 58 | { 59 | let mut iter = self.text_buf.split_whitespace(); 60 | let seconds = try!(iter.next() 61 | .ok_or(Error::Uptime(UptimeError::BadFormat))); 62 | let idle_sec = try!(iter.next() 63 | .ok_or(Error::Uptime(UptimeError::BadFormat))); 64 | *uptime = try!(parse_uptime(seconds)); 65 | *idle_time = try!(parse_uptime(idle_sec)); 66 | } 67 | try!(read_stat(&mut self.text_buf, "/proc/self/stat", process) 68 | .map_err(Error::Stat)); 69 | for (&tid, _) in &self.thread_names { 70 | self.path_buf.truncate(0); 71 | write!(&mut self.path_buf, 72 | "/proc/self/task/{}/stat", tid).unwrap(); 73 | try!(read_stat(&mut self.text_buf, &self.path_buf[..], 74 | threads.entry(tid).or_insert_with(ThreadInfo::new)) 75 | .map_err(|e| Error::ThreadStat(tid, e))); 76 | } 77 | Ok(()) 78 | } 79 | 80 | #[cfg(not(target_os="linux"))] 81 | fn read_cpu_times(&mut self, process: &mut ThreadInfo, 82 | threads: &mut HashMap, 83 | uptime: &mut u64, idle_time: &mut u64) 84 | -> Result<(), Error> 85 | { 86 | Ok(()) 87 | } 88 | 89 | #[cfg(target_os="linux")] 90 | fn read_memory(&mut self, snap: &mut Snapshot) 91 | -> Result<(), StatusError> 92 | { 93 | self.text_buf.truncate(0); 94 | try!(File::open("/proc/self/status") 95 | .and_then(|mut f| f.read_to_string(&mut self.text_buf))); 96 | for line in self.text_buf.lines() { 97 | let mut pairs = line.split(':'); 98 | match (pairs.next(), pairs.next()) { 99 | (Some("VmPeak"), Some(text)) 100 | => snap.memory_virtual_peak = try!(parse_memory(text)), 101 | (Some("VmSize"), Some(text)) 102 | => snap.memory_virtual = try!(parse_memory(text)), 103 | (Some("VmRSS"), Some(text)) 104 | => snap.memory_rss = try!(parse_memory(text)), 105 | (Some("VmSwap"), Some(text)) 106 | => snap.memory_swap = try!(parse_memory(text)), 107 | _ => {} 108 | } 109 | } 110 | Ok(()) 111 | } 112 | 113 | #[cfg(not(target_os="linux"))] 114 | fn read_memory(&mut self, snap: &mut Snapshot) 115 | -> Result<(), StatusError> 116 | { 117 | Ok(()) 118 | } 119 | 120 | #[cfg(target_os="linux")] 121 | fn read_io(&mut self, snap: &mut Snapshot) 122 | -> Result<(), Error> 123 | { 124 | let err = &|e: ParseIntError| Error::IoStat(e.into()); 125 | self.text_buf.truncate(0); 126 | self.io_file.seek(SeekFrom::Start(0)) 127 | .map_err(IoStatError::Io)?; 128 | self.io_file.read_to_string(&mut self.text_buf) 129 | .map_err(IoStatError::Io)?; 130 | for line in self.text_buf.lines() { 131 | let mut pairs = line.split(':'); 132 | match (pairs.next(), pairs.next().map(|x| x.trim())) { 133 | (Some("rchar"), Some(text)) 134 | => snap.read_bytes = text.parse().map_err(err)?, 135 | (Some("wchar"), Some(text)) 136 | => snap.write_bytes = text.parse().map_err(err)?, 137 | (Some("syscr"), Some(text)) 138 | => snap.read_ops = text.parse().map_err(err)?, 139 | (Some("syscw"), Some(text)) 140 | => snap.write_ops = text.parse().map_err(err)?, 141 | (Some("read_bytes"), Some(text)) 142 | => snap.read_disk_bytes = text.parse().map_err(err)?, 143 | (Some("write_bytes"), Some(text)) 144 | => snap.write_disk_bytes = text.parse().map_err(err)?, 145 | (Some("cancelled_write_bytes"), Some(text)) => { 146 | snap.write_cancelled_bytes = text.parse().map_err(err)?; 147 | } 148 | _ => {} 149 | } 150 | } 151 | Ok(()) 152 | } 153 | #[cfg(not(target_os="linux"))] 154 | fn read_io(&mut self, snap: &mut Snapshot) 155 | -> Result<(), Error> 156 | { 157 | // No IO tracking yet 158 | Ok(()) 159 | } 160 | 161 | } 162 | 163 | fn parse_memory(value: &str) -> Result { 164 | let mut pair = value.split_whitespace(); 165 | let value = try!(try!(pair.next().ok_or(StatusError::BadFormat)) 166 | .parse::()); 167 | match pair.next() { 168 | Some("kB") => Ok(value * 1024), 169 | _ => Err(StatusError::BadUnit), 170 | } 171 | } 172 | 173 | pub fn parse_uptime(value: &str) -> Result { 174 | if value.len() <= 3 { 175 | return Err(UptimeError::BadFormat); 176 | } 177 | let dot = value.find('.').ok_or(UptimeError::BadFormat)?; 178 | let (integer, decimals) = value.split_at(dot); 179 | if decimals.len() == 1+1 { 180 | Ok(try!(integer.parse::()) * 100 + 181 | try!(decimals[1..].parse::())*10) 182 | } else if decimals.len() == 1+2 { 183 | Ok(try!(integer.parse::()) * 100 + 184 | try!(decimals[1..].parse::())) 185 | } else { 186 | Err(UptimeError::BadFormat) 187 | } 188 | } 189 | 190 | fn read_stat(text_buf: &mut String, path: &str, thread_info: &mut ThreadInfo) 191 | -> Result<(), StatError> 192 | { 193 | text_buf.truncate(0); 194 | try!(File::open(path) 195 | .and_then(|mut f| f.read_to_string(text_buf))); 196 | let right_paren = try!(text_buf.rfind(')') 197 | .ok_or(StatError::BadFormat)); 198 | let mut iter = text_buf[right_paren+1..].split_whitespace(); 199 | thread_info.user_time = try!( 200 | try!(iter.nth(11).ok_or(StatError::BadFormat)).parse()); 201 | thread_info.system_time = try!( 202 | try!(iter.next().ok_or(StatError::BadFormat)).parse()); 203 | thread_info.child_user_time = try!( 204 | try!(iter.next().ok_or(StatError::BadFormat)).parse()); 205 | thread_info.child_system_time = try!( 206 | try!(iter.next().ok_or(StatError::BadFormat)).parse()); 207 | Ok(()) 208 | } 209 | 210 | impl ThreadInfo { 211 | fn new() -> ThreadInfo { 212 | ThreadInfo { 213 | user_time: 0, 214 | system_time: 0, 215 | child_user_time: 0, 216 | child_system_time: 0, 217 | } 218 | } 219 | } 220 | 221 | impl Snapshot { 222 | fn new(threads: &HashMap) -> Snapshot { 223 | Snapshot { 224 | timestamp: SystemTime::now(), 225 | instant: Instant::now(), 226 | uptime: 0, 227 | idle_time: 0, 228 | process: ThreadInfo::new(), 229 | memory_rss: 0, 230 | memory_virtual: 0, 231 | memory_virtual_peak: 0, 232 | memory_swap: 0, 233 | read_bytes: 0, 234 | write_bytes: 0, 235 | read_ops: 0, 236 | write_ops: 0, 237 | read_disk_bytes: 0, 238 | write_disk_bytes: 0, 239 | write_cancelled_bytes: 0, 240 | threads: threads.iter() 241 | .map(|(&pid, _)| (pid, ThreadInfo::new())) 242 | .collect(), 243 | } 244 | } 245 | } 246 | 247 | #[cfg(test)] 248 | mod test { 249 | use super::parse_uptime; 250 | 251 | #[test] 252 | fn normal_uptime() { 253 | assert_eq!(parse_uptime("1927830.69").unwrap(), 192783069); 254 | } 255 | #[test] 256 | fn one_zero_uptime() { 257 | assert_eq!(parse_uptime("4780.0").unwrap(), 478000); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/serialize.rs: -------------------------------------------------------------------------------- 1 | use std::time::{SystemTime, Duration, UNIX_EPOCH}; 2 | 3 | use serde::Serializer; 4 | 5 | 6 | fn tstamp_to_ms(tm: SystemTime) -> u64 { 7 | let ts = tm.duration_since(UNIX_EPOCH) 8 | .expect("timestamp is always after unix epoch"); 9 | return ts.as_secs()*1000 + (ts.subsec_nanos() / 1000000) as u64; 10 | } 11 | 12 | fn duration_to_ms(dur: Duration) -> u64 { 13 | return dur.as_secs()*1000 + (dur.subsec_nanos() / 1000000) as u64; 14 | } 15 | 16 | pub fn serialize_timestamp(tm: &SystemTime, ser: S) 17 | -> Result 18 | where S: Serializer 19 | { 20 | ser.serialize_u64(tstamp_to_ms(*tm)) 21 | } 22 | 23 | pub fn serialize_duration(tm: &Duration, ser: S) 24 | -> Result 25 | where S: Serializer 26 | { 27 | ser.serialize_u64(duration_to_ms(*tm)) 28 | } 29 | -------------------------------------------------------------------------------- /vagga.yaml: -------------------------------------------------------------------------------- 1 | commands: 2 | 3 | cargo: !Command 4 | description: Run any cargo command 5 | container: ubuntu 6 | run: [cargo] 7 | 8 | make: !Command 9 | description: Build the library 10 | container: ubuntu 11 | run: [cargo, build] 12 | 13 | test-uid: !Command 14 | container: ubuntu 15 | user-id: 1 16 | run: ['sh'] 17 | 18 | containers: 19 | 20 | ubuntu: 21 | setup: 22 | - !Ubuntu xenial 23 | - !Install [build-essential, ca-certificates] 24 | - !TarInstall 25 | url: "https://static.rust-lang.org/dist/rust-1.16.0-x86_64-unknown-linux-gnu.tar.gz" 26 | script: "./install.sh --prefix=/usr \ 27 | --components=rustc,rust-std-x86_64-unknown-linux-gnu,cargo" 28 | 29 | environ: 30 | HOME: /work/target 31 | USER: user 32 | --------------------------------------------------------------------------------