├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── screenshot.png └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "tally" 3 | version = "0.4.2" 4 | dependencies = [ 5 | "clap 2.26.2 (registry+https://github.com/rust-lang/crates.io-index)", 6 | "colored 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 7 | "csv 1.0.0-beta.4 (registry+https://github.com/rust-lang/crates.io-index)", 8 | "libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)", 9 | "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", 10 | ] 11 | 12 | [[package]] 13 | name = "ansi_term" 14 | version = "0.9.0" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | 17 | [[package]] 18 | name = "atty" 19 | version = "0.2.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | dependencies = [ 22 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 23 | "libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)", 24 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 25 | ] 26 | 27 | [[package]] 28 | name = "bitflags" 29 | version = "0.9.1" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | 32 | [[package]] 33 | name = "clap" 34 | version = "2.26.2" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | dependencies = [ 37 | "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 38 | "atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 39 | "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 40 | "strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 41 | "term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 42 | "textwrap 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 43 | "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 44 | "vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 45 | ] 46 | 47 | [[package]] 48 | name = "colored" 49 | version = "1.5.2" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | dependencies = [ 52 | "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 53 | ] 54 | 55 | [[package]] 56 | name = "csv" 57 | version = "1.0.0-beta.4" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | dependencies = [ 60 | "csv-core 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 61 | "serde 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", 62 | ] 63 | 64 | [[package]] 65 | name = "csv-core" 66 | version = "0.1.3" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | dependencies = [ 69 | "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 70 | ] 71 | 72 | [[package]] 73 | name = "kernel32-sys" 74 | version = "0.2.2" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | dependencies = [ 77 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 78 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 79 | ] 80 | 81 | [[package]] 82 | name = "lazy_static" 83 | version = "0.2.8" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | 86 | [[package]] 87 | name = "libc" 88 | version = "0.2.30" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | 91 | [[package]] 92 | name = "memchr" 93 | version = "1.0.1" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | dependencies = [ 96 | "libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)", 97 | ] 98 | 99 | [[package]] 100 | name = "redox_syscall" 101 | version = "0.1.31" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | 104 | [[package]] 105 | name = "serde" 106 | version = "1.0.14" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | 109 | [[package]] 110 | name = "strsim" 111 | version = "0.6.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | 114 | [[package]] 115 | name = "term_size" 116 | version = "0.3.0" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | dependencies = [ 119 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 120 | "libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)", 121 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 122 | ] 123 | 124 | [[package]] 125 | name = "textwrap" 126 | version = "0.8.0" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | dependencies = [ 129 | "term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 130 | "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 131 | ] 132 | 133 | [[package]] 134 | name = "time" 135 | version = "0.1.38" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | dependencies = [ 138 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 139 | "libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)", 140 | "redox_syscall 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)", 141 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 142 | ] 143 | 144 | [[package]] 145 | name = "unicode-width" 146 | version = "0.1.4" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | 149 | [[package]] 150 | name = "vec_map" 151 | version = "0.8.0" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | 154 | [[package]] 155 | name = "winapi" 156 | version = "0.2.8" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | 159 | [[package]] 160 | name = "winapi-build" 161 | version = "0.1.1" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | 164 | [metadata] 165 | "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" 166 | "checksum atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d912da0db7fa85514874458ca3651fe2cddace8d0b0505571dbdcd41ab490159" 167 | "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" 168 | "checksum clap 2.26.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3451e409013178663435d6f15fdb212f14ee4424a3d74f979d081d0a66b6f1f2" 169 | "checksum colored 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9d95596bdc53fde0e14c7dca1113a7784fcb2a9247676fb7415ee889b20de38a" 170 | "checksum csv 1.0.0-beta.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a841f3bc2c613b7a3e892c9c4404044de9dab6bc506e253bf8ef05ef906531e5" 171 | "checksum csv-core 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ae1fbabf21d9a52d04675cc5b032d7bae24ecdcd22646f7eefcd0496a122686c" 172 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 173 | "checksum lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3b37545ab726dd833ec6420aaba8231c5b320814b9029ad585555d2a03e94fbf" 174 | "checksum libc 0.2.30 (registry+https://github.com/rust-lang/crates.io-index)" = "2370ca07ec338939e356443dac2296f581453c35fe1e3a3ed06023c49435f915" 175 | "checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4" 176 | "checksum redox_syscall 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "8dde11f18c108289bef24469638a04dce49da56084f2d50618b226e47eb04509" 177 | "checksum serde 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "bcb6a7637a47663ee073391a139ed07851f27ed2532c2abc88c6bf27a16cdf34" 178 | "checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" 179 | "checksum term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b6b55df3198cc93372e85dd2ed817f0e38ce8cc0f22eb32391bfad9c4bf209" 180 | "checksum textwrap 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df8e08afc40ae3459e4838f303e465aa50d823df8d7f83ca88108f6d3afe7edd" 181 | "checksum time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d788d3aa77bc0ef3e9621256885555368b47bd495c13dd2e7413c89f845520" 182 | "checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" 183 | "checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c" 184 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 185 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 186 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tally" 3 | version = "0.4.3" 4 | 5 | description = "A prettier version of the time command" 6 | readme = "README.md" 7 | 8 | authors = ["Jon Gjengset "] 9 | 10 | homepage = "https://github.com/jonhoo/tally" 11 | repository = "https://github.com/jonhoo/tally.git" 12 | 13 | keywords = ["cli", "system", "utilities"] 14 | categories = ["command-line-utilities"] 15 | 16 | license = "MIT/Apache-2.0" 17 | 18 | [badges] 19 | maintenance = { status = "as-is" } 20 | 21 | [dependencies] 22 | csv = "1.0.0-beta.4" 23 | colored = "1.5.2" 24 | clap = "2.26.2" 25 | libc = "0.2" 26 | time = "0.1" 27 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Jon Gjengset 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A prettier version of the well-known `time` command. 2 | 3 | Running with `-p` gives POSIX-compatible output, and `-g` gives GNU time 4 | compatible output. Running without flags gives the pretty new output 5 | format. You can also use `-d ` to produce machine-readable 6 | CSV-like output. 7 | 8 | Here's what it looks like: 9 | 10 | ![screenshot](screenshot.png?raw=true) 11 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonhoo/tally/f9b9e8e0a9e386fcc123c4f500ae5310f80573b1/screenshot.png -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate clap; 3 | extern crate colored; 4 | extern crate csv; 5 | extern crate libc; 6 | extern crate time; 7 | 8 | use clap::{App, AppSettings, Arg}; 9 | use libc::{c_long, getrusage, rusage, suseconds_t, time_t, timeval, RUSAGE_CHILDREN}; 10 | use std::process::Command; 11 | use std::process; 12 | 13 | fn main() { 14 | let mut app = App::new("tally") 15 | .version(crate_version!()) 16 | .about("prettier subsitute for time") 17 | .help_message("Prints help information. Use --help for more details.") 18 | .long_about( 19 | "\ 20 | tally runs the specified program `command` with the given arguments. \ 21 | When `command` finishes, time writes a message to standard error giving timing \ 22 | statistics about this program run. These statistics include (i) the elapsed real \ 23 | time between invocation and termination, (ii) the user and system CPU time as \ 24 | returned by getrusage(2), and (iii) other runtime statistics such as peak \ 25 | resident memory usage, number of page faults, etc.", 26 | ) 27 | .setting(AppSettings::AllowExternalSubcommands) 28 | .arg( 29 | Arg::with_name("posix") 30 | .short("p") 31 | .long("portability") 32 | .help("Use the portable output format.") 33 | .long_help( 34 | "\ 35 | When in the POSIX locale, use the precise traditional format 36 | 37 | \"real %f\\nuser %f\\nsys %f\\n\" 38 | 39 | (with numbers in seconds) where the number of decimals in the 40 | output for %f is unspecified but is sufficient to express the 41 | clock tick accuracy, and at least one.", 42 | ), 43 | ) 44 | .arg( 45 | Arg::with_name("gnu") 46 | .short("g") 47 | .long("gnu") 48 | .help("Use the GNU time output format.") 49 | .long_help( 50 | "\ 51 | Use the precise output format produced by GNU time: 52 | 53 | %Uuser %Ssystem %Eelapsed %PCPU (%Xtext+%Ddata %Mmax)k 54 | %Iinputs+%Ooutputs (%Fmajor+%Rminor)pagefaults %Wswaps 55 | 56 | Some of these fields are deprecated (%X, %D, and %W), 57 | and will always be 0.", 58 | ), 59 | ) 60 | .arg( 61 | Arg::with_name("delimited") 62 | .short("d") 63 | .long("delimited") 64 | .takes_value(true) 65 | .require_equals(true) 66 | .min_values(0) 67 | .default_value(",") 68 | .help( 69 | "Output data in delimited format (CSV with custom delimiter).", 70 | ) 71 | .next_line_help(true) 72 | .validator(|v| { 73 | use std::ascii::AsciiExt; 74 | let mut chars = v.chars(); 75 | let first = chars.next(); 76 | if first.is_none() { 77 | return Err(String::from("no delimiter given")); 78 | } 79 | if chars.next().is_some() { 80 | return Err(String::from( 81 | "only single-character delimiters are supported", 82 | )); 83 | } 84 | let first = first.unwrap(); 85 | if !first.is_ascii() { 86 | return Err(String::from("only ASCII delimiters are supported")); 87 | } 88 | Ok(()) 89 | }) 90 | .long_help( 91 | "\ 92 | Outputs timing informating in a machine-readable delimited format. 93 | Each row has a single metric with two columns: field name and 94 | value. The metrics are: 95 | 96 | user: user time (in nanoseconds) 97 | system: system time (in nanoseconds) 98 | real: elapsed wall clock time (in nanoseconds) 99 | peak_mem: max resident memory (in kbytes) 100 | major_faults: major page faults 101 | minor_faults: minor page faults", 102 | ), 103 | ) 104 | .usage("tally time [options] command [arguments]...") 105 | .after_help( 106 | "ARGS: 107 | command the command to tally 108 | arguments any arguments to pass to ", 109 | ); 110 | let matches = app.clone().get_matches(); 111 | 112 | let mut usage = rusage { 113 | ru_utime: timeval { 114 | tv_sec: 0 as time_t, 115 | tv_usec: 0 as suseconds_t, 116 | }, 117 | ru_stime: timeval { 118 | tv_sec: 0 as time_t, 119 | tv_usec: 0 as suseconds_t, 120 | }, 121 | ru_maxrss: 0 as c_long, 122 | ru_ixrss: 0 as c_long, 123 | ru_idrss: 0 as c_long, 124 | ru_isrss: 0 as c_long, 125 | ru_minflt: 0 as c_long, 126 | ru_majflt: 0 as c_long, 127 | ru_nswap: 0 as c_long, 128 | ru_inblock: 0 as c_long, 129 | ru_oublock: 0 as c_long, 130 | ru_msgsnd: 0 as c_long, 131 | ru_msgrcv: 0 as c_long, 132 | ru_nsignals: 0 as c_long, 133 | ru_nvcsw: 0 as c_long, 134 | ru_nivcsw: 0 as c_long, 135 | }; 136 | 137 | let (cmd, cmd_args) = matches.subcommand(); 138 | if cmd.is_empty() { 139 | app.print_long_help().unwrap(); 140 | process::exit(127); 141 | } 142 | 143 | let mut command = Command::new(cmd); 144 | if let Some(args) = cmd_args.unwrap().values_of("") { 145 | command.args(args); 146 | } 147 | 148 | let mut child = match command.spawn() { 149 | Ok(child) => child, 150 | Err(e) => { 151 | use std::io::ErrorKind; 152 | match e.kind() { 153 | ErrorKind::NotFound => { 154 | process::exit(127); 155 | } 156 | ErrorKind::PermissionDenied => { 157 | process::exit(126); 158 | } 159 | _ => {} 160 | } 161 | match e.raw_os_error() { 162 | Some(e) if e > 0 && e <= 125 => { 163 | process::exit(125); 164 | } 165 | _ => process::exit(1), 166 | } 167 | } 168 | }; 169 | 170 | let start = time::PreciseTime::now(); 171 | let exit = child.wait(); 172 | let end = time::PreciseTime::now(); 173 | let exit = match exit { 174 | Ok(exit) => { 175 | match exit.code() { 176 | Some(exit) => exit, 177 | None => { 178 | // signal 179 | 1 180 | } 181 | } 182 | } 183 | Err(_) => 1, 184 | }; 185 | 186 | match unsafe { getrusage(RUSAGE_CHILDREN, (&mut usage) as *mut rusage) } { 187 | 0 => {} 188 | _ => process::exit(exit), 189 | } 190 | 191 | let real_time = start.to(end); 192 | let ns: u64 = if let Some(ns) = real_time.num_nanoseconds() { 193 | ns as u64 - real_time.num_seconds() as u64 * 1_000_000_000 194 | } else if let Some(us) = real_time.num_microseconds() { 195 | us as u64 - real_time.num_seconds() as u64 * 1_000_000 196 | } else { 197 | let ms = real_time.num_milliseconds(); 198 | ms as u64 - real_time.num_seconds() as u64 * 1_000 199 | }; 200 | 201 | let utime_ns = 202 | usage.ru_utime.tv_sec as u64 * 1_000_000_000 + usage.ru_utime.tv_usec as u64 * 1_000; 203 | let stime_ns = 204 | usage.ru_stime.tv_sec as u64 * 1_000_000_000 + usage.ru_stime.tv_usec as u64 * 1_000; 205 | let rtime_ns = real_time.num_seconds() as u64 * 1_000_000_000 + ns; 206 | let ns_to_ms_frac = |ns: u64| { 207 | format!( 208 | "{}.{:03}", 209 | ns / 1_000_000_000, 210 | (ns % 1_000_000_000) / 1_000_000 211 | ) 212 | }; 213 | 214 | if matches.is_present("posix") { 215 | eprintln!( 216 | "real {}\nuser {}\nsys {}", 217 | ns_to_ms_frac(rtime_ns), 218 | ns_to_ms_frac(utime_ns), 219 | ns_to_ms_frac(stime_ns), 220 | ); 221 | process::exit(exit); 222 | } else if matches.is_present("gnu") { 223 | let mut pretty_time = String::new(); 224 | let mut t = real_time.num_seconds(); 225 | if t / 3600 > 0 { 226 | pretty_time.push_str(&format!("{}:", t / 3600)); 227 | } 228 | t = t % 3600; 229 | pretty_time.push_str(&format!("{}:", t / 60)); 230 | t = t % 60; 231 | pretty_time.push_str(&format!("{:02}", t)); 232 | pretty_time.push_str(&format!(".{:03}", (rtime_ns % 1_000_000_000) / 1_000_000)); 233 | eprintln!( 234 | "\ 235 | {}user {}system {}elapsed {:.1}%CPU ({}text+{}data {}max)k\n\ 236 | {}inputs+{}outputs ({}major+{}minor)pagefaults {}swaps", 237 | ns_to_ms_frac(utime_ns), 238 | ns_to_ms_frac(stime_ns), 239 | pretty_time, 240 | (utime_ns + stime_ns) as f64 / rtime_ns as f64, 241 | 0, // deprecated 242 | 0, // deprecated 243 | usage.ru_maxrss, 244 | usage.ru_inblock, 245 | usage.ru_oublock, 246 | usage.ru_majflt, 247 | usage.ru_minflt, 248 | 0, // deprecated 249 | ); 250 | process::exit(exit); 251 | } else if matches.occurrences_of("delimited") != 0 { 252 | let d = matches.value_of("delimited").unwrap(); 253 | use std::io; 254 | 255 | let mut w = csv::WriterBuilder::new(); 256 | // we know there's only one character due to the validator 257 | let delim = d.chars().next().unwrap(); 258 | // we know there's exactly one ascii character 259 | let mut b = [0; 1]; 260 | delim.encode_utf8(&mut b); 261 | w.delimiter(b[0]); 262 | // write all the stuff to stdout 263 | let stderr = io::stderr(); 264 | let handle = stderr.lock(); 265 | let mut wrt = w.from_writer(handle); 266 | wrt.write_field(b"user").unwrap(); 267 | wrt.write_record(&[format!("{}", utime_ns)]).unwrap(); 268 | wrt.write_field(b"system").unwrap(); 269 | wrt.write_record(&[format!("{}", stime_ns)]).unwrap(); 270 | wrt.write_field(b"real").unwrap(); 271 | wrt.write_record(&[format!("{}", rtime_ns)]).unwrap(); 272 | wrt.write_field(b"peak_mem").unwrap(); 273 | wrt.write_record(&[format!("{}", usage.ru_maxrss)]).unwrap(); 274 | wrt.write_field(b"major_faults").unwrap(); 275 | wrt.write_record(&[format!("{}", usage.ru_majflt)]).unwrap(); 276 | wrt.write_field(b"minor_faults").unwrap(); 277 | wrt.write_record(&[format!("{}", usage.ru_minflt)]).unwrap(); 278 | drop(wrt); 279 | process::exit(exit); 280 | } 281 | 282 | use colored::Colorize; 283 | let unit = |v, u: &str| format!("{}{}", v, u.white().dimmed()); 284 | 285 | // we want to show the same units on every row 286 | let has_h = 287 | real_time.num_hours() > 0 || usage.ru_utime.tv_sec > 3600 || usage.ru_stime.tv_sec > 3600; 288 | let has_m = has_h || real_time.num_minutes() > 0 || usage.ru_utime.tv_sec > 60 || 289 | usage.ru_stime.tv_sec > 60; 290 | let has_usec = usage.ru_utime.tv_usec % 1_000 > 0 || usage.ru_stime.tv_usec % 1_000 > 0; 291 | let has_msec = has_usec || usage.ru_utime.tv_usec > 1_000 || usage.ru_stime.tv_usec > 1_000; 292 | 293 | let pretty_seconds = |mut s| { 294 | let mut pretty_time = String::new(); 295 | if has_h { 296 | pretty_time.push_str(&unit(format!("{:>2}", s / 3600), "h ")); 297 | } 298 | s = s % 3600; 299 | if has_h || has_m { 300 | pretty_time.push_str(&unit(format!("{:>2}", s / 60), "m ")); 301 | } 302 | s = s % 60; 303 | pretty_time.push_str(&unit(format!("{:>2}", s), "s")); 304 | pretty_time 305 | }; 306 | let pretty_time = |t: &timeval| { 307 | let mut s = pretty_seconds(t.tv_sec); 308 | let mut usec = t.tv_usec; 309 | if has_msec { 310 | s.push_str(" "); 311 | s.push_str(&unit(format!("{:>3}", usec / 1_000), "ms")); 312 | } 313 | usec = usec % 1_000; 314 | if has_usec { 315 | s.push_str(" "); 316 | s.push_str(&unit(format!("{:>3}", usec), "µs")); 317 | } 318 | s 319 | }; 320 | let pretty_time2 = || { 321 | let mut s = pretty_seconds(real_time.num_seconds()); 322 | let mut ns = ns; 323 | if has_msec { 324 | s.push_str(" "); 325 | s.push_str(&unit(format!("{:>3}", ns / 1_000_000), "ms")); 326 | } 327 | if has_usec { 328 | ns = ns % 1_000_000; 329 | s.push_str(" "); 330 | s.push_str(&unit(format!("{:>3}", ns / 1_000), "µs")); 331 | } 332 | ns = ns % 1_000; 333 | if ns != 0 { 334 | s.push_str(" "); 335 | s.push_str(&unit(format!("{:>3}", ns), "ns")); 336 | } 337 | s 338 | }; 339 | let pretty_mem = |ks| if ks > 10 * 1024 * 1024 { 340 | unit(format!("{:.0} ", ks as f64 / 1024f64 / 1024f64), "GB") 341 | } else if ks > 1024 * 1024 { 342 | unit(format!("{:.1} ", ks as f64 / 1024f64 / 1024f64), "GB") 343 | } else if ks > 10 * 1024 { 344 | unit(format!("{:.0} ", ks as f64 / 1024f64), "MB") 345 | } else if ks > 1024 { 346 | unit(format!("{:.1} ", ks as f64 / 1024f64), "MB") 347 | } else { 348 | unit(format!("{} ", ks), "kB") 349 | }; 350 | 351 | // now for our new and pretty output format 352 | eprintln!( 353 | "\ 354 | {}\n\ 355 | \n\ 356 | {} {}\n\ 357 | {} {}\n\ 358 | {} {}\n\n\ 359 | {} {}\n\ 360 | {} {}, {}\n\ 361 | \n{}", 362 | format!("{:-^45}", " [stats] ").white().dimmed(), 363 | format!("{:>15}", "user time:").yellow(), 364 | pretty_time(&usage.ru_utime), 365 | format!("{:>15}", "system time:").yellow(), 366 | pretty_time(&usage.ru_stime), 367 | format!("{:>15}", "real time:").yellow(), 368 | pretty_time2(), 369 | format!("{:>15}", "max memory:").yellow(), 370 | pretty_mem(usage.ru_maxrss), 371 | format!("{:>15}", "page faults:").yellow(), 372 | unit(format!("{}", usage.ru_majflt), "major"), 373 | unit(format!("{}", usage.ru_minflt), "minor"), 374 | format!("{:-^45}", "").white().dimmed(), 375 | ); 376 | process::exit(exit); 377 | } 378 | --------------------------------------------------------------------------------