├── .editorconfig ├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── appveyor.yml ├── examples ├── acme.rs ├── growth.rs ├── histogram.rs ├── http_download.rs ├── http_server.rs ├── iron_middleware.rs └── physical.rs ├── src ├── bin │ └── mmvdump.rs ├── client │ ├── metric │ │ ├── counter.rs │ │ ├── countvector.rs │ │ ├── gauge.rs │ │ ├── gaugevector.rs │ │ ├── histogram.rs │ │ ├── mod.rs │ │ └── timer.rs │ └── mod.rs ├── lib.rs ├── mmv │ ├── mmvfmt.rs │ └── mod.rs └── private.rs └── tests ├── data ├── mmvdump_ip1.mmv ├── mmvdump_ip2.mmv ├── mmvdump_ip3.mmv ├── mmvdump_ip4.mmv ├── mmvdump_ip5.mmv ├── mmvdump_ip6.mmv ├── mmvdump_op1.golden ├── mmvdump_op2.golden ├── mmvdump_op3.golden ├── mmvdump_op4.golden ├── mmvdump_op5.golden └── mmvdump_op6.golden └── mmvfmt.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | 8 | [*.{rs, toml, yml}] 9 | indent_style = space 10 | 11 | [*.{rs, toml}] 12 | indent_size = 4 13 | 14 | [*.yml] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | 7 | cache: cargo 8 | 9 | os: 10 | - linux 11 | - osx 12 | 13 | sudo: false 14 | 15 | env: 16 | global: 17 | - RUSTFLAGS="-C link-dead-code" 18 | 19 | addons: 20 | apt: 21 | packages: 22 | - libcurl4-openssl-dev 23 | - libelf-dev 24 | - libdw-dev 25 | - cmake 26 | - gcc 27 | - binutils-dev 28 | - libiberty-dev 29 | 30 | after_success: | 31 | wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz && 32 | tar xzf master.tar.gz && 33 | cd kcov-master && 34 | mkdir build && 35 | cd build && 36 | cmake .. && 37 | make && 38 | make install DESTDIR=../../kcov-build && 39 | cd ../.. && 40 | rm -rf kcov-master && 41 | for file in target/debug/hornet-*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done && 42 | bash <(curl -s https://codecov.io/bash) && 43 | echo "Uploaded code coverage" 44 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hornet" 3 | version = "0.1.0" 4 | authors = ["Saurav Sachidanand "] 5 | license = "MIT OR Apache-2.0" 6 | description = "A Rust implementation of the PCP instrumentation API" 7 | repository = "https://github.com/performancecopilot/hornet" 8 | readme="README.md" 9 | keywords = ["performance", "instrumentation", "metric", "pcp", "mmv"] 10 | 11 | [dependencies] 12 | bitflags = "0.9.1" 13 | byteorder = "1.0.0" 14 | hdrsample = "4.0.0" 15 | lazy_static = "0.2.8" 16 | memmap = "0.5.2" 17 | regex = "0.2" 18 | time = "0.1" 19 | 20 | [dev-dependencies] 21 | rand = "0.3.15" 22 | hyper = "0.11.2" 23 | futures = "0.1.14" 24 | curl = "0.4.8" 25 | iron = "0.5.1" 26 | 27 | [target.'cfg(unix)'.dependencies] 28 | nix = "0.8.0" 29 | 30 | [target.'cfg(windows)'.dependencies] 31 | kernel32-sys = "0.2.2" 32 | -------------------------------------------------------------------------------- /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 | Copyright (c) 2017 Saurav Sachidanand 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hornet [![crates.io badge](https://img.shields.io/crates/v/hornet.svg)](https://crates.io/crates/hornet) [![docs.rs badge](https://docs.rs/hornet/badge.svg)](https://docs.rs/hornet/0.1.0/hornet/) [![Travis CI Build Status](https://travis-ci.org/performancecopilot/hornet.svg?branch=master)](https://travis-ci.org/performancecopilot/hornet) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/ccvbo3chne8046vn/branch/master?svg=true)](https://ci.appveyor.com/project/saurvs/hornet-2qtki/branch/master) [![codecov](https://codecov.io/gh/performancecopilot/hornet/branch/master/graph/badge.svg)](https://codecov.io/gh/performancecopilot/hornet) 2 | 3 | `hornet` is a Performance Co-Pilot (PCP) Memory Mapped Values (MMV) instrumentation library written in Rust. 4 | 5 | **Contents** 6 | 7 | * [What is PCP MMV instrumentation?](#what-is-pcp-mmv-instrumentation) 8 | * [Usage](#usage) 9 | * [API](#api) 10 | * [Singleton Metric](#singleton-metric) 11 | * [Instance Metric](#instance-metric) 12 | * [Special Metrics](#special-metrics) 13 | * [Client](#client) 14 | * [Monitoring metrics](#monitoring-metrics) 15 | * [License](#license) 16 | 17 | ## What is PCP MMV instrumentation? 18 | 19 | [Performance Co-Pilot](http://pcp.io/) is a systems performance analysis framework with a distributed and scalable architecture. It supports a low overhead 20 | method for instrumenting applications called Memory Mapped Values (MMV), in which instrumented processes share part of their virtual memory address space with another monitoring process through a common memory-mapped file. The shared address space contains various performance analysis metrics stored in a structured binary data format called MMV; it's formal spec can be found [here](http://pcp.io/man/man5/mmv.5.html). When processes wish to update their metrics, they simply write certain bytes to the memory mapped file, and the monitoring process reads it at appropriate times. No explicit inter-process communication, synchronization or systems calls are involved. 21 | 22 | ## Usage 23 | 24 | * Add the ```hornet``` dependency to your ```Cargo.toml``` 25 | ```toml 26 | [dependencies] 27 | hornet = "0.1.0" 28 | ``` 29 | 30 | * Include the ```hornet``` crate in your code and import the following modules 31 | ```rust 32 | extern crate hornet; 33 | 34 | use hornet::client::Client; 35 | use hornet::client::metric::*; 36 | ``` 37 | 38 | ## API 39 | 40 | There are essentially two kinds of metrics in `hornet`. 41 | 42 | ### Singleton Metric 43 | 44 | A singleton metric is a metric associated with a primitive value type, a `Unit`, a `Semantics` type, and some metadata. A primitive value can be any one of `i64`, `u64`, `i32`, `u32`, `f64`, `f32`, or `String`, 45 | 46 | The primitive value type of a metric is determined implicitly at *compile-time* by the inital primitive value passed to the metric while creating it. The programmer also needn't worry about reading or writing data of the wrong primitive type from a metric, as the Rust compiler enforces type safety for a metric's primitive value during complilation. 47 | 48 | Let's look at creating a simple `i64` metric 49 | 50 | ```rust 51 | let mut metric = Metric::new( 52 | "simple", // metric name 53 | 1, // inital value of type i64 54 | Semantics::Counter, 55 | Unit::new().count(Count::One, 1).unwrap(), // unit with a 'count' dimension of power 1 56 | "Short text", // short description 57 | "Long text", // long description 58 | ).unwrap(); 59 | ``` 60 | 61 | If we want to create an `f64` metric, we simply pass an `f64` inital value instead 62 | 63 | ```rust 64 | let mut metric = Metric::new( 65 | "simple_f64", // metric name 66 | 1.5, // inital value of type f64 67 | Semantics::Instant, 68 | Unit::new().count(Time::Sec, 1).unwrap(), // unit with a 'time' dimension of power 1 69 | "Short text", // short description 70 | "Long text", // long description 71 | ).unwrap(); 72 | ``` 73 | 74 | And similarly for a `String` metric 75 | 76 | ```rust 77 | let mut metric = Metric::new( 78 | "simple_string", // metric name 79 | "Hello, world!".to_string(), // inital value of type String 80 | Semantics::Discrete, 81 | Unit::new().unwrap(), // unit with no dimension 82 | "Short text", // short description 83 | "Long text", // long description 84 | ).unwrap(); 85 | ``` 86 | 87 | The detailed API on singleton metrics can be found [here](https://docs.rs/hornet/0.1.0/hornet/client/metric/struct.Metric.html). 88 | 89 | ### Instance Metric 90 | 91 | An instance metric is similar to a singleton metric in that it is also associated with a primitive valye type, `Unit`, and `Semantics`, 92 | but additionally also holds multiple independent primitive values of the same type. The same type inference rules also hold for instance metrics - the type 93 | of the inital value determines the type of the instance metric. 94 | 95 | Before we can create an instance metric, we need to create what's called an 96 | *instance domain*. An instance domain is a set of `String` values that act 97 | as unique identifiers for the multiple independent values of an instance metric. Why have a separate object for this purpose? So that we can reuse the same identifiers as a "domain" for several different but related instance metrics. An example will clear this up. 98 | 99 | Suppose we are modeling the fictional [Acme Corporation factory](https://en.wikipedia.org/wiki/Acme_Corporation). Let's assume we have three items that can be manufactured - Anvils, Rockets, and Giant Rubber Bands. Each item is associated with a "count" metric of how many copies have been manufactured so far, and a "time" metric of how much time has been spent manufacturing each item. We can create instance metrics like so 100 | 101 | ```rust 102 | /* instance domain */ 103 | let indom = Indom::new( 104 | &["Anvils", "Rockets", "Giant_Rubber_Bands"], 105 | "Acme products", // short description 106 | "Most popular products produced by the Acme Corporation" // long description 107 | ).unwrap(); 108 | 109 | /* two instance metrics */ 110 | 111 | let mut counts = InstanceMetric::new( 112 | &indom, 113 | "products.count", // instance metric name 114 | 0, // inital value of type i64 115 | Semantics::Counter, 116 | Unit::new().count(Count::One, 1).unwrap(), 117 | "Acme factory product throughput", 118 | "Monotonic increasing counter of products produced in the Acme Corporation factory since starting the Acme production application." 119 | ).unwrap(); 120 | 121 | let mut times = InstanceMetric::new( 122 | &indom, 123 | "products.time", // instance metric name 124 | 0.0, // inital value of type f64 125 | Semantics::Instance, 126 | Unit::new().time(Time::Sec, 1).unwrap(), 127 | "Time spent producing products", 128 | "Machine time spent producing Acme Corporation products." 129 | ).unwrap(); 130 | 131 | ``` 132 | 133 | Here, our `indom` contains three identifiers - `Anvils`, `Rockets` and `Giant_Rubber_Bands`. 134 | We've created two instance metrics - `counts` of type `i64` and `times` of type `f64` with relevant units and semantics. 135 | 136 | The detailed API on instance metrics can be found [here](https://docs.rs/hornet/0.1.0/hornet/client/metric/struct.InstanceMetric.html). 137 | 138 | ### Updating metrics 139 | 140 | So far we've seen how to create metrics with various attributes. Updating their primitive values is pretty simple. 141 | 142 | For singleton metrics, the `val(&self) -> &T` method returns a reference to the underlying value, and the 143 | `set_val(&mut self, new_val: T) -> io::Result<()>` method updates the underlying value and 144 | writes to the memory mapped file. The arguments and return values for these methods 145 | are generic over the different primitive types for a metric, and hence are completely type safe. 146 | 147 | For instance metrics, the `val(&self, instance: &str) -> Option<&T>` method returns a reference to the primitive value for the given instance identifier, if it exists. The 148 | `set_val(&mut self, instance: &str, new_val: T) -> Option>` method updates the primitive value for the given instance identifier, if it exists. These methods are similarly 149 | generic over primitive value types. 150 | 151 | ## Special metrics 152 | 153 | Singleton metrics and instance metrics are powerful and general enough to be used for a wide variety of performance analysis needs. However, for many common applications, simpler metric interfaces would be more appropriate and easy to use. Hence `hornet` includes 6 high-level metrics that are built on top of singleton and instance metrics, and they offer a more specialized and simpler API. 154 | 155 | #### Counter 156 | 157 | A `Counter` is a singleton metric of type `u64`, `Counter` semantics, and unit of 1 count dimension. It implements the following methods: `up` to increment by one, `inc` to increment 158 | by a delta, `reset` to set count to the inital count, and `val` to return the current count. 159 | 160 | ```rust 161 | let mut c = Counter::new( 162 | "counter", // name 163 | 1, // inital value 164 | "", "" // short and long description strings 165 | ).unwrap(); 166 | 167 | c.up(); // 2 168 | c.inc(3); // 5 169 | c.reset(); // 1 170 | 171 | let count = c.val(); // 1 172 | ``` 173 | 174 | The [CountVector](https://docs.rs/hornet/0.1.0/hornet/client/metric/struct.CountVector.html) is the instance metric version of the `Counter`. It holds multiple counts each associated with a `String` identifier. 175 | 176 | #### Gauge 177 | 178 | A `Gauge` is a singleton metric of type `f64`, `Instant` semantics, and unit of 1 count dimension. It implements the following methods: `inc` to increment the gauge by a delta, `dec` to decrement the gauge by a delta, `set` to set the gauge to an arbritrary value, and `val` which returns the current value of the gauge. 179 | 180 | ```rust 181 | let mut gauge = Gauge::new("gauge", 1.5, "", "").unwrap(); 182 | 183 | gauge.set(3.0).unwrap(); // 3.0 184 | gauge.inc(3.0).unwrap(); // 6.0 185 | gauge.dec(1.5).unwrap(); // 4.5 186 | gauge.reset().unwrap(); // 1.5 187 | ``` 188 | 189 | The [GaugeVector](https://docs.rs/hornet/0.1.0/hornet/client/metric/struct.GaugeVector.html) is the instance metric version of the `Gauge`. It holds multiple gauge values each associated with an identifier. 190 | 191 | #### Timer 192 | 193 | A `Timer` is a singleton metric of type `i64`, `Instant` semantics, and a user specified time unit. It implements the following methods: `start` starts the timer by recording the current time, `stop` stops the timer by recording the current time 194 | and returns the elapsed time since the last `start`, and `elapsed` returns the 195 | total time elapsed so far between all start and stop pairs. 196 | 197 | ```rust 198 | let mut timer = Timer::new("timer", Time::MSec, "", "").unwrap(); 199 | 200 | timer.start().unwrap(); 201 | let e1 = timer.stop().unwrap(); 202 | 203 | timer.start().unwrap(); 204 | let e2 = timer.stop().unwrap(); 205 | 206 | let elapsed = timer.elapsed(); // = e1 + e2 207 | ``` 208 | 209 | #### Histogram 210 | 211 | A `Histogram` is a high dynamic range (HDR) histogram metric which records `u64` data points and exports various statistics about the data. It is implemented using an instance metric of `f64` type and `Instance` semantics. The `Histogram` metric is infact essentially a wrapper around the `Histogram` object from the [hdrsample](https://github.com/jonhoo/hdrsample) crate, and it exports the maximum, minimum, mean and standard deviation statistics to the MMV file. 212 | 213 | ```rust 214 | let low = 1; 215 | let high = 100; 216 | let sigfig = 5; 217 | 218 | let mut hist = Histogram::new( 219 | "histogram", 220 | low, 221 | high, 222 | sigfig, 223 | Unit::new().count(Count::One, 1).unwrap(), 224 | "Simple histogram example", "" 225 | ).unwrap(); 226 | 227 | let range = Range::new(low, high); 228 | let mut thread_rng = thread_rng(); 229 | 230 | for _ in 0..100 { 231 | hist.record(range.ind_sample(&mut thread_rng)).unwrap(); 232 | } 233 | ``` 234 | 235 | Much of the `Histogram` [API](https://docs.rs/hornet/0.1.0/hornet/client/metric/struct.Histogram.html) is largely similar to the [hdrsample API](https://docs.rs/hdrsample/6.0.1/hdrsample/struct.Histogram.html). 236 | 237 | ### Client 238 | 239 | In order to export our metrics to a memory mapped file, we must first create a `Client` 240 | 241 | ```rust 242 | let client = Client::new("client").unwrap(); // MMV file will be named 'client' 243 | ``` 244 | 245 | Now to export metrics, we simply call `export` 246 | 247 | ```rust 248 | client.export(&mut [&mut metric1, &mut metric2, &mut metric3]); 249 | ``` 250 | 251 | If you have a valid PCP installation, the `Client` writes the MMV file to `$PCP_TMP_DIR/mmv/`, and otherwise it writes it to `/tmp/mmv/`. 252 | 253 | After metrics are exported through a `Client`, all updates to their primitive values will show up in the MMV file. 254 | 255 | ### Monitoring metrics 256 | 257 | With a valid PCP installation on a machine, metrics can be monitored externally by using the follwing command 258 | ```bash 259 | $ pminfo -f mmv._name_ 260 | ``` 261 | where `_name_` is the name passed to `Client` while creating it. 262 | 263 | Another way to inspect metrics externally is to dump the contents of the MMV file itself. This can be done using a command line tool called `mmvdump` included in `hornet`. After issuing `cargo build` from within the project directory, `mmvdump` can be found built under `target/debug/`. 264 | 265 | Usage of `mmvdump` is pretty straightforward 266 | 267 | ```rust 268 | $ ./mmvdump simple.mmv 269 | 270 | Version = 1 271 | Generated = 1468770536 272 | TOC count = 3 273 | Cluster = 127 274 | Process = 29956 275 | Flags = process (0x2) 276 | 277 | TOC[0]: toc offset 40, metrics offset 88 (1 entries) 278 | [725/88] simple.counter 279 | type=Int32 (0x0), sem=counter (0x1), pad=0x0 280 | unit=count (0x100000) 281 | (no indom) 282 | shorttext=A Simple Metric 283 | longtext=This is a simple counter metric to demonstrate the hornet API 284 | 285 | TOC[1]: toc offset 56, values offset 192 (1 entries) 286 | [725/192] simple.counter = 42 287 | 288 | TOC[2]: toc offset 72, strings offset 224 (2 entries) 289 | [1/224] A Simple Metric 290 | [2/480] This is a simple counter metric to demonstrate the hornet API 291 | ``` 292 | 293 | ## License 294 | 295 | Licensed under either of 296 | 297 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 298 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 299 | 300 | at your option. 301 | 302 | ### Contribution 303 | 304 | Unless you explicitly state otherwise, any contribution intentionally 305 | submitted for inclusion in the work by you, as defined in the Apache-2.0 306 | license, shall be dual licensed as above, without any additional terms or 307 | conditions. 308 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | # i686-pc-windows-gnu 4 | - TARGET: i686-pc-windows-gnu 5 | CHANNEL: stable 6 | MINGW_URL: https://s3.amazonaws.com/rust-lang-ci 7 | MINGW_ARCHIVE: i686-4.9.2-release-win32-dwarf-rt_v4-rev4.7z 8 | MINGW_DIR: mingw32 9 | - TARGET: i686-pc-windows-gnu 10 | CHANNEL: beta 11 | MINGW_URL: https://s3.amazonaws.com/rust-lang-ci 12 | MINGW_ARCHIVE: i686-4.9.2-release-win32-dwarf-rt_v4-rev4.7z 13 | MINGW_DIR: mingw32 14 | - TARGET: i686-pc-windows-gnu 15 | CHANNEL: nightly 16 | MINGW_URL: https://s3.amazonaws.com/rust-lang-ci 17 | MINGW_ARCHIVE: i686-4.9.2-release-win32-dwarf-rt_v4-rev4.7z 18 | MINGW_DIR: mingw32 19 | 20 | # x86_64-pc-windows-gnu 21 | - TARGET: x86_64-pc-windows-gnu 22 | CHANNEL: stable 23 | MSYS_BITS: 64 24 | - TARGET: x86_64-pc-windows-gnu 25 | CHANNEL: beta 26 | MSYS_BITS: 64 27 | - TARGET: x86_64-pc-windows-gnu 28 | CHANNEL: nightly 29 | MSYS_BITS: 64 30 | 31 | # i686-pc-windows-msvc 32 | - TARGET: i686-pc-windows-msvc 33 | CHANNEL: stable 34 | - TARGET: i686-pc-windows-msvc 35 | CHANNEL: beta 36 | - TARGET: i686-pc-windows-msvc 37 | CHANNEL: nightly 38 | 39 | # x86_64-pc-windows-msvc 40 | - TARGET: x86_64-pc-windows-msvc 41 | CHANNEL: stable 42 | - TARGET: x86_64-pc-windows-msvc 43 | CHANNEL: beta 44 | - TARGET: x86_64-pc-windows-msvc 45 | CHANNEL: nightly 46 | 47 | install: 48 | - appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe 49 | - rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y 50 | - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin 51 | - if defined MSYS_BITS set PATH=C:\msys64\mingw%MSYS_BITS%\bin;C:\msys64\usr\bin;%PATH% 52 | - if defined MINGW_URL appveyor DownloadFile %MINGW_URL%/%MINGW_ARCHIVE% 53 | - if defined MINGW_URL 7z x -y %MINGW_ARCHIVE% > nul 54 | - if defined MINGW_URL set PATH=C:\Python27;%CD%\%MINGW_DIR%\bin;C:\msys64\usr\bin;%PATH% 55 | - rustc -V 56 | - cargo -V 57 | 58 | build: false 59 | 60 | test_script: 61 | - cargo test --verbose 62 | 63 | cache: 64 | - target 65 | - C:\Users\appveyor\.cargo\registry 66 | 67 | matrix: 68 | allow_failures: 69 | - channel: beta 70 | - channel: nightly 71 | -------------------------------------------------------------------------------- /examples/acme.rs: -------------------------------------------------------------------------------- 1 | extern crate hornet; 2 | extern crate rand; 3 | 4 | use hornet::client::Client; 5 | use hornet::client::metric::*; 6 | use rand::random; 7 | use std::thread; 8 | use std::time::Duration; 9 | 10 | fn main() { 11 | 12 | let products = ["Anvils", "Rockets", "Giant_Rubber_Bands"]; 13 | let indom = Indom::new( 14 | &products, 15 | "Acme products", 16 | "Most popular products produced by the Acme Corporation" 17 | ).unwrap(); 18 | 19 | /* create three instance metrics */ 20 | 21 | let mut counts = InstanceMetric::new( 22 | &indom, 23 | "products.count", 24 | 0, 25 | Semantics::Counter, 26 | Unit::new().count(Count::One, 1).unwrap(), 27 | "Acme factory product throughput", 28 | "Monotonic increasing counter of products produced in the Acme Corporation\nfactory since starting the Acme production application. Quality guaranteed." 29 | ).unwrap(); 30 | 31 | let sec_unit = Unit::new().time(Time::Sec, 1).unwrap(); 32 | 33 | let mut times = InstanceMetric::new( 34 | &indom, 35 | "products.time", 36 | 0, 37 | Semantics::Counter, 38 | sec_unit, 39 | "Machine time spent producing Acme products", 40 | "Machine time spent producing Acme Corporation products. Does not include\ntime in queues waiting for production machinery." 41 | ).unwrap(); 42 | 43 | let mut queue_times = InstanceMetric::new( 44 | &indom, 45 | "products.queuetime", 46 | 0, 47 | Semantics::Counter, 48 | sec_unit, 49 | "Queued time while producing Acme products", 50 | "Time spent in the queue waiting to build Acme Corporation products,\nwhile some other Acme product was being built instead of this one." 51 | ).unwrap(); 52 | 53 | /* create a client, register the metrics with it, and export them */ 54 | 55 | Client::new("acme").unwrap() 56 | .export(&mut [&mut counts, &mut times, &mut queue_times]).unwrap(); 57 | 58 | /* update metrics */ 59 | 60 | loop { 61 | let rnd_idx = random::() % products.len(); 62 | let product = products[rnd_idx]; 63 | let working_time = random::() % 3; 64 | thread::sleep(Duration::from_secs(working_time)); 65 | 66 | let count = *counts.val(product).unwrap(); 67 | counts.set_val(product, count + 1).unwrap().unwrap(); 68 | 69 | let time = *times.val(product).unwrap(); 70 | times.set_val(product, time + 1).unwrap().unwrap(); 71 | 72 | for i in 0..products.len() { 73 | if i != rnd_idx { 74 | let queued_product = products[i]; 75 | 76 | let queue_time = *queue_times.val(queued_product).unwrap(); 77 | queue_times.set_val(queued_product, queue_time + 1).unwrap().unwrap(); 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /examples/growth.rs: -------------------------------------------------------------------------------- 1 | extern crate hornet; 2 | extern crate rand; 3 | 4 | use hornet::client::Client; 5 | use hornet::client::metric::*; 6 | use std::thread; 7 | use std::time::Duration; 8 | 9 | /* this examples demonstrates use of Counter and GaugeVector */ 10 | 11 | fn main() { 12 | 13 | let mut n = Counter::new( 14 | "n", 15 | 0, 16 | "Input to various functions", "").unwrap(); 17 | 18 | let mut f_n = GaugeVector::new( 19 | "functions", 20 | 0.0, 21 | &["log2(n)", "nlog2(n)", "n^2", "n^3", "n^4", "2^n", "10^n"], 22 | "Growth of various functions", "").unwrap(); 23 | 24 | let client = Client::new("growth").unwrap(); 25 | client.export(&mut [&mut n, &mut f_n]).unwrap(); 26 | println!("Values mapped at {}", client.mmv_path().to_str().unwrap()); 27 | 28 | for _ in 0..60 { 29 | 30 | let val = n.val() as f64; 31 | 32 | f_n.set("log2(n)", val.log2()).unwrap().unwrap(); 33 | f_n.set("nlog2(n)", val*val.log2()).unwrap().unwrap(); 34 | f_n.set("n^2", val.powi(2)).unwrap().unwrap(); 35 | f_n.set("n^3", val.powi(3)).unwrap().unwrap(); 36 | f_n.set("n^4", val.powi(4)).unwrap().unwrap(); 37 | f_n.set("2^n", val.exp2()).unwrap().unwrap(); 38 | f_n.set("10^n", 10_f64.powf(val)).unwrap().unwrap(); 39 | 40 | n.up().unwrap(); 41 | 42 | thread::sleep(Duration::from_secs(1)); 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /examples/histogram.rs: -------------------------------------------------------------------------------- 1 | extern crate hornet; 2 | extern crate rand; 3 | 4 | use hornet::client::Client; 5 | use hornet::client::metric::*; 6 | use rand::thread_rng; 7 | use rand::distributions::{IndependentSample, Range}; 8 | 9 | /* 10 | For detailed usage and behaviour of the underlying HDR histogram object, 11 | check out jonhoo's hdrsample crate at https://github.com/jonhoo/hdrsample 12 | */ 13 | 14 | fn main() { 15 | 16 | /* pick parameters for the histogram */ 17 | 18 | let low = 1; 19 | let high = 100; 20 | let significant_figures = 5; 21 | 22 | /* create a histogram metric */ 23 | 24 | let mut hist = Histogram::new( 25 | "histogram", 26 | low, 27 | high, 28 | significant_figures, 29 | Unit::new().count(Count::One, 1).unwrap(), 30 | "Simple histogram example", "" 31 | ).unwrap(); 32 | 33 | /* export it to an mmv */ 34 | 35 | let client = Client::new("histogram").unwrap(); 36 | client.export(&mut [&mut hist]).unwrap(); 37 | println!("Histogram mapped at {}", client.mmv_path().to_str().unwrap()); 38 | 39 | /* record 100 random values */ 40 | 41 | let range = Range::new(low, high); 42 | let mut thread_rng = thread_rng(); 43 | 44 | for _ in 0..100 { 45 | hist.record(range.ind_sample(&mut thread_rng)).unwrap(); 46 | } 47 | 48 | /* record a single random value 100 times */ 49 | 50 | hist.record_n(range.ind_sample(&mut thread_rng), 100).unwrap(); 51 | 52 | } 53 | -------------------------------------------------------------------------------- /examples/http_download.rs: -------------------------------------------------------------------------------- 1 | extern crate hornet; 2 | extern crate curl; 3 | 4 | use hornet::client::Client; 5 | use hornet::client::metric::*; 6 | use curl::easy::Easy; 7 | 8 | /* 9 | this example uses the Timer metric to measure time spent 10 | downloading the zipped linux kernel source using libcurl bindings 11 | */ 12 | 13 | const URL: &'static str = "https://codeload.github.com/torvalds/linux/zip/master"; 14 | 15 | fn main() { 16 | 17 | let mut timer = Timer::new( 18 | "time", 19 | Time::Sec, 20 | "Time elapsed downloading", "").unwrap(); 21 | 22 | let mut bytes = Metric::new( 23 | "bytes", 24 | 0, 25 | Semantics::Discrete, 26 | Unit::new().space(Space::Byte, 1).unwrap(), 27 | "Bytes downloaded so far", "").unwrap(); 28 | 29 | let client = Client::new("download").unwrap(); 30 | client.export(&mut [&mut timer, &mut bytes]).unwrap(); 31 | 32 | let mut easy = Easy::new(); 33 | easy.url(URL).unwrap(); 34 | 35 | easy.progress(true).unwrap(); 36 | easy.progress_function(move |_, bytes_downloaded, _, _| { 37 | timer.stop().ok(); 38 | timer.start().ok(); 39 | bytes.set_val(bytes_downloaded as u64).unwrap(); 40 | true 41 | }).unwrap(); 42 | 43 | println!("Downloading from {}", URL); 44 | println!("Progress mapped at {}", client.mmv_path().to_str().unwrap()); 45 | 46 | easy.perform().unwrap(); 47 | 48 | } 49 | -------------------------------------------------------------------------------- /examples/http_server.rs: -------------------------------------------------------------------------------- 1 | extern crate hornet; 2 | extern crate hyper; 3 | extern crate futures; 4 | 5 | use std::sync::{Mutex, Arc}; 6 | use hornet::client::Client; 7 | use hornet::client::metric::*; 8 | use futures::future::FutureResult; 9 | use hyper::header::{ContentLength, ContentType}; 10 | use hyper::{Get, StatusCode}; 11 | use hyper::server::{Http, Service, Request, Response}; 12 | 13 | /* 14 | records count of HTTP GET requests on localhost:8000 15 | 16 | this example also shows how to safely read and update 17 | a metric concurrently 18 | */ 19 | 20 | static URL: &'static str = "127.0.0.1:8000"; 21 | 22 | struct HTTPCounterService { 23 | arc: Arc> 24 | } 25 | 26 | impl Service for HTTPCounterService { 27 | type Request = Request; 28 | type Response = Response; 29 | type Error = hyper::Error; 30 | type Future = FutureResult; 31 | 32 | fn call(&self, req: Request) -> Self::Future { 33 | futures::future::ok(match (req.method(), req.path()) { 34 | (&Get, "/") => { 35 | 36 | let mut counter = self.arc.lock().unwrap(); 37 | 38 | /* increase the counter value by one */ 39 | counter.up().unwrap(); 40 | 41 | let body = format!("HTTP GET count = {}", counter.val()); 42 | Response::new() 43 | .with_header(ContentLength(body.len() as u64)) 44 | .with_header(ContentType::plaintext()) 45 | .with_body(body) 46 | 47 | }, 48 | _ => { 49 | Response::new() 50 | .with_status(StatusCode::NotFound) 51 | } 52 | }) 53 | } 54 | 55 | } 56 | 57 | fn main() { 58 | 59 | /* create a counter metric */ 60 | 61 | let mut counter = Counter::new( 62 | "get", 63 | 0, // initial value 64 | "GET request count", // short description 65 | &format!("Count of GET requests on http://{}/", URL) // long description 66 | ).unwrap(); 67 | 68 | /* export it to an mmv */ 69 | 70 | let client = Client::new("localhost.http").unwrap(); 71 | client.export(&mut [&mut counter]).unwrap(); 72 | 73 | /* 74 | since the counter could be updated concurrently, wrap it 75 | in a mutex. to have shared ownership of the mutex itself, 76 | wrap it in an atomic reference counting pointer 77 | */ 78 | 79 | let mutex = Mutex::new(counter); 80 | let arc = Arc::new(mutex); 81 | 82 | /* create and run the server */ 83 | 84 | let addr = URL.parse().unwrap(); 85 | let server = Http::new().bind(&addr, move || { 86 | Ok(HTTPCounterService { 87 | arc: arc.clone() 88 | }) 89 | }).unwrap(); 90 | 91 | println!("Listening on http://{}", server.local_addr().unwrap()); 92 | println!("Counter mapped at {}", client.mmv_path().to_str().unwrap()); 93 | 94 | server.run().unwrap(); 95 | 96 | } 97 | -------------------------------------------------------------------------------- /examples/iron_middleware.rs: -------------------------------------------------------------------------------- 1 | extern crate iron; 2 | extern crate hornet; 3 | 4 | use std::sync::Mutex; 5 | use hornet::client::Client; 6 | use hornet::client::metric::*; 7 | use iron::prelude::*; 8 | use iron::middleware::BeforeMiddleware; 9 | use iron::method::Method; 10 | use iron::status; 11 | 12 | /* 13 | this examples demonstrates usage of CountVector metric 14 | embedded in Iron BeforeMiddleware 15 | */ 16 | 17 | static URL: &'static str = "127.0.0.1:8000"; 18 | 19 | fn method_str(method: &Method) -> String { 20 | format!("{}", method) 21 | } 22 | 23 | struct MethodCounter { 24 | pub metric: Mutex 25 | } 26 | 27 | impl MethodCounter { 28 | fn new() -> Self { 29 | let metric = CountVector::new( 30 | "methods_count", 31 | 0, 32 | &[ 33 | &method_str(&Method::Options), 34 | &method_str(&Method::Get), 35 | &method_str(&Method::Post), 36 | &method_str(&Method::Put), 37 | &method_str(&Method::Delete), 38 | &method_str(&Method::Head), 39 | &method_str(&Method::Trace), 40 | &method_str(&Method::Connect) 41 | ], 42 | "Counts of recieved HTTP request methods", "").unwrap(); 43 | 44 | MethodCounter { 45 | metric: Mutex::new(metric) 46 | } 47 | } 48 | } 49 | 50 | impl BeforeMiddleware for MethodCounter { 51 | fn before(&self, req: &mut Request) -> IronResult<()> { 52 | match &req.method { 53 | &Method::Extension(_) => {}, 54 | _ => { 55 | let mut counter = self.metric.lock().unwrap(); 56 | counter.up(&method_str(&req.method)).unwrap().unwrap(); 57 | } 58 | } 59 | Ok(()) 60 | } 61 | 62 | fn catch(&self, _: &mut Request, _: IronError) -> IronResult<()> { 63 | Ok(()) 64 | } 65 | } 66 | 67 | fn main() { 68 | let method_counter = MethodCounter::new(); 69 | 70 | let client = Client::new("localhost.methods").unwrap(); 71 | { 72 | let mut metric = method_counter.metric.lock().unwrap(); 73 | client.export(&mut [&mut *metric]).unwrap(); 74 | } 75 | 76 | let mut chain = Chain::new(|_: &mut Request| { 77 | Ok(Response::with((status::Ok, "Hello World!"))) 78 | }); 79 | chain.link_before(method_counter); 80 | 81 | println!("Listening on http://{}", URL); 82 | println!("Counter mapped at {}", client.mmv_path().to_str().unwrap()); 83 | 84 | Iron::new(chain).http(URL).unwrap(); 85 | } 86 | -------------------------------------------------------------------------------- /examples/physical.rs: -------------------------------------------------------------------------------- 1 | extern crate hornet; 2 | extern crate rand; 3 | 4 | use hornet::client::Client; 5 | use hornet::client::metric::*; 6 | use rand::{thread_rng, Rng}; 7 | use std::thread; 8 | use std::time::Duration; 9 | 10 | fn main() { 11 | 12 | /* create three singleton metrics */ 13 | 14 | let mut color = Metric::new( 15 | "color", 16 | String::from("cyan"), 17 | Semantics::Discrete, 18 | Unit::new(), 19 | "Color", 20 | "", 21 | ).unwrap(); 22 | 23 | let hz = Unit::new().time(Time::Sec, -1).unwrap(); 24 | let mut freq = Metric::new( 25 | "frequency", // name (max 63 bytes) 26 | thread_rng().gen::(), // initial value 27 | Semantics::Instant, // semantics 28 | hz, // unit 29 | "", // optional short description (max 255 bytes) 30 | "", // optional long description (max 255 bytes) 31 | ).unwrap(); 32 | 33 | let mut photons = Metric::new( 34 | "photons", 35 | thread_rng().gen::(), 36 | Semantics::Counter, 37 | Unit::new().count(Count::One, 1).unwrap(), 38 | "No. of photons", 39 | "Number of photons emitted by source", 40 | ).unwrap(); 41 | 42 | /* create a client, register the metrics with it, and export them */ 43 | 44 | Client::new("physical_metrics").unwrap() 45 | .export(&mut [&mut freq, &mut color, &mut photons]).unwrap(); 46 | 47 | /* update metric values */ 48 | 49 | color.set_val(String::from("magenta")).unwrap(); 50 | 51 | loop { 52 | freq.set_val(thread_rng().gen::()).unwrap(); 53 | photons.set_val(thread_rng().gen::()).unwrap(); 54 | 55 | thread::sleep(Duration::from_secs(1)); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/bin/mmvdump.rs: -------------------------------------------------------------------------------- 1 | extern crate hornet; 2 | 3 | use hornet::mmv; 4 | use std::env; 5 | use std::path::Path; 6 | 7 | fn main() { 8 | let path_arg = env::args().nth(1) 9 | .expect("Specify path to mmv file"); 10 | let mmv_path = Path::new(&path_arg); 11 | 12 | print!("{}", mmv::dump(&mmv_path).unwrap()); 13 | } 14 | -------------------------------------------------------------------------------- /src/client/metric/counter.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// A counter metric for a strictly increasing integer value, in 4 | /// possibly varying increments 5 | /// 6 | /// Internally uses a `Metric` with `Semantics::Counter` and 7 | /// `Count::One` scale, and `1` count dimension 8 | pub struct Counter { 9 | metric: Metric, 10 | init_val: u64 11 | } 12 | 13 | impl Counter { 14 | /// Creates a new counter metric with given initial value 15 | pub fn new(name: &str, init_val: u64, shorthelp_text: &str, longhelp_text: &str) -> Result { 16 | let metric = Metric::new( 17 | name, 18 | init_val, 19 | Semantics::Counter, 20 | Unit::new().count(Count::One, 1)?, 21 | shorthelp_text, 22 | longhelp_text 23 | )?; 24 | 25 | Ok(Counter { 26 | metric: metric, 27 | init_val: init_val 28 | }) 29 | } 30 | 31 | /// Returns the current value of the counter 32 | pub fn val(&self) -> u64 { 33 | *self.metric.val() 34 | } 35 | 36 | /// Increments the counter by the given value 37 | pub fn inc(&mut self, increment: u64) -> io::Result<()> { 38 | let val = *self.metric.val(); 39 | self.metric.set_val(val + increment) 40 | } 41 | 42 | /// Increments the counter by `+1` 43 | pub fn up(&mut self) -> io::Result<()> { 44 | self.inc(1) 45 | } 46 | 47 | /// Resets the counter to the initial value that was passed when 48 | /// creating it 49 | pub fn reset(&mut self) -> io::Result<()> { 50 | self.metric.set_val(self.init_val) 51 | } 52 | } 53 | 54 | impl MMVWriter for Counter { 55 | private_impl!{} 56 | 57 | fn write(&mut self, ws: &mut MMVWriterState, c: &mut Cursor<&mut [u8]>, mmv_ver: Version) -> io::Result<()> { 58 | self.metric.write(ws, c, mmv_ver) 59 | } 60 | 61 | fn register(&self, ws: &mut MMVWriterState, mmv_ver: Version) { 62 | self.metric.register(ws, mmv_ver) 63 | } 64 | 65 | fn has_mmv2_string(&self) -> bool { 66 | self.metric.has_mmv2_string() 67 | } 68 | } 69 | 70 | #[test] 71 | pub fn test() { 72 | use super::super::Client; 73 | 74 | let mut counter = Counter::new("counter", 1, "", "").unwrap(); 75 | assert_eq!(counter.val(), 1); 76 | 77 | Client::new("counter_test").unwrap() 78 | .export(&mut [&mut counter]).unwrap(); 79 | 80 | counter.up().unwrap(); 81 | assert_eq!(counter.val(), 2); 82 | 83 | counter.inc(3).unwrap(); 84 | assert_eq!(counter.val(), 5); 85 | 86 | counter.reset().unwrap(); 87 | assert_eq!(counter.val(), 1); 88 | } 89 | -------------------------------------------------------------------------------- /src/client/metric/countvector.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use super::*; 3 | 4 | /// A count vector for multiple strictly increasing integer values, in possibly 5 | /// varying increments 6 | /// 7 | /// Internally uses an `InstanceMetric` with `Semantics::Counter` and 8 | /// `Count::One` scale, and `1` count dimension 9 | pub struct CountVector { 10 | im: InstanceMetric, 11 | indom: Indom, 12 | init_vals: HashMap 13 | } 14 | 15 | impl CountVector { 16 | /// Creates a new count vector with given instances and a single initial value 17 | pub fn new(name: &str, init_val: u64, instances: &[&str], 18 | shorthelp_text: &str, longhelp_text: &str) -> Result { 19 | 20 | let mut instances_and_initvals = Vec::new(); 21 | for instance in instances { 22 | instances_and_initvals.push((*instance, init_val)); 23 | } 24 | 25 | Self::new_with_initvals( 26 | name, 27 | &instances_and_initvals, 28 | shorthelp_text, 29 | longhelp_text 30 | ) 31 | } 32 | 33 | /// Creates a new count vector with given pairs of an instance and it's initial value 34 | pub fn new_with_initvals(name: &str, instances_and_initvals: &[(&str, u64)], 35 | shorthelp_text: &str, longhelp_text: &str) -> Result { 36 | 37 | let mut instances = Vec::new(); 38 | for &(instance, _) in instances_and_initvals.iter() { 39 | instances.push(instance); 40 | } 41 | 42 | let indom_helptext = format!("Instance domain for CounterVector '{}'", name); 43 | let indom = Indom::new( 44 | &instances, 45 | &indom_helptext, &indom_helptext 46 | )?; 47 | 48 | let mut im = InstanceMetric::new( 49 | &indom, 50 | name, 51 | 0, 52 | Semantics::Counter, 53 | Unit::new().count(Count::One, 1)?, 54 | shorthelp_text, 55 | longhelp_text 56 | )?; 57 | 58 | let mut init_vals = HashMap::new(); 59 | for &(instance, init_val) in instances_and_initvals.iter() { 60 | init_vals.insert(instance.to_owned(), init_val); 61 | im.set_val(instance, init_val).unwrap().unwrap(); 62 | } 63 | 64 | Ok(CountVector { 65 | im: im, 66 | indom: indom, 67 | init_vals: init_vals 68 | }) 69 | } 70 | 71 | /// Returns the current count of the instance 72 | pub fn val(&self, instance: &str) -> Option { 73 | self.im.val(instance).cloned() 74 | } 75 | 76 | /// Increments the count of the instance by the given value 77 | /// 78 | /// The wrapping `Option` is `None` if the instance wasn't found 79 | pub fn inc(&mut self, instance: &str, increment: u64) -> Option> { 80 | self.im.val(instance).cloned().and_then(|val| 81 | self.im.set_val(instance, val + increment) 82 | ) 83 | } 84 | 85 | /// Increments the count of the instance by `+1` 86 | /// 87 | /// The wrapping `Option` is `None` if the instance wasn't found 88 | pub fn up(&mut self, instance: &str) -> Option> { 89 | self.inc(instance, 1) 90 | } 91 | 92 | /// Increments the count of all instances by the given value 93 | pub fn inc_all(&mut self, increment: u64) -> io::Result<()> { 94 | for instance in self.indom.instances_iter() { 95 | let val = self.im.val(instance).cloned().unwrap(); 96 | self.im.set_val(instance, val + increment).unwrap()?; 97 | } 98 | Ok(()) 99 | } 100 | 101 | /// Increments the count of all instances by `+1` 102 | pub fn up_all(&mut self) -> io::Result<()> { 103 | self.inc_all(1) 104 | } 105 | 106 | /// Resets the count of the instance to it's initial value that 107 | /// was passed when creating the vector 108 | /// 109 | /// The wrapping `Option` is `None` if the instance wasn't found 110 | pub fn reset(&mut self, instance: &str) -> Option> { 111 | self.im.set_val(instance, *self.init_vals.get(instance).unwrap()) 112 | } 113 | 114 | /// Resets the count of all instances to it's initial value that 115 | /// was passed when creating the vector 116 | pub fn reset_all(&mut self) -> io::Result<()> { 117 | for (instance, init_val) in self.init_vals.iter() { 118 | self.im.set_val(instance, *init_val).unwrap()?; 119 | } 120 | Ok(()) 121 | } 122 | 123 | /// Internally created instance domain 124 | pub fn indom(&self) -> &Indom { &self.indom } 125 | } 126 | 127 | impl MMVWriter for CountVector { 128 | private_impl!{} 129 | 130 | fn write(&mut self, ws: &mut MMVWriterState, c: &mut Cursor<&mut [u8]>, mmv_ver: Version) -> io::Result<()> { 131 | self.im.write(ws, c, mmv_ver) 132 | } 133 | 134 | fn register(&self, ws: &mut MMVWriterState, mmv_ver: Version) { 135 | self.im.register(ws, mmv_ver) 136 | } 137 | 138 | fn has_mmv2_string(&self) -> bool { 139 | self.im.has_mmv2_string() 140 | } 141 | } 142 | 143 | #[test] 144 | pub fn test() { 145 | use super::super::Client; 146 | 147 | let mut cv = CountVector::new( 148 | "count_vector", 149 | 1, 150 | &["a", "b", "c"], 151 | "", "" 152 | ).unwrap(); 153 | 154 | assert_eq!(cv.val("a").unwrap(), 1); 155 | assert_eq!(cv.val("b").unwrap(), 1); 156 | assert_eq!(cv.val("c").unwrap(), 1); 157 | 158 | Client::new("count_vector_test").unwrap() 159 | .export(&mut [&mut cv]).unwrap(); 160 | 161 | cv.up("b").unwrap().unwrap(); 162 | assert_eq!(cv.val("b").unwrap(), 2); 163 | 164 | cv.inc("c", 3).unwrap().unwrap(); 165 | assert_eq!(cv.val("c").unwrap(), 4); 166 | 167 | cv.inc_all(2).unwrap(); 168 | assert_eq!(cv.val("a").unwrap(), 3); 169 | assert_eq!(cv.val("b").unwrap(), 4); 170 | assert_eq!(cv.val("c").unwrap(), 6); 171 | 172 | cv.up_all().unwrap(); 173 | assert_eq!(cv.val("a").unwrap(), 4); 174 | assert_eq!(cv.val("b").unwrap(), 5); 175 | assert_eq!(cv.val("c").unwrap(), 7); 176 | 177 | cv.reset("b").unwrap().unwrap(); 178 | assert_eq!(cv.val("b").unwrap(), 1); 179 | 180 | cv.reset_all().unwrap(); 181 | assert_eq!(cv.val("a").unwrap(), 1); 182 | assert_eq!(cv.val("b").unwrap(), 1); 183 | assert_eq!(cv.val("c").unwrap(), 1); 184 | } 185 | 186 | #[test] 187 | pub fn test_multiple_initvals() { 188 | use super::super::Client; 189 | 190 | let mut cv = CountVector::new_with_initvals( 191 | "count_vector_mutiple_initvals", 192 | &[("a", 1), ("b", 2), ("c", 3)], 193 | "", "" 194 | ).unwrap(); 195 | 196 | assert_eq!(cv.val("a").unwrap(), 1); 197 | assert_eq!(cv.val("b").unwrap(), 2); 198 | assert_eq!(cv.val("c").unwrap(), 3); 199 | 200 | Client::new("count_vector_test").unwrap() 201 | .export(&mut [&mut cv]).unwrap(); 202 | 203 | cv.up_all().unwrap(); 204 | assert_eq!(cv.val("a").unwrap(), 2); 205 | assert_eq!(cv.val("b").unwrap(), 3); 206 | assert_eq!(cv.val("c").unwrap(), 4); 207 | 208 | cv.reset("b").unwrap().unwrap(); 209 | assert_eq!(cv.val("b").unwrap(), 2); 210 | 211 | cv.reset_all().unwrap(); 212 | assert_eq!(cv.val("a").unwrap(), 1); 213 | assert_eq!(cv.val("b").unwrap(), 2); 214 | assert_eq!(cv.val("c").unwrap(), 3); 215 | } 216 | -------------------------------------------------------------------------------- /src/client/metric/gauge.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// A gauge metric for floating point values with helper methods 4 | /// for incrementing and decrementing it's value 5 | /// 6 | /// Internally uses a `Metric` with `Semantics::Instant`, 7 | /// `Count::One` scale, and `1` count dimension 8 | pub struct Gauge { 9 | metric: Metric, 10 | init_val: f64 11 | } 12 | 13 | impl Gauge { 14 | /// Creates a new gauge metric with given initial value 15 | pub fn new(name: &str, init_val: f64, shorthelp_text: &str, longhelp_text: &str) -> Result { 16 | let metric = Metric::new( 17 | name, 18 | init_val, 19 | Semantics::Instant, 20 | Unit::new().count(Count::One, 1)?, 21 | shorthelp_text, 22 | longhelp_text 23 | )?; 24 | 25 | Ok(Gauge { 26 | metric: metric, 27 | init_val: init_val 28 | }) 29 | } 30 | 31 | /// Returns the current value of the gauge 32 | pub fn val(&self) -> f64 { 33 | *self.metric.val() 34 | } 35 | 36 | /// Sets the value of the gauge 37 | pub fn set(&mut self, val: f64) -> io::Result<()> { 38 | self.metric.set_val(val) 39 | } 40 | 41 | /// Increments the gauge by the given value 42 | pub fn inc(&mut self, increment: f64) -> io::Result<()> { 43 | let val = *self.metric.val(); 44 | self.metric.set_val(val + increment) 45 | } 46 | 47 | /// Decrements the gauge by the given value 48 | pub fn dec(&mut self, decrement: f64) -> io::Result<()> { 49 | let val = *self.metric.val(); 50 | self.metric.set_val(val - decrement) 51 | } 52 | 53 | /// Resets the gauge to the initial value that was passed when 54 | /// creating it 55 | pub fn reset(&mut self) -> io::Result<()> { 56 | self.metric.set_val(self.init_val) 57 | } 58 | } 59 | 60 | impl MMVWriter for Gauge { 61 | private_impl!{} 62 | 63 | fn write(&mut self, ws: &mut MMVWriterState, c: &mut Cursor<&mut [u8]>, mmv_ver: Version) -> io::Result<()> { 64 | self.metric.write(ws, c, mmv_ver) 65 | } 66 | 67 | fn register(&self, ws: &mut MMVWriterState, mmv_ver: Version) { 68 | self.metric.register(ws, mmv_ver) 69 | } 70 | 71 | fn has_mmv2_string(&self) -> bool { 72 | self.metric.has_mmv2_string() 73 | } 74 | } 75 | 76 | #[test] 77 | pub fn test() { 78 | use super::super::Client; 79 | 80 | let mut gauge = Gauge::new("gauge", 1.5, "", "").unwrap(); 81 | assert_eq!(gauge.val(), 1.5); 82 | 83 | Client::new("gauge_test").unwrap() 84 | .export(&mut [&mut gauge]).unwrap(); 85 | 86 | gauge.set(3.0).unwrap(); 87 | assert_eq!(gauge.val(), 3.0); 88 | 89 | gauge.inc(3.0).unwrap(); 90 | assert_eq!(gauge.val(), 6.0); 91 | 92 | gauge.dec(1.5).unwrap(); 93 | assert_eq!(gauge.val(), 4.5); 94 | 95 | gauge.reset().unwrap(); 96 | assert_eq!(gauge.val(), 1.5); 97 | } 98 | -------------------------------------------------------------------------------- /src/client/metric/gaugevector.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// A gauge vector for multiple floating point values with helper methods 4 | /// for incrementing and decrementing their value 5 | /// 6 | /// Internally uses an `InstanceMetric` with `Semantics::Instant` and 7 | /// `Count::One` scale, and `1` count dimension 8 | pub struct GaugeVector { 9 | im: InstanceMetric, 10 | indom: Indom, 11 | init_val: f64 12 | } 13 | 14 | impl GaugeVector { 15 | /// Creates a new gauge vector with given initial value and instances 16 | pub fn new(name: &str, init_val: f64, instances: &[&str], 17 | shorthelp_text: &str, longhelp_text: &str) -> Result { 18 | 19 | let indom_helptext = format!("Instance domain for GaugeVector '{}'", name); 20 | let indom = Indom::new(instances, &indom_helptext, &indom_helptext)?; 21 | 22 | let im = InstanceMetric::new( 23 | &indom, 24 | name, 25 | init_val, 26 | Semantics::Counter, 27 | Unit::new().count(Count::One, 1)?, 28 | shorthelp_text, 29 | longhelp_text 30 | )?; 31 | 32 | Ok(GaugeVector { 33 | im: im, 34 | indom: indom, 35 | init_val: init_val 36 | }) 37 | } 38 | 39 | /// Returns the current gauge of the instance 40 | pub fn val(&self, instance: &str) -> Option { 41 | self.im.val(instance).cloned() 42 | } 43 | 44 | /// Sets the gauge of the instance 45 | pub fn set(&mut self, instance: &str, val: f64) -> Option> { 46 | self.im.set_val(instance, val) 47 | } 48 | 49 | /// Increments the gauge of the instance by the given value 50 | /// 51 | /// The wrapping `Option` is `None` if the instance wasn't found 52 | pub fn inc(&mut self, instance: &str, increment: f64) -> Option> { 53 | self.im.val(instance).cloned().and_then(|val| 54 | self.im.set_val(instance, val + increment) 55 | ) 56 | } 57 | 58 | /// Decrements the gauge of the instance by the given value 59 | /// 60 | /// The wrapping `Option` is `None` if the instance wasn't found 61 | pub fn dec(&mut self, instance: &str, decrement: f64) -> Option> { 62 | self.inc(instance, -decrement) 63 | } 64 | 65 | /// Increments the gauge of all instances by the given value 66 | pub fn inc_all(&mut self, increment: f64) -> io::Result<()> { 67 | for instance in self.indom.instances_iter() { 68 | let val = self.im.val(instance).cloned().unwrap(); 69 | self.im.set_val(instance, val + increment).unwrap()?; 70 | } 71 | Ok(()) 72 | } 73 | 74 | /// Decrements the gauge of all instances by the given value 75 | pub fn dec_all(&mut self, decrement: f64) -> io::Result<()> { 76 | self.inc_all(-decrement) 77 | } 78 | 79 | /// Resets the gauge of the instance to the initial value that 80 | /// was passed when creating the vector 81 | /// 82 | /// The wrapping `Option` is `None` if the instance wasn't found 83 | pub fn reset(&mut self, instance: &str) -> Option> { 84 | self.im.set_val(instance, self.init_val) 85 | } 86 | 87 | /// Resets the gauge of all instances to the initial value that 88 | /// was passed when creating the vector 89 | pub fn reset_all(&mut self) -> io::Result<()> { 90 | for instance in self.indom.instances_iter() { 91 | self.im.set_val(instance, self.init_val).unwrap()?; 92 | } 93 | Ok(()) 94 | } 95 | 96 | /// Internally created instance domain 97 | pub fn indom(&self) -> &Indom { &self.indom } 98 | } 99 | 100 | impl MMVWriter for GaugeVector { 101 | private_impl!{} 102 | 103 | fn write(&mut self, ws: &mut MMVWriterState, c: &mut Cursor<&mut [u8]>, mmv_ver: Version) -> io::Result<()> { 104 | self.im.write(ws, c, mmv_ver) 105 | } 106 | 107 | fn register(&self, ws: &mut MMVWriterState, mmv_ver: Version) { 108 | self.im.register(ws, mmv_ver) 109 | } 110 | 111 | fn has_mmv2_string(&self) -> bool { 112 | self.im.has_mmv2_string() 113 | } 114 | } 115 | 116 | #[test] 117 | pub fn test() { 118 | use super::super::Client; 119 | 120 | let mut gv = GaugeVector::new( 121 | "gauge_vector", 122 | 1.5, 123 | &["a", "b", "c"], 124 | "", "").unwrap(); 125 | 126 | assert_eq!(gv.val("a").unwrap(), 1.5); 127 | assert_eq!(gv.val("b").unwrap(), 1.5); 128 | assert_eq!(gv.val("c").unwrap(), 1.5); 129 | 130 | Client::new("count_vector_test").unwrap() 131 | .export(&mut [&mut gv]).unwrap(); 132 | 133 | gv.set("a", 2.5).unwrap().unwrap(); 134 | assert_eq!(gv.val("a").unwrap(), 2.5); 135 | 136 | gv.inc("b", 1.5).unwrap().unwrap(); 137 | assert_eq!(gv.val("b").unwrap(), 3.0); 138 | 139 | gv.dec("c", 1.5).unwrap().unwrap(); 140 | assert_eq!(gv.val("c").unwrap(), 0.0); 141 | 142 | gv.inc_all(2.0).unwrap(); 143 | assert_eq!(gv.val("a").unwrap(), 4.5); 144 | assert_eq!(gv.val("b").unwrap(), 5.0); 145 | assert_eq!(gv.val("c").unwrap(), 2.0); 146 | 147 | gv.dec_all(0.5).unwrap(); 148 | assert_eq!(gv.val("a").unwrap(), 4.0); 149 | assert_eq!(gv.val("b").unwrap(), 4.5); 150 | assert_eq!(gv.val("c").unwrap(), 1.5); 151 | 152 | gv.reset("b").unwrap().unwrap(); 153 | assert_eq!(gv.val("b").unwrap(), 1.5); 154 | 155 | gv.reset_all().unwrap(); 156 | assert_eq!(gv.val("a").unwrap(), 1.5); 157 | assert_eq!(gv.val("b").unwrap(), 1.5); 158 | assert_eq!(gv.val("c").unwrap(), 1.5); 159 | } 160 | -------------------------------------------------------------------------------- /src/client/metric/histogram.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use hdrsample; 3 | use hdrsample::Histogram as HdrHist; 4 | 5 | /// A histogram metric that records data and reports statistics 6 | /// 7 | /// Internally backed by a [HDR Histogram](https://github.com/jonhoo/hdrsample), 8 | /// much of API and documentation being borrowed from it. 9 | /// 10 | /// Exports the `max`, `min`, `mean` and `stdev` statistics to an MMV 11 | /// by using an `InstanceMetric` with `Semantics::Instant`. 12 | pub struct Histogram { 13 | im: InstanceMetric, 14 | indom: Indom, 15 | histogram: HdrHist 16 | } 17 | 18 | const MAX_INST: &str = "max"; 19 | const MIN_INST: &str = "min"; 20 | const MEAN_INST: &str = "mean"; 21 | const STDEV_INST: &str = "stdev"; 22 | 23 | const HIST_INSTANCES: &[&str] = &[MAX_INST, MIN_INST, MEAN_INST, STDEV_INST]; 24 | 25 | /// Error encountered while creating a histogram 26 | #[derive(Debug)] 27 | pub enum CreationError { 28 | /// Instance error 29 | Instance(String), 30 | /// HDR Histogram creation error 31 | HdrHist(hdrsample::CreationError) 32 | } 33 | 34 | impl From for CreationError { 35 | fn from(err: String) -> CreationError { 36 | CreationError::Instance(err) 37 | } 38 | } 39 | 40 | impl From for CreationError { 41 | fn from(err: hdrsample::CreationError) -> CreationError { 42 | CreationError::HdrHist(err) 43 | } 44 | } 45 | 46 | /// Error encountered while a histogram records values 47 | #[derive(Debug)] 48 | pub enum RecordError { 49 | /// IO error 50 | Io(io::Error), 51 | /// HDR histogram record error 52 | HdrHist(hdrsample::RecordError) 53 | } 54 | 55 | impl From for RecordError { 56 | fn from(err: io::Error) -> RecordError { 57 | RecordError::Io(err) 58 | } 59 | } 60 | 61 | impl From for RecordError { 62 | fn from(err: hdrsample::RecordError) -> RecordError { 63 | RecordError::HdrHist(err) 64 | } 65 | } 66 | 67 | impl Histogram { 68 | /// Creates a new histogram metric 69 | /// 70 | /// Internally creates a corresponding HDR histogram with auto-resizing disabled 71 | pub fn new(name: &str, low: u64, high: u64, sigfig: u8, unit: Unit, 72 | shorthelp_text: &str, longhelp_text: &str) -> Result { 73 | 74 | let indom_helptext = format!("Instance domain for Histogram '{}'", name); 75 | let indom = Indom::new(HIST_INSTANCES, &indom_helptext, &indom_helptext).unwrap(); 76 | 77 | let im = InstanceMetric::new( 78 | &indom, 79 | name, 80 | 0.0, 81 | Semantics::Instant, 82 | unit, 83 | shorthelp_text, 84 | longhelp_text 85 | )?; 86 | 87 | let mut histogram = HdrHist::::new_with_bounds(low, high, sigfig)?; 88 | histogram.auto(false); 89 | 90 | Ok(Histogram { 91 | im: im, 92 | indom: indom, 93 | histogram: histogram 94 | }) 95 | } 96 | 97 | fn update_instances(&mut self) -> io::Result<()> { 98 | self.im.set_val(MIN_INST, self.histogram.min() as f64).unwrap()?; 99 | self.im.set_val(MAX_INST, self.histogram.max() as f64).unwrap()?; 100 | self.im.set_val(MEAN_INST, self.histogram.mean()).unwrap()?; 101 | self.im.set_val(STDEV_INST, self.histogram.stdev()).unwrap() 102 | } 103 | 104 | /// Records a value 105 | pub fn record(&mut self, val: u64) -> Result<(), RecordError> { 106 | self.histogram.record(val)?; 107 | self.update_instances()?; 108 | Ok(()) 109 | } 110 | 111 | /// Records multiple samples of a single value 112 | pub fn record_n(&mut self, val: u64, n: u64) -> Result<(), RecordError> { 113 | self.histogram.record_n(val, n)?; 114 | self.update_instances()?; 115 | Ok(()) 116 | } 117 | 118 | /// Resets the contents and statistics of the histogram 119 | pub fn reset(&mut self) -> io::Result<()> { 120 | self.histogram.reset(); 121 | self.update_instances() 122 | } 123 | 124 | /// Lowest discernible value 125 | pub fn low(&self) -> u64 { self.histogram.low() } 126 | /// Highest trackable value 127 | pub fn high(&self) -> u64 { self.histogram.high() } 128 | /// Significant value digits 129 | pub fn significant_figures(&self) -> u8 { self.histogram.sigfig() } 130 | /// Total number of samples recorded so far 131 | pub fn count(&self) -> u64 { self.histogram.count() } 132 | /// Number of distinct values that can currently be represented 133 | pub fn len(&self) -> usize { self.histogram.len() } 134 | 135 | /// Lowest recorded value 136 | /// 137 | /// If no values are yet recorded `0` is returned 138 | pub fn min(&self) -> u64 { self.histogram.min() } 139 | 140 | /// Highest recorded value 141 | /// 142 | /// If no values are yet recorded, an undefined value is returned 143 | pub fn max(&self) -> u64 { self.histogram.max() } 144 | 145 | /// Mean of recorded values 146 | pub fn mean(&self) -> f64 { self.histogram.mean() } 147 | 148 | /// Standard deviation of recorded values 149 | pub fn stdev(&self) -> f64 { self.histogram.stdev() } 150 | 151 | /// Returns corresponding value at percentile 152 | pub fn value_at_percentile(&self, percentile: f64) -> u64 { 153 | self.histogram.value_at_percentile(percentile) 154 | } 155 | 156 | /// Control whether or not the histogram can auto-resize and auto-adjust 157 | /// it's highest trackable value as high-valued samples are recorded 158 | pub fn set_autoresize(&mut self, enable: bool) { 159 | self.histogram.auto(enable); 160 | } 161 | 162 | /// Internally created instance domain 163 | pub fn indom(&self) -> &Indom { &self.indom } 164 | 165 | /// Internally created HDR histogram 166 | pub fn hdr_histogram(&self) -> &HdrHist { &self.histogram } 167 | } 168 | 169 | impl MMVWriter for Histogram { 170 | private_impl!{} 171 | 172 | fn write(&mut self, ws: &mut MMVWriterState, c: &mut Cursor<&mut [u8]>, mmv_ver: Version) -> io::Result<()> { 173 | self.im.write(ws, c, mmv_ver) 174 | } 175 | 176 | fn register(&self, ws: &mut MMVWriterState, mmv_ver: Version) { 177 | self.im.register(ws, mmv_ver) 178 | } 179 | 180 | fn has_mmv2_string(&self) -> bool { 181 | self.im.has_mmv2_string() 182 | } 183 | } 184 | 185 | #[test] 186 | pub fn test() { 187 | use super::super::Client; 188 | use rand::{thread_rng, Rng}; 189 | use rand::distributions::{IndependentSample, Range}; 190 | 191 | let low = 1; 192 | let high = 60 * 60 * 1000; 193 | let sigfig = 2; 194 | 195 | let mut hist = Histogram::new( 196 | "histogram", 197 | low, high, sigfig, 198 | Unit::new(), 199 | "", "" 200 | ).unwrap(); 201 | 202 | Client::new("histogram_test").unwrap() 203 | .export(&mut [&mut hist]).unwrap(); 204 | 205 | let val_range = Range::new(low, high); 206 | let mut rng = thread_rng(); 207 | 208 | let n = thread_rng().gen::() % 100; 209 | for _ in 0..n { 210 | hist.record(val_range.ind_sample(&mut rng)).unwrap(); 211 | } 212 | hist.record_n(val_range.ind_sample(&mut rng), n).unwrap(); 213 | 214 | assert_eq!( 215 | *hist.im.val(MIN_INST).unwrap(), 216 | hist.histogram.min() as f64 217 | ); 218 | 219 | assert_eq!( 220 | *hist.im.val(MAX_INST).unwrap(), 221 | hist.histogram.max() as f64 222 | ); 223 | 224 | assert_eq!( 225 | *hist.im.val(MEAN_INST).unwrap(), 226 | hist.histogram.mean() 227 | ); 228 | 229 | assert_eq!( 230 | *hist.im.val(STDEV_INST).unwrap(), 231 | hist.histogram.stdev() 232 | ); 233 | } 234 | -------------------------------------------------------------------------------- /src/client/metric/mod.rs: -------------------------------------------------------------------------------- 1 | use byteorder::WriteBytesExt; 2 | use memmap::{Mmap, MmapViewSync, Protection}; 3 | use std::collections::HashSet; 4 | use std::collections::hash_map::{DefaultHasher, HashMap}; 5 | use std::collections::hash_set::Iter; 6 | use std::fmt; 7 | use std::hash::{Hash, Hasher}; 8 | use std::io; 9 | use std::io::{Write, Cursor}; 10 | use std::mem; 11 | use std::str; 12 | 13 | use super::super::mmv::{MTCode, Version}; 14 | use super::super::{ 15 | Endian, 16 | ITEM_BIT_LEN, 17 | INDOM_BIT_LEN, 18 | STRING_BLOCK_LEN, 19 | VALUE_BLOCK_LEN, 20 | NUMERIC_VALUE_SIZE, 21 | INDOM_BLOCK_LEN, 22 | MMV1_NAME_MAX_LEN, 23 | METRIC_BLOCK_LEN_MMV1, 24 | INSTANCE_BLOCK_LEN_MMV1, 25 | METRIC_BLOCK_LEN_MMV2, 26 | INSTANCE_BLOCK_LEN_MMV2 27 | }; 28 | 29 | mod counter; 30 | pub use self::counter::Counter; 31 | 32 | mod gauge; 33 | pub use self::gauge::Gauge; 34 | 35 | mod timer; 36 | pub use self::timer::Timer; 37 | 38 | mod countvector; 39 | pub use self::countvector::CountVector; 40 | 41 | mod gaugevector; 42 | pub use self::gaugevector::GaugeVector; 43 | 44 | mod histogram; 45 | pub use self::histogram::Histogram; 46 | pub use self::histogram::CreationError as HistCreationError; 47 | pub use self::histogram::RecordError as HistRecordError; 48 | 49 | mod private { 50 | use byteorder::WriteBytesExt; 51 | use std::io; 52 | 53 | /// Generic type for any Metric's value 54 | pub trait MetricType { 55 | private_decl!{} 56 | 57 | /// Returns the MMV metric type code 58 | fn type_code(&self) -> u32; 59 | /// Writes the byte representation of the value to a writer. 60 | /// 61 | /// For integer and float types, the byte sequence is little endian. 62 | /// 63 | /// For the string type, the UTF-8 byte sequence is suffixed with a null byte. 64 | fn write(&self, writer: &mut W) -> io::Result<()>; 65 | } 66 | 67 | use memmap::MmapViewSync; 68 | use std::collections::HashMap; 69 | 70 | pub struct MMVWriterState { 71 | // Mmap view of the entier MMV file 72 | pub mmap_view: Option, 73 | 74 | // generation numbers 75 | pub gen: i64, 76 | pub gen2_off: u64, 77 | 78 | // counts 79 | pub n_toc: u64, 80 | pub n_metrics: u64, 81 | pub n_values: u64, 82 | pub n_strings: u64, 83 | pub n_indoms: u64, 84 | pub n_instances: u64, 85 | 86 | // caches 87 | pub non_value_string_cache: HashMap>, // (string, offset to it) 88 | // if the offset is None, it means the string hasn't been written yet 89 | // 90 | pub indom_cache: HashMap>>, // (indom_id, offsets to it's instances) 91 | // if the offsets vector is None, it means the instances haven't been written yet 92 | 93 | // offsets to blocks 94 | pub indom_sec_off: u64, 95 | pub instance_sec_off: u64, 96 | pub metric_sec_off: u64, 97 | pub value_sec_off: u64, 98 | pub string_sec_off: u64, 99 | pub string_toc_off: u64, 100 | 101 | // running indexes of objects written so far 102 | pub indom_idx: u64, 103 | pub instance_idx: u64, 104 | pub metric_blk_idx: u64, 105 | pub value_blk_idx: u64, 106 | pub string_blk_idx: u64, 107 | 108 | // mmv header data 109 | pub flags: u32, 110 | pub cluster_id: u32 111 | } 112 | 113 | impl MMVWriterState { 114 | pub fn new() -> Self { 115 | MMVWriterState { 116 | mmap_view: None, 117 | 118 | gen: 0, 119 | gen2_off: 0, 120 | 121 | n_toc: 0, 122 | n_metrics: 0, 123 | n_values: 0, 124 | n_strings: 0, 125 | n_indoms: 0, 126 | n_instances: 0, 127 | 128 | indom_cache: HashMap::new(), 129 | non_value_string_cache: HashMap::new(), 130 | 131 | indom_sec_off: 0, 132 | instance_sec_off: 0, 133 | metric_sec_off: 0, 134 | value_sec_off: 0, 135 | string_sec_off: 0, 136 | string_toc_off: 0, 137 | 138 | indom_idx: 0, 139 | instance_idx: 0, 140 | metric_blk_idx: 0, 141 | value_blk_idx: 0, 142 | string_blk_idx: 0, 143 | 144 | flags: 0, 145 | cluster_id: 0 146 | } 147 | } 148 | } 149 | 150 | use super::Version; 151 | 152 | /// MMV object that writes blocks to an MMV 153 | pub trait MMVWriter { 154 | private_decl!{} 155 | 156 | fn write(&mut self, 157 | writer_state: &mut MMVWriterState, 158 | cursor: &mut io::Cursor<&mut [u8]>, mmv_ver: Version) -> io::Result<()>; 159 | 160 | fn register(&self, ws: &mut MMVWriterState, mmv_ver: Version); 161 | 162 | fn has_mmv2_string(&self) -> bool; 163 | } 164 | } 165 | 166 | pub (super) use self::private::MetricType; 167 | pub (super) use self::private::{MMVWriter, MMVWriterState}; 168 | 169 | macro_rules! impl_metric_type_for ( 170 | ($typ:tt, $base_typ:tt, $type_code:expr) => ( 171 | impl MetricType for $typ { 172 | 173 | private_impl!{} 174 | 175 | fn type_code(&self) -> u32 { 176 | $type_code as u32 177 | } 178 | 179 | fn write(&self, w: &mut W) 180 | -> io::Result<()> { 181 | w.write_u64::( 182 | unsafe { 183 | mem::transmute::<$typ, $base_typ>(*self) as u64 184 | } 185 | ) 186 | } 187 | 188 | } 189 | ) 190 | ); 191 | 192 | impl_metric_type_for!(i32, u32, MTCode::I32); 193 | impl_metric_type_for!(u32, u32, MTCode::U32); 194 | impl_metric_type_for!(i64, u64, MTCode::I64); 195 | impl_metric_type_for!(u64, u64, MTCode::U64); 196 | impl_metric_type_for!(f32, u32, MTCode::F32); 197 | impl_metric_type_for!(f64, u64, MTCode::F64); 198 | 199 | impl MetricType for String { 200 | private_impl!{} 201 | 202 | fn type_code(&self) -> u32 { 203 | MTCode::String as u32 204 | } 205 | 206 | fn write(&self, writer: &mut W) -> io::Result<()> { 207 | writer.write_all(self.as_bytes())?; 208 | writer.write_all(&[0]) 209 | } 210 | } 211 | 212 | #[derive(Copy, Clone)] 213 | /// Scale for the space component of a unit 214 | pub enum Space { 215 | /// byte 216 | Byte = 0, 217 | /// kilobyte (1024 bytes) 218 | KByte, 219 | /// megabyte (1024^2 bytes) 220 | MByte, 221 | /// gigabyte (1024^3 bytes) 222 | GByte, 223 | /// terabyte (1024^4 bytes) 224 | TByte, 225 | /// petabyte (1024^5 bytes) 226 | PByte, 227 | /// exabyte (1024^6 bytes) 228 | EByte 229 | } 230 | 231 | impl Space { 232 | fn from_u8(x: u8) -> Option { 233 | match x { 234 | 0 => Some(Space::Byte), 235 | 1 => Some(Space::KByte), 236 | 2 => Some(Space::MByte), 237 | 3 => Some(Space::GByte), 238 | 4 => Some(Space::TByte), 239 | 5 => Some(Space::PByte), 240 | 6 => Some(Space::EByte), 241 | _ => None 242 | } 243 | } 244 | } 245 | 246 | impl fmt::Display for Space { 247 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 248 | match *self { 249 | Space::Byte => write!(f, "B"), 250 | Space::KByte => write!(f, "KiB"), 251 | Space::MByte => write!(f, "MiB"), 252 | Space::GByte => write!(f, "GiB"), 253 | Space::TByte => write!(f, "TiB"), 254 | Space::PByte => write!(f, "PiB"), 255 | Space::EByte => write!(f, "EiB") 256 | } 257 | } 258 | } 259 | 260 | #[derive(Copy, Clone)] 261 | /// Scale for the time component of a unit 262 | pub enum Time { 263 | /// nanosecond 264 | NSec = 0, 265 | /// microsecond 266 | USec, 267 | /// millisecond 268 | MSec, 269 | /// second 270 | Sec, 271 | /// minute 272 | Min, 273 | /// hour 274 | Hour 275 | } 276 | 277 | impl Time { 278 | fn from_u8(x: u8) -> Option { 279 | match x { 280 | 0 => Some(Time::NSec), 281 | 1 => Some(Time::USec), 282 | 2 => Some(Time::MSec), 283 | 3 => Some(Time::Sec), 284 | 4 => Some(Time::Min), 285 | 5 => Some(Time::Hour), 286 | _ => None 287 | } 288 | } 289 | } 290 | 291 | impl fmt::Display for Time { 292 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 293 | match *self { 294 | Time::NSec => write!(f, "nsec"), 295 | Time::USec => write!(f, "usec"), 296 | Time::MSec => write!(f, "msec"), 297 | Time::Sec => write!(f, "sec"), 298 | Time::Min => write!(f, "min"), 299 | Time::Hour => write!(f, "hr"), 300 | } 301 | } 302 | } 303 | 304 | #[derive(Copy, Clone)] 305 | /// Scale for the count component of a unit 306 | pub enum Count { 307 | One = 0 308 | } 309 | 310 | impl Count { 311 | fn from_u8(x: u8) -> Option { 312 | match x { 313 | 0 => Some(Count::One), 314 | _ => None 315 | } 316 | } 317 | } 318 | 319 | impl fmt::Display for Count { 320 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 321 | match *self { 322 | Count::One => write!(f, "count") 323 | } 324 | } 325 | } 326 | 327 | #[derive(Copy, Clone)] 328 | /// Unit for a Metric 329 | pub struct Unit { 330 | /* 331 | pmapi representation of a unit. below, 31 refers to MSB 332 | bits 31 - 28 : space dim (signed) 333 | 27 - 24 : time dim (signed) 334 | 23 - 20 : count dim (signed) 335 | 19 - 16 : space scale (unsigned) 336 | 15 - 12 : time scale (unsigned) 337 | 11 - 8 : count scale (unsigned) 338 | 7 - 0 : zero pad 339 | */ 340 | pmapi_repr: u32 341 | } 342 | 343 | const SPACE_DIM_LSB: u8 = 28; 344 | const TIME_DIM_LSB: u8 = 24; 345 | const COUNT_DIM_LSB: u8 = 20; 346 | const SPACE_SCALE_LSB: u8 = 16; 347 | const TIME_SCALE_LSB: u8 = 12; 348 | const COUNT_SCALE_LSB: u8 = 8; 349 | 350 | const LS_FOUR_BIT_MASK: u32 = 0xF; 351 | 352 | macro_rules! check_dim ( 353 | ($dim:expr) => ( 354 | if $dim > 7 || $dim < -8 { 355 | return Err(format!("Unit dimension {} is out of range [-8, 7]", $dim)) 356 | } 357 | ) 358 | ); 359 | 360 | impl Unit { 361 | /// Returns a unit constructed from a raw PMAPI representation 362 | pub fn from_raw(pmapi_repr: u32) -> Self { 363 | Unit { 364 | pmapi_repr: pmapi_repr 365 | } 366 | } 367 | 368 | /// Returns an empty unit with all dimensions set to `0` 369 | /// and all scales set to an undefined variant 370 | pub fn new() -> Self { 371 | Self::from_raw(0) 372 | } 373 | 374 | /// Modifies and returns the unit with given space scale and dimension 375 | pub fn space(mut self, scale: Space, dim: i8) -> Result { 376 | check_dim!(dim); 377 | self.pmapi_repr |= (scale as u32) << SPACE_SCALE_LSB; 378 | self.pmapi_repr |= ((dim as u32) & LS_FOUR_BIT_MASK) << SPACE_DIM_LSB; 379 | Ok(self) 380 | } 381 | 382 | /// Modifies and returns the unit with given time scale and dimension 383 | pub fn time(mut self, time: Time, dim: i8) -> Result { 384 | check_dim!(dim); 385 | self.pmapi_repr |= (time as u32) << TIME_SCALE_LSB; 386 | self.pmapi_repr |= ((dim as u32) & LS_FOUR_BIT_MASK) << TIME_DIM_LSB; 387 | Ok(self) 388 | } 389 | 390 | /// Modifies and returns the unit with given count scale and dimension 391 | pub fn count(mut self, count: Count, dim: i8) -> Result { 392 | check_dim!(dim); 393 | self.pmapi_repr |= (count as u32) << COUNT_SCALE_LSB; 394 | self.pmapi_repr |= ((dim as u32) & LS_FOUR_BIT_MASK) << COUNT_DIM_LSB; 395 | Ok(self) 396 | } 397 | 398 | fn space_scale(&self) -> u8 { 399 | ((self.pmapi_repr >> SPACE_SCALE_LSB) & LS_FOUR_BIT_MASK) as u8 400 | } 401 | 402 | fn time_scale(&self) -> u8 { 403 | ((self.pmapi_repr >> TIME_SCALE_LSB) & LS_FOUR_BIT_MASK) as u8 404 | } 405 | 406 | fn count_scale(&self) -> u8 { 407 | ((self.pmapi_repr >> COUNT_SCALE_LSB) & LS_FOUR_BIT_MASK) as u8 408 | } 409 | 410 | /* 411 | We have a 4-bit value in two's complement form which we have to 412 | sign-extend to 8 bits. We first left shift our 4 bits in pmapi_repr 413 | to the most significant position, then do an arithmetic right shift 414 | to bring them to the least significant position in order to 415 | sign-extend the remaining 4 bits. 416 | 417 | In Rust, an arthimetic/logical right shift is performed depending on 418 | whether the integer is signed or unsigned. Hence, we cast our integer 419 | to an i32 before we right shift it. 420 | */ 421 | fn dim(&self, lsb: u8) -> i8 { 422 | ( 423 | ( self.pmapi_repr << (32 - (lsb + 4)) ) as i32 424 | >> 28 425 | ) as i8 426 | } 427 | 428 | fn space_dim(&self) -> i8 { 429 | self.dim(SPACE_DIM_LSB) 430 | } 431 | 432 | fn time_dim(&self) -> i8 { 433 | self.dim(TIME_DIM_LSB) 434 | } 435 | 436 | fn count_dim(&self) -> i8 { 437 | self.dim(COUNT_DIM_LSB) 438 | } 439 | } 440 | 441 | macro_rules! write_dim ( 442 | ($dim:expr, $scale:expr, $scale_type:tt, $f:expr) => ( 443 | if let Some(dim_scale) = $scale_type::from_u8($scale) { 444 | write!($f, "{}", dim_scale)?; 445 | if $dim.abs() > 1 { 446 | write!($f, "^{}", $dim.abs())?; 447 | } 448 | write!($f, " ")?; 449 | } 450 | ) 451 | ); 452 | 453 | impl fmt::Display for Unit { 454 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 455 | let space_dim = self.space_dim(); 456 | let space_scale = self.space_scale(); 457 | let time_dim = self.time_dim(); 458 | let time_scale = self.time_scale(); 459 | let count_dim = self.count_dim(); 460 | let count_scale = self.count_scale(); 461 | 462 | if space_dim > 0 { 463 | write_dim!(space_dim, space_scale, Space, f); 464 | } 465 | if time_dim > 0 { 466 | write_dim!(time_dim, time_scale, Time, f); 467 | } 468 | if count_dim > 0 { 469 | write_dim!(count_dim, count_scale, Count, f); 470 | } 471 | 472 | if space_dim < 0 || time_dim < 0 || count_dim < 0 { 473 | write!(f, "/ ")?; 474 | if space_dim < 0 { 475 | write_dim!(space_dim, space_scale, Space, f); 476 | } 477 | if time_dim < 0 { 478 | write_dim!(time_dim, time_scale, Time, f); 479 | } 480 | if count_dim < 0 { 481 | write_dim!(count_dim, count_scale, Count, f); 482 | } 483 | } 484 | 485 | write!(f, "(0x{:x})", self.pmapi_repr) 486 | } 487 | } 488 | 489 | #[derive(Copy, Clone)] 490 | /// Semantic for a Metric 491 | pub enum Semantics { 492 | /// Counter 493 | Counter = 1, 494 | /// Instant 495 | Instant = 3, 496 | /// Discrete 497 | Discrete = 4 498 | } 499 | 500 | impl Semantics { 501 | pub fn from_u32(x: u32) -> Option { 502 | match x { 503 | 1 => Some(Semantics::Counter), 504 | 3 => Some(Semantics::Instant), 505 | 4 => Some(Semantics::Discrete), 506 | _ => None 507 | } 508 | } 509 | } 510 | 511 | impl fmt::Display for Semantics { 512 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 513 | match *self { 514 | Semantics::Counter => write!(f, "counter")?, 515 | Semantics::Instant => write!(f, "instant")?, 516 | Semantics::Discrete => write!(f, "discrete")? 517 | } 518 | write!(f, " (0x{:x})", *self as u32) 519 | } 520 | } 521 | 522 | /// Singleton metric 523 | pub struct Metric { 524 | name: String, 525 | item: u32, 526 | sem: Semantics, 527 | indom: u32, 528 | unit: u32, 529 | shorthelp: String, 530 | longhelp: String, 531 | val: T, 532 | mmap_view: MmapViewSync 533 | } 534 | 535 | lazy_static! { 536 | static ref SCRATCH_VIEW: MmapViewSync = { 537 | Mmap::anonymous(STRING_BLOCK_LEN as usize, Protection::ReadWrite).unwrap() 538 | .into_view_sync() 539 | }; 540 | } 541 | 542 | impl Metric { 543 | /// Creates a new PCP MMV Metric 544 | /// 545 | /// The result is an error if the length of `name`, `shorthelp` 546 | /// or `longhelp` exceed 255 bytes. 547 | pub fn new( 548 | name: &str, init_val: T, sem: Semantics, unit: Unit, 549 | shorthelp: &str, longhelp: &str) -> Result { 550 | 551 | if name.len() >= STRING_BLOCK_LEN as usize { 552 | return Err(format!("name longer than {} bytes", STRING_BLOCK_LEN - 1)); 553 | } 554 | if shorthelp.len() >= STRING_BLOCK_LEN as usize { 555 | return Err(format!("short help text longer than {} bytes", STRING_BLOCK_LEN - 1)); 556 | } 557 | if longhelp.len() >= STRING_BLOCK_LEN as usize { 558 | return Err(format!("long help text longer than {} bytes", STRING_BLOCK_LEN - 1)); 559 | } 560 | 561 | let mut hasher = DefaultHasher::new(); 562 | hasher.write(name.as_bytes()); 563 | let item = (hasher.finish() as u32) & ((1 << ITEM_BIT_LEN) - 1); 564 | 565 | Ok(Metric { 566 | name: name.to_owned(), 567 | item: item, 568 | sem: sem, 569 | indom: 0, 570 | unit: unit.pmapi_repr, 571 | shorthelp: shorthelp.to_owned(), 572 | longhelp: longhelp.to_owned(), 573 | val: init_val, 574 | mmap_view: unsafe { SCRATCH_VIEW.clone() } 575 | }) 576 | } 577 | 578 | /// Returns the current value of the metric 579 | pub fn val(&self) -> &T { 580 | &self.val 581 | } 582 | 583 | /// Sets the current value of the metric. 584 | /// 585 | /// If the metric is exported using a client, 586 | /// the value is written to the relevant MMV file. 587 | /// 588 | /// If the metric isn't exported, this method will still 589 | /// succeed and update the value. 590 | pub fn set_val(&mut self, new_val: T) -> io::Result<()> { 591 | new_val.write(unsafe { &mut self.mmap_view.as_mut_slice() })?; 592 | self.val = new_val; 593 | Ok(()) 594 | } 595 | 596 | pub fn name(&self) -> &str { &self.name } 597 | pub fn item(&self) -> u32 { self.item } 598 | pub fn type_code(&self) -> u32 { self.val.type_code() } 599 | pub fn sem(&self) -> &Semantics { &self.sem } 600 | pub fn unit(&self) -> u32 { self.unit } 601 | pub fn indom(&self) -> u32 { self.indom } 602 | pub fn shorthelp(&self) -> &str { &self.shorthelp } 603 | pub fn longhelp(&self) -> &str { &self.longhelp } 604 | } 605 | 606 | #[derive(Clone)] 607 | /// An instance domain is a set of instances 608 | pub struct Indom { 609 | instances: HashSet, 610 | id: u32, 611 | shorthelp: String, 612 | longhelp: String 613 | } 614 | 615 | impl Indom { 616 | /// Creates a new instance domain 617 | /// 618 | /// The result is an error if the length of any `instance`, `shorthelp` 619 | /// or `longhelp` exceed 255 bytes. 620 | pub fn new(instances: &[&str], shorthelp: &str, longhelp: &str) -> Result { 621 | let mut hasher = DefaultHasher::new(); 622 | instances.hash(&mut hasher); 623 | 624 | for instance in instances { 625 | if instance.len() >= STRING_BLOCK_LEN as usize { 626 | return Err(format!("instance longer than {} bytes", STRING_BLOCK_LEN - 1)); 627 | } 628 | } 629 | if shorthelp.len() >= STRING_BLOCK_LEN as usize { 630 | return Err(format!("short help text longer than {} bytes", STRING_BLOCK_LEN - 1)); 631 | } 632 | if longhelp.len() >= STRING_BLOCK_LEN as usize { 633 | return Err(format!("long help text longer than {} bytes", STRING_BLOCK_LEN - 1)); 634 | } 635 | 636 | Ok(Indom { 637 | instances: instances.into_iter().map(|inst| inst.to_string()).collect(), 638 | id: (hasher.finish() as u32) & ((1 << INDOM_BIT_LEN) - 1), 639 | shorthelp: shorthelp.to_owned(), 640 | longhelp: longhelp.to_owned() 641 | }) 642 | } 643 | 644 | /// Returns the number of instances in the domain 645 | pub fn instance_count(&self) -> u32 { 646 | self.instances.len() as u32 647 | } 648 | 649 | /// Checks if given instance is in the domain 650 | pub fn has_instance(&self, instance: &str) -> bool { 651 | self.instances.contains(instance) 652 | } 653 | 654 | /// Returns an iterator visiting the instances in 655 | /// arbitrary order 656 | pub fn instances_iter(&self) -> Iter { 657 | self.instances.iter() 658 | } 659 | 660 | pub fn shorthelp(&self) -> &str { &self.shorthelp } 661 | pub fn longhelp(&self) -> &str { &self.longhelp } 662 | 663 | fn instance_id(instance: &str) -> u32 { 664 | let mut hasher = DefaultHasher::new(); 665 | instance.hash(&mut hasher); 666 | hasher.finish() as u32 667 | } 668 | 669 | fn has_mmv2_string(&self) -> bool { 670 | self.instances.iter().any(|instance| 671 | instance.len() >= MMV1_NAME_MAX_LEN as usize 672 | ) 673 | } 674 | } 675 | 676 | struct Instance { 677 | val: T, 678 | mmap_view: MmapViewSync 679 | } 680 | 681 | /// An instance metric is a set of related metrics with same 682 | /// type, semantics and unit. Many instance metrics can share 683 | /// the same set of instances, i.e., instance domain. 684 | pub struct InstanceMetric { 685 | indom: Indom, 686 | vals: HashMap>, 687 | metric: Metric 688 | } 689 | 690 | impl InstanceMetric { 691 | /// Creates a new instance metric 692 | /// 693 | /// The result is an error if the length of `name`, `shorthelp` 694 | /// or `longhelp` exceed 255 bytes. 695 | pub fn new( 696 | indom: &Indom, 697 | name: &str, 698 | init_val: T, 699 | sem: Semantics, 700 | unit: Unit, 701 | shorthelp: &str, 702 | longhelp: &str) -> Result { 703 | 704 | let mut vals = HashMap::with_capacity(indom.instances.len()); 705 | for instance_str in &indom.instances { 706 | let instance = Instance { 707 | val: init_val.clone(), 708 | mmap_view: unsafe { SCRATCH_VIEW.clone() } 709 | }; 710 | vals.insert(instance_str.to_owned(), instance); 711 | } 712 | 713 | let mut metric = Metric::new( 714 | name, init_val.clone(), sem, unit, shorthelp, longhelp 715 | )?; 716 | metric.indom = indom.id; 717 | 718 | Ok(InstanceMetric { 719 | indom: indom.clone(), 720 | vals: vals, 721 | metric: metric 722 | }) 723 | } 724 | 725 | /// Returns the number of instances that're part of the metric 726 | pub fn instance_count(&self) -> u32 { 727 | self.vals.len() as u32 728 | } 729 | 730 | /// Check if given instance is part of the metric 731 | pub fn has_instance(&self, instance: &str) -> bool { 732 | self.vals.contains_key(instance) 733 | } 734 | 735 | /// Returns the value of the given instance 736 | pub fn val(&self, instance: &str) -> Option<&T> { 737 | self.vals.get(instance).map(|i| &i.val) 738 | } 739 | 740 | /// Sets the value of the given instance. If the instance isn't 741 | /// found, returns `None`. 742 | pub fn set_val(&mut self, instance: &str, new_val: T) -> Option> { 743 | self.vals.get_mut(instance).map(|i| { 744 | new_val.write(unsafe { &mut i.mmap_view.as_mut_slice() })?; 745 | i.val = new_val; 746 | Ok(()) 747 | }) 748 | } 749 | 750 | pub fn name(&self) -> &str { &self.metric.name } 751 | pub fn sem(&self) -> &Semantics { &self.metric.sem } 752 | pub fn unit(&self) -> u32 { self.metric.unit } 753 | pub fn shorthelp(&self) -> &str { &self.metric.shorthelp } 754 | pub fn longhelp(&self) -> &str { &self.metric.longhelp } 755 | } 756 | 757 | impl Metric { 758 | fn write_to_mmv(&mut self, ws: &mut MMVWriterState, c: &mut Cursor<&mut [u8]>, 759 | mmv_ver: Version, write_value_blk: bool) -> io::Result { 760 | 761 | let orig_pos = c.position(); 762 | 763 | // metric block 764 | let metric_blk_len = match mmv_ver { 765 | Version::V1 => METRIC_BLOCK_LEN_MMV1, 766 | Version::V2 => METRIC_BLOCK_LEN_MMV2 767 | }; 768 | let metric_blk_off = 769 | ws.metric_sec_off 770 | + metric_blk_len*ws.metric_blk_idx; 771 | c.set_position(metric_blk_off); 772 | 773 | // name 774 | match mmv_ver { 775 | Version::V1 => { 776 | c.write_all(self.name.as_bytes())?; 777 | c.write_all(&[0])?; 778 | c.set_position(metric_blk_off + MMV1_NAME_MAX_LEN); 779 | }, 780 | Version::V2 => { 781 | let name_off = write_mmv_string(ws, c, &self.name, false)?; 782 | c.write_u64::(name_off)?; 783 | } 784 | } 785 | 786 | // item 787 | c.write_u32::(self.item)?; 788 | // type code 789 | c.write_u32::(self.val.type_code())?; 790 | // sem 791 | c.write_u32::(self.sem as u32)?; 792 | // unit 793 | c.write_u32::(self.unit)?; 794 | // indom 795 | c.write_u32::(self.indom)?; 796 | // zero pad 797 | c.write_u32::(0)?; 798 | // short help 799 | let short_help_off = write_mmv_string(ws, c, &self.shorthelp, false)?; 800 | c.write_u64::(short_help_off)?; 801 | // long help 802 | let long_help_off = write_mmv_string(ws, c, &self.longhelp, false)?; 803 | c.write_u64::(long_help_off)?; 804 | 805 | if write_value_blk { 806 | let (value_offset, value_size) = 807 | write_value_block(ws, c, &self.val, metric_blk_off, 0)?; 808 | 809 | let mmap_view = unsafe { 810 | ws.mmap_view.as_mut().unwrap().clone() 811 | }; 812 | let (_, value_mmap_view, _) = 813 | three_way_split(mmap_view, value_offset, value_size)?; 814 | self.mmap_view = value_mmap_view; 815 | } 816 | 817 | ws.metric_blk_idx += 1; 818 | c.set_position(orig_pos); 819 | Ok(metric_blk_off) 820 | } 821 | } 822 | 823 | impl MMVWriter for Metric { 824 | private_impl!{} 825 | 826 | fn write(&mut self, ws: &mut MMVWriterState, c: &mut Cursor<&mut [u8]>, mmv_ver: Version) -> io::Result<()> { 827 | self.write_to_mmv(ws, c, mmv_ver, true)?; 828 | Ok(()) 829 | } 830 | 831 | fn register(&self, ws: &mut MMVWriterState, mmv_ver: Version) { 832 | ws.n_metrics += 1; 833 | ws.n_values += 1; 834 | 835 | if self.val.type_code() == MTCode::String as u32 { 836 | ws.n_strings += 1; 837 | } 838 | 839 | cache_and_register_string(ws, &self.shorthelp); 840 | cache_and_register_string(ws, &self.longhelp); 841 | 842 | match mmv_ver { 843 | Version::V1 => {}, 844 | Version::V2 => cache_and_register_string(ws, &self.name) 845 | } 846 | } 847 | 848 | fn has_mmv2_string(&self) -> bool { 849 | self.name.len() >= MMV1_NAME_MAX_LEN as usize 850 | } 851 | } 852 | 853 | impl MMVWriter for InstanceMetric { 854 | private_impl!{} 855 | 856 | fn write(&mut self, ws: &mut MMVWriterState, c: &mut Cursor<&mut [u8]>, mmv_ver: Version) -> io::Result<()> { 857 | // write metric block 858 | let metric_blk_off = self.metric.write_to_mmv(ws, c, mmv_ver, false)?; 859 | 860 | // write indom and instances 861 | let instance_blk_offs = write_indom_and_instances(ws, c, &self.indom, mmv_ver)?; 862 | 863 | // write value blocks 864 | for (instance_name, instance) in self.vals.iter_mut() { 865 | 866 | let instance_blk_off = *instance_blk_offs.get(instance_name).unwrap(); 867 | let (value_offset, value_size) = 868 | write_value_block(ws, c, &instance.val, metric_blk_off, instance_blk_off)?; 869 | 870 | // set mmap_view for instance 871 | let mmap_view = unsafe { 872 | ws.mmap_view.as_mut().unwrap().clone() 873 | }; 874 | let (_, value_mmap_view, _) = 875 | three_way_split(mmap_view, value_offset, value_size)?; 876 | instance.mmap_view = value_mmap_view; 877 | 878 | } 879 | 880 | Ok(()) 881 | } 882 | 883 | fn register(&self, ws: &mut MMVWriterState, mmv_ver: Version) { 884 | ws.n_metrics += 1; 885 | ws.n_values += self.vals.len() as u64; 886 | 887 | if self.metric.val.type_code() == MTCode::String as u32 { 888 | ws.n_strings += 1; 889 | } 890 | 891 | cache_and_register_string(ws, &self.metric.shorthelp); 892 | cache_and_register_string(ws, &self.metric.longhelp); 893 | cache_and_register_string(ws, &self.indom.shorthelp); 894 | cache_and_register_string(ws, &self.indom.longhelp); 895 | 896 | if !ws.indom_cache.contains_key(&self.indom.id) { 897 | ws.n_indoms += 1; 898 | ws.n_instances += self.indom.instances.len() as u64; 899 | ws.indom_cache.insert(self.indom.id, None); 900 | 901 | match mmv_ver { 902 | Version::V1 => {}, 903 | Version::V2 => { 904 | cache_and_register_string(ws, &self.metric.name); 905 | for instance in &self.indom.instances { 906 | cache_and_register_string(ws, instance); 907 | } 908 | } 909 | } 910 | } 911 | } 912 | 913 | fn has_mmv2_string(&self) -> bool { 914 | self.metric.has_mmv2_string() || self.indom.has_mmv2_string() 915 | } 916 | } 917 | 918 | fn write_indom_and_instances<'a>(ws: &mut MMVWriterState, c: &mut Cursor<&mut [u8]>, 919 | indom: &Indom, mmv_ver: Version)-> io::Result> { 920 | 921 | // write each indom and it's instances only once 922 | if let Some(blk_offs) = ws.indom_cache.get(&indom.id) { 923 | if let &Some(ref blk_offs) = blk_offs { 924 | return Ok(blk_offs.clone()) 925 | } 926 | } 927 | 928 | // write indom block 929 | let indom_off = 930 | ws.indom_sec_off 931 | + INDOM_BLOCK_LEN*ws.indom_idx; 932 | c.set_position(indom_off); 933 | // indom id 934 | c.write_u32::(indom.id)?; 935 | // number of instances 936 | c.write_u32::(indom.instance_count())?; 937 | 938 | // offset to instances 939 | let instance_blk_len = match mmv_ver { 940 | Version::V1 => INSTANCE_BLOCK_LEN_MMV1, 941 | Version::V2 => INSTANCE_BLOCK_LEN_MMV2 942 | }; 943 | let mut instance_blk_off = 944 | ws.instance_sec_off 945 | + instance_blk_len*ws.instance_idx; 946 | c.write_u64::(instance_blk_off)?; 947 | 948 | // short help 949 | let short_help_off = write_mmv_string(ws, c, indom.shorthelp(), false)?; 950 | c.write_u64::(short_help_off)?; 951 | // long help 952 | let long_help_off = write_mmv_string(ws, c, indom.longhelp(), false)?; 953 | c.write_u64::(long_help_off)?; 954 | 955 | // write instances and record their offsets 956 | let mut instance_blk_offs = HashMap::with_capacity(indom.instances.len()); 957 | for instance in &indom.instances { 958 | c.set_position(instance_blk_off); 959 | 960 | // indom offset 961 | c.write_u64::(indom_off)?; 962 | // zero pad 963 | c.write_u32::(0)?; 964 | // instance id 965 | c.write_u32::(Indom::instance_id(&instance))?; 966 | 967 | // instance 968 | match mmv_ver { 969 | Version::V1 => { 970 | c.write_all(instance.as_bytes())?; 971 | c.write_all(&[0])?; 972 | }, 973 | Version::V2 => { 974 | let instance_off = write_mmv_string(ws, c, instance, false)?; 975 | c.write_u64::(instance_off)?; 976 | } 977 | } 978 | 979 | instance_blk_offs.insert(instance.to_owned(), instance_blk_off); 980 | instance_blk_off += instance_blk_len; 981 | } 982 | 983 | ws.instance_idx += instance_blk_offs.len() as u64; 984 | ws.indom_idx += 1; 985 | 986 | let cloned_offs = instance_blk_offs.clone(); 987 | ws.indom_cache.insert(indom.id, Some(instance_blk_offs)); 988 | Ok(cloned_offs) 989 | } 990 | 991 | fn three_way_split(view: MmapViewSync, mid_idx: usize, mid_len: usize) -> io::Result<(MmapViewSync, MmapViewSync, MmapViewSync)> { 992 | let (left_view, mid_right_view) = view.split_at(mid_idx).unwrap(); 993 | let (mid_view, right_view) = mid_right_view.split_at(mid_len).unwrap(); 994 | Ok((left_view, mid_view, right_view)) 995 | } 996 | 997 | // writes `value` at end of value section, updates value count in value TOC, 998 | // and returns the offset `val` was written at and it's size - (offset, size) 999 | // 1000 | // leaves the cursor in the original position it was at when passed 1001 | fn write_value_block(ws: &mut MMVWriterState, 1002 | mut c: &mut Cursor<&mut [u8]>, value: &T, 1003 | metric_blk_off: u64, instance_blk_off: u64) -> io::Result<(usize, usize)> { 1004 | 1005 | let orig_pos = c.position(); 1006 | 1007 | let value_blk_off = 1008 | ws.value_sec_off 1009 | + ws.value_blk_idx*VALUE_BLOCK_LEN; 1010 | ws.value_blk_idx += 1; 1011 | c.set_position(value_blk_off); 1012 | 1013 | let (value_offset, value_size); 1014 | if value.type_code() == MTCode::String as u32 { 1015 | // numeric value 1016 | c.write_u64::(0)?; 1017 | 1018 | // string offset 1019 | 1020 | // we can't pass the actual `m.val()` string to write_mmv_string, 1021 | // and in order to not replicate the logic of write_mmv_string here, 1022 | // we perform an extra write of the string to a temp buffer so we 1023 | // can pass that to write_mmv_string. 1024 | let mut str_buf = [0u8; (STRING_BLOCK_LEN - 1) as usize]; 1025 | value.write(&mut (&mut str_buf as &mut [u8]))?; 1026 | 1027 | let str_val = unsafe { str::from_utf8_unchecked(&str_buf) }; 1028 | let string_val_off = write_mmv_string(ws, c, str_val, true)?; 1029 | c.write_u64::(string_val_off)?; 1030 | 1031 | value_offset = string_val_off as usize; 1032 | value_size = STRING_BLOCK_LEN as usize; 1033 | } else { 1034 | value_offset = c.position() as usize; 1035 | value_size = NUMERIC_VALUE_SIZE; 1036 | 1037 | // numeric value 1038 | value.write(&mut c)?; 1039 | // string offset 1040 | c.write_u64::(0)?; 1041 | } 1042 | // offset to metric block 1043 | c.write_u64::(metric_blk_off)?; 1044 | // offset to instance block 1045 | c.write_u64::(instance_blk_off)?; 1046 | 1047 | c.set_position(orig_pos); 1048 | Ok((value_offset, value_size)) 1049 | } 1050 | 1051 | fn cache_and_register_string(ws: &mut MMVWriterState, string: &str) { 1052 | if string.len() > 0 && !ws.non_value_string_cache.contains_key(string) { 1053 | ws.non_value_string_cache.insert(string.to_owned(), None); 1054 | ws.n_strings += 1; 1055 | } 1056 | } 1057 | 1058 | // writes `string` at end of string section, updates string count in string TOC, 1059 | // and returns the offset `string` was written at 1060 | // 1061 | // leaves the cursor in the original position it was at when passed 1062 | // 1063 | // when writing first string in MMV, also writes the string TOC block 1064 | fn write_mmv_string(ws: &mut MMVWriterState, 1065 | c: &mut Cursor<&mut [u8]>, string: &str, is_value: bool) -> io::Result { 1066 | 1067 | if string.len() == 0 { 1068 | return Ok(0); 1069 | } 1070 | 1071 | let orig_pos = c.position(); 1072 | 1073 | let string_block_off = 1074 | ws.string_sec_off 1075 | + STRING_BLOCK_LEN*ws.string_blk_idx; 1076 | 1077 | // only cache if the string is not a value 1078 | if !is_value { 1079 | if let Some(cached_offset) = ws.non_value_string_cache.get(string).clone() { 1080 | if let &Some(off) = cached_offset { 1081 | return Ok(off); 1082 | } 1083 | } 1084 | 1085 | ws.non_value_string_cache.insert(string.to_owned(), Some(string_block_off)); 1086 | } 1087 | 1088 | // write string in string section 1089 | c.set_position(string_block_off); 1090 | c.write_all(string.as_bytes())?; 1091 | c.write_all(&[0])?; 1092 | 1093 | ws.string_blk_idx += 1; 1094 | 1095 | c.set_position(orig_pos); 1096 | Ok(string_block_off) 1097 | } 1098 | 1099 | #[test] 1100 | fn test_instance_metrics() { 1101 | use super::Client; 1102 | 1103 | let caches = Indom::new( 1104 | &["L1", "L2", "L3"], 1105 | "Caches", 1106 | "Different levels of CPU caches" 1107 | ).unwrap(); 1108 | 1109 | let mut cache_sizes = InstanceMetric::new( 1110 | &caches, 1111 | "cache_size", 1112 | 0, 1113 | Semantics::Discrete, 1114 | Unit::new().space(Space::KByte, 1).unwrap(), 1115 | "Cache sizes", 1116 | "Sizes of different CPU caches" 1117 | ).unwrap(); 1118 | 1119 | assert!(cache_sizes.has_instance("L1")); 1120 | assert!(!cache_sizes.has_instance("L4")); 1121 | 1122 | assert_eq!(*cache_sizes.val("L2").unwrap(), 0); 1123 | assert!(cache_sizes.val("L5").is_none()); 1124 | 1125 | let mut cpu = Metric::new( 1126 | "cpu", 1127 | String::from("kabylake"), 1128 | Semantics::Discrete, 1129 | Unit::new(), 1130 | "CPU family", "", 1131 | ).unwrap(); 1132 | 1133 | Client::new("system").unwrap() 1134 | .export(&mut [&mut cache_sizes, &mut cpu]).unwrap(); 1135 | 1136 | assert!(cache_sizes.set_val("L3", 8192).is_some()); 1137 | assert_eq!(*cache_sizes.val("L3").unwrap(), 8192); 1138 | 1139 | assert!(cache_sizes.set_val("L4", 16384).is_none()); 1140 | } 1141 | 1142 | #[test] 1143 | fn test_units() { 1144 | assert_eq!(Unit::new().pmapi_repr, 0); 1145 | 1146 | assert_eq!( 1147 | Unit::new().space(Space::KByte, 1).unwrap().pmapi_repr, 1148 | 1 << 28 | (Space::KByte as u32) << 16 1149 | ); 1150 | assert_eq!( 1151 | Unit::new().time(Time::Min, 1).unwrap().pmapi_repr, 1152 | 1 << 24 | (Time::Min as u32) << 12 1153 | ); 1154 | assert_eq!( 1155 | Unit::new().count(Count::One, 1).unwrap().pmapi_repr, 1156 | 1 << 20 | (Count::One as u32) << 8 1157 | ); 1158 | 1159 | let (space_dim, time_dim, count_dim) = (-3, -2, 1); 1160 | let unit = Unit::new() 1161 | .space(Space::EByte, space_dim).unwrap() 1162 | .time(Time::Hour, time_dim).unwrap() 1163 | .count(Count::One, count_dim).unwrap(); 1164 | 1165 | assert_eq!(unit.pmapi_repr, 1166 | ((space_dim as u32) & ((1 << 4) - 1)) << 28 | 1167 | ((time_dim as u32) & ((1 << 4) - 1)) << 24 | 1168 | ((count_dim as u32) & ((1 << 4) - 1)) << 20 | 1169 | (Space::EByte as u32) << 16 | 1170 | (Time::Hour as u32) << 12 | 1171 | (Count::One as u32) << 8 1172 | ); 1173 | 1174 | assert!(Unit::new().space(Space::Byte, 8).is_err()); 1175 | assert!(Unit::new().time(Time::Sec, -9).is_err()); 1176 | } 1177 | 1178 | #[test] 1179 | fn test_invalid_strings() { 1180 | use rand::{thread_rng, Rng}; 1181 | 1182 | let sem = Semantics::Discrete; 1183 | let unit = Unit::new(); 1184 | 1185 | let invalid_string: String = thread_rng().gen_ascii_chars() 1186 | .take(STRING_BLOCK_LEN as usize).collect(); 1187 | 1188 | assert!(Metric::new( 1189 | &invalid_string, 0, sem, unit, "", "" 1190 | ).is_err()); 1191 | assert!(Metric::new( 1192 | "", 0, sem, unit, &invalid_string, "" 1193 | ).is_err()); 1194 | assert!(Metric::new( 1195 | "", 0, sem, unit, "", &invalid_string 1196 | ).is_err()); 1197 | 1198 | assert!(Indom::new( 1199 | &[&invalid_string], "", "" 1200 | ).is_err()); 1201 | assert!(Indom::new( 1202 | &[], &invalid_string, "" 1203 | ).is_err()); 1204 | assert!(Indom::new( 1205 | &[], "", &invalid_string, 1206 | ).is_err()); 1207 | 1208 | let indom = Indom::new(&[], "", "").unwrap(); 1209 | assert!(InstanceMetric::new( 1210 | &indom, &invalid_string, 0, sem, unit, "", "" 1211 | ).is_err()); 1212 | assert!(InstanceMetric::new( 1213 | &indom, "", 0, sem, unit, &invalid_string, "" 1214 | ).is_err()); 1215 | assert!(InstanceMetric::new( 1216 | &indom, "", 0, sem, unit, "", &invalid_string 1217 | ).is_err()); 1218 | } 1219 | 1220 | #[test] 1221 | fn test_mmv2_string_check() { 1222 | use rand::{thread_rng, Rng}; 1223 | 1224 | let sem = Semantics::Discrete; 1225 | let unit = Unit::new(); 1226 | 1227 | let mmv1_string: String = thread_rng().gen_ascii_chars() 1228 | .take((MMV1_NAME_MAX_LEN - 1) as usize).collect(); 1229 | let mmv2_string: String = thread_rng().gen_ascii_chars() 1230 | .take((STRING_BLOCK_LEN - 1) as usize).collect(); 1231 | 1232 | let mmv1_metric = Metric::new(&mmv1_string, 0, sem, unit, "", "").unwrap(); 1233 | assert_eq!(mmv1_metric.has_mmv2_string(), false); 1234 | let mmv2_metric = Metric::new(&mmv2_string, 0, sem, unit, "", "").unwrap(); 1235 | assert_eq!(mmv2_metric.has_mmv2_string(), true); 1236 | 1237 | let mmv1_indom = Indom::new(&[&mmv1_string], "", "").unwrap(); 1238 | assert_eq!(mmv1_indom.has_mmv2_string(), false); 1239 | let mmv2_indom = Indom::new(&[&mmv1_string, &mmv2_string], "", "").unwrap(); 1240 | assert_eq!(mmv2_indom.has_mmv2_string(), true); 1241 | 1242 | let mmv1_im = InstanceMetric::new(&mmv1_indom, &mmv1_string, 0, sem, unit, "", "").unwrap(); 1243 | assert_eq!(mmv1_im.has_mmv2_string(), false); 1244 | let mmv2_im = InstanceMetric::new(&mmv2_indom, &mmv1_string, 0, sem, unit, "", "").unwrap(); 1245 | assert_eq!(mmv2_im.has_mmv2_string(), true); 1246 | let mmv2_im = InstanceMetric::new(&mmv1_indom, &mmv2_string, 0, sem, unit, "", "").unwrap(); 1247 | assert_eq!(mmv2_im.has_mmv2_string(), true); 1248 | let mmv2_im = InstanceMetric::new(&mmv2_indom, &mmv2_string, 0, sem, unit, "", "").unwrap(); 1249 | assert_eq!(mmv2_im.has_mmv2_string(), true); 1250 | } 1251 | 1252 | #[test] 1253 | fn test_mmv2_string_blocks() { 1254 | use super::super::mmv::*; 1255 | use rand::{thread_rng, Rng}; 1256 | use super::Client; 1257 | 1258 | let sem = Semantics::Discrete; 1259 | let unit = Unit::new(); 1260 | 1261 | let mmv2_string: String = thread_rng().gen_ascii_chars() 1262 | .take((STRING_BLOCK_LEN - 1) as usize).collect(); 1263 | 1264 | let mut metric = Metric::new(&mmv2_string, 0, sem, unit, "", "").unwrap(); 1265 | let indom = Indom::new(&[&mmv2_string], "", "").unwrap(); 1266 | let mut im = InstanceMetric::new(&indom, &mmv2_string, 0, sem, unit, "", "").unwrap(); 1267 | 1268 | let client = Client::new("mmv2_string_blocks").unwrap(); 1269 | client.export(&mut [&mut metric, &mut im]).unwrap(); 1270 | 1271 | let mmv = dump(client.mmv_path()).unwrap(); 1272 | 1273 | for m_blk in mmv.metric_blks().values() { 1274 | match m_blk.name() { 1275 | &VersionSpecificString::String(ref s) => 1276 | panic!("metric name \"{}\" should be in string section", s), 1277 | &VersionSpecificString::Offset(ref off) => { 1278 | let string = mmv.string_blks().get(off).unwrap().string(); 1279 | assert_eq!(string, mmv2_string); 1280 | } 1281 | } 1282 | } 1283 | 1284 | for i_blk in mmv.instance_blks().values() { 1285 | match i_blk.external_id() { 1286 | &VersionSpecificString::String(ref s) => 1287 | panic!("instance \"{}\" should be in string section", s), 1288 | &VersionSpecificString::Offset(ref off) => { 1289 | let string = mmv.string_blks().get(off).unwrap().string(); 1290 | assert_eq!(string, mmv2_string); 1291 | } 1292 | } 1293 | } 1294 | } 1295 | 1296 | #[test] 1297 | fn test_random_numeric_metrics() { 1298 | use byteorder::ReadBytesExt; 1299 | use rand::{thread_rng, Rng}; 1300 | use super::Client; 1301 | 1302 | let mut metrics = Vec::new(); 1303 | let mut new_vals = Vec::new(); 1304 | let n_metrics = thread_rng().gen::() % 20; 1305 | 1306 | let client = Client::new("numeric_metrics").unwrap(); 1307 | 1308 | for _ in 1..n_metrics { 1309 | let rnd_name: String = thread_rng().gen_ascii_chars() 1310 | .take(MMV1_NAME_MAX_LEN as usize - 1).collect(); 1311 | 1312 | let rnd_shorthelp: String = thread_rng().gen_ascii_chars() 1313 | .take(STRING_BLOCK_LEN as usize - 1).collect(); 1314 | 1315 | let rnd_longhelp: String = thread_rng().gen_ascii_chars() 1316 | .take(STRING_BLOCK_LEN as usize - 1).collect(); 1317 | 1318 | let rnd_val1 = thread_rng().gen::(); 1319 | 1320 | let mut metric = Metric::new( 1321 | &rnd_name, 1322 | rnd_val1, 1323 | Semantics::Discrete, 1324 | Unit::new(), 1325 | &rnd_shorthelp, 1326 | &rnd_longhelp, 1327 | ).unwrap(); 1328 | 1329 | assert_eq!(*metric.val(), rnd_val1); 1330 | 1331 | let rnd_val2 = thread_rng().gen::(); 1332 | assert!(metric.set_val(rnd_val2).is_ok()); 1333 | assert_eq!(*metric.val(), rnd_val2); 1334 | 1335 | metrics.push(metric); 1336 | new_vals.push(thread_rng().gen::()); 1337 | } 1338 | 1339 | { // mmv_writers needs to go out of scope before we can mutate 1340 | // the metrics after exporting. The type annotation is needed 1341 | // because type inference fails. 1342 | let mut mmv_writers: Vec<&mut MMVWriter> = metrics.iter_mut() 1343 | .map(|m| m as &mut MMVWriter) 1344 | .collect(); 1345 | client.export(&mut mmv_writers).unwrap(); 1346 | } 1347 | 1348 | for (m, v) in metrics.iter_mut().zip(&mut new_vals) { 1349 | assert!(m.set_val(*v).is_ok()); 1350 | } 1351 | 1352 | for (m, v) in metrics.iter_mut().zip(new_vals) { 1353 | let mut slice = unsafe { m.mmap_view.as_slice() }; 1354 | assert_eq!(v, slice.read_u64::().unwrap() as u32); 1355 | } 1356 | } 1357 | 1358 | #[test] 1359 | fn test_simple_metrics() { 1360 | use byteorder::ReadBytesExt; 1361 | use rand::{thread_rng, Rng}; 1362 | use std::ffi::CStr; 1363 | use std::mem::transmute; 1364 | use super::Client; 1365 | 1366 | // f64 metric 1367 | let hz = Unit::new().time(Time::Sec, -1).unwrap(); 1368 | let mut freq = Metric::new( 1369 | "frequency", 1370 | thread_rng().gen::(), 1371 | Semantics::Instant, 1372 | hz, 1373 | "", "", 1374 | ).unwrap(); 1375 | 1376 | // string metric 1377 | let mut color = Metric::new( 1378 | "color", 1379 | String::from("cyan"), 1380 | Semantics::Discrete, 1381 | Unit::new(), 1382 | "Color", "", 1383 | ).unwrap(); 1384 | 1385 | // u32 metric 1386 | let mut photons = Metric::new( 1387 | "photons", 1388 | thread_rng().gen::(), 1389 | Semantics::Counter, 1390 | Unit::new().count(Count::One, 1).unwrap(), 1391 | "No. of photons", 1392 | "Number of photons emitted by source", 1393 | ).unwrap(); 1394 | 1395 | Client::new("physical_metrics").unwrap() 1396 | .export(&mut [&mut freq, &mut color, &mut photons]).unwrap(); 1397 | 1398 | let new_freq = thread_rng().gen::(); 1399 | assert!(freq.set_val(new_freq).is_ok()); 1400 | 1401 | let new_color = String::from("magenta"); 1402 | assert!(color.set_val(new_color.clone()).is_ok()); 1403 | 1404 | let new_photon_count = thread_rng().gen::(); 1405 | assert!(photons.set_val(new_photon_count).is_ok()); 1406 | 1407 | let mut freq_slice = unsafe { freq.mmap_view.as_slice() }; 1408 | assert_eq!( 1409 | new_freq, 1410 | unsafe { 1411 | transmute::(freq_slice.read_u64::().unwrap()) 1412 | } 1413 | ); 1414 | 1415 | let color_slice = unsafe { color.mmap_view.as_slice() }; 1416 | let cstr = unsafe { 1417 | CStr::from_ptr(color_slice.as_ptr() as *const i8) 1418 | }; 1419 | assert_eq!(new_color, cstr.to_str().unwrap()); 1420 | 1421 | let mut photon_slice = unsafe { photons.mmap_view.as_slice() }; 1422 | assert_eq!( 1423 | new_photon_count, 1424 | photon_slice.read_u64::().unwrap() as u32 1425 | ); 1426 | 1427 | // TODO: after implementing mmvdump functionality, test the 1428 | // bytes of the entier MMV file 1429 | } 1430 | -------------------------------------------------------------------------------- /src/client/metric/timer.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use time; 3 | use time::Tm; 4 | 5 | /// A timer metric for tracking elapsed time 6 | /// 7 | /// Internally uses a `Metric` with `Semantics::Instant` and `1` time dimension 8 | pub struct Timer { 9 | metric: Metric, 10 | time_scale: Time, 11 | start_time: Option 12 | } 13 | 14 | /// Error encountered while starting or stopping a timer 15 | #[derive(Debug)] 16 | pub enum Error { 17 | /// IO error 18 | Io(io::Error), 19 | /// Timer was already started 20 | TimerAlreadyStarted, 21 | /// Timer wasn't previously started 22 | TimerNotStarted, 23 | } 24 | 25 | impl From for Error { 26 | fn from(err: io::Error) -> Error { 27 | Error::Io(err) 28 | } 29 | } 30 | 31 | impl Timer { 32 | /// Creates a new timer metric with given time scale 33 | pub fn new(name: &str, time_scale: Time, 34 | shorthelp_text: &str, longhelp_text: &str) -> Result { 35 | 36 | let metric = Metric::new( 37 | name, 38 | 0, 39 | Semantics::Instant, 40 | Unit::new().time(time_scale, 1)?, 41 | shorthelp_text, 42 | longhelp_text 43 | )?; 44 | 45 | Ok(Timer { 46 | metric: metric, 47 | time_scale: time_scale, 48 | start_time: None 49 | }) 50 | } 51 | 52 | /// Starts the timer. Returns an error if the timer is 53 | /// already started. 54 | pub fn start(&mut self) -> Result<(), Error> { 55 | if self.start_time.is_some() { 56 | return Err(Error::TimerAlreadyStarted) 57 | } 58 | self.start_time = Some(time::now()); 59 | Ok(()) 60 | } 61 | 62 | /// Stops the timer, updates the internal metric, and 63 | /// returns the time elapsed since the last `start`. If 64 | /// the timer was stopped too early or too late such that 65 | /// the internal nanosecond, microsecond or millisecond value 66 | /// over/under-flows, then elapsed time isn't updated. 67 | pub fn stop(&mut self) -> Result { 68 | match self.start_time { 69 | Some(start_time) => { 70 | let duration = time::now() - start_time; 71 | 72 | let elapsed = match self.time_scale { 73 | Time::NSec => duration.num_nanoseconds().unwrap_or(0), 74 | Time::USec => duration.num_microseconds().unwrap_or(0), 75 | Time::MSec => duration.num_microseconds().unwrap_or(0), 76 | Time::Sec => duration.num_seconds(), 77 | Time::Min => duration.num_minutes(), 78 | Time::Hour => duration.num_hours() 79 | }; 80 | 81 | let val = *self.metric.val(); 82 | self.metric.set_val(val + elapsed)?; 83 | 84 | // we need to record the time elapsed even if stop() 85 | // was called before a single unit of time_scale passed 86 | if elapsed != 0 { 87 | self.start_time = None; 88 | } 89 | 90 | Ok(elapsed) 91 | }, 92 | None => Err(Error::TimerNotStarted) 93 | } 94 | } 95 | 96 | /// Returns the cumulative time elapsed between every 97 | /// `start` and `stop` pair. 98 | pub fn elapsed(&mut self) -> i64 { 99 | *self.metric.val() 100 | } 101 | } 102 | 103 | impl MMVWriter for Timer { 104 | private_impl!{} 105 | 106 | fn write(&mut self, ws: &mut MMVWriterState, c: &mut Cursor<&mut [u8]>, mmv_ver: Version) -> io::Result<()> { 107 | self.metric.write(ws, c, mmv_ver) 108 | } 109 | 110 | fn register(&self, ws: &mut MMVWriterState, mmv_ver: Version) { 111 | self.metric.register(ws, mmv_ver) 112 | } 113 | 114 | fn has_mmv2_string(&self) -> bool { 115 | self.metric.has_mmv2_string() 116 | } 117 | } 118 | 119 | #[test] 120 | pub fn test() { 121 | use super::super::Client; 122 | use std::thread; 123 | use std::time::Duration; 124 | 125 | let mut timer = Timer::new("timer", Time::MSec, "", "").unwrap(); 126 | assert_eq!(timer.elapsed(), 0); 127 | 128 | Client::new("timer_test").unwrap() 129 | .export(&mut [&mut timer]).unwrap(); 130 | 131 | assert!(timer.stop().is_err()); 132 | 133 | let sleep_time = 2; // seconds 134 | 135 | timer.start().unwrap(); 136 | assert!(timer.start().is_err()); 137 | thread::sleep(Duration::from_secs(sleep_time)); 138 | let elapsed1 = timer.stop().unwrap(); 139 | assert_eq!(timer.elapsed(), elapsed1); 140 | 141 | timer.start().unwrap(); 142 | thread::sleep(Duration::from_secs(sleep_time)); 143 | let elapsed2 = timer.stop().unwrap(); 144 | assert_eq!(timer.elapsed(), elapsed1 + elapsed2); 145 | } 146 | -------------------------------------------------------------------------------- /src/client/mod.rs: -------------------------------------------------------------------------------- 1 | use byteorder::WriteBytesExt; 2 | use memmap::{Mmap, Protection}; 3 | use regex::bytes::Regex; 4 | use std::env; 5 | use std::ffi::{OsStr, OsString}; 6 | use std::fmt; 7 | use std::fs; 8 | use std::fs::{File, OpenOptions}; 9 | use std::io; 10 | use std::io::{BufReader, Cursor}; 11 | use std::io::prelude::*; 12 | use std::path::{MAIN_SEPARATOR, Path, PathBuf}; 13 | use std::str; 14 | use time; 15 | 16 | use super::mmv::Version; 17 | use super::{ 18 | Endian, 19 | CLUSTER_ID_BIT_LEN, 20 | HDR_LEN, 21 | TOC_BLOCK_LEN, 22 | VALUE_BLOCK_LEN, 23 | STRING_BLOCK_LEN, 24 | INDOM_BLOCK_LEN, 25 | METRIC_BLOCK_LEN_MMV1, 26 | INSTANCE_BLOCK_LEN_MMV1, 27 | METRIC_BLOCK_LEN_MMV2, 28 | INSTANCE_BLOCK_LEN_MMV2, 29 | }; 30 | 31 | pub mod metric; 32 | use self::metric::{MMVWriter, MMVWriterState}; 33 | 34 | static PCP_TMP_DIR_KEY: &'static str = "PCP_TMP_DIR"; 35 | static MMV_DIR_SUFFIX: &'static str = "mmv"; 36 | 37 | #[cfg(unix)] 38 | fn get_process_id() -> i32 { 39 | use nix; 40 | nix::unistd::getpid() 41 | } 42 | 43 | #[cfg(windows)] 44 | fn get_process_id() -> i32 { 45 | use kernel32; 46 | unsafe { kernel32::GetCurrentProcessId() as i32 } 47 | } 48 | 49 | #[cfg(unix)] 50 | fn osstr_from_bytes(slice: &[u8]) -> &OsStr { 51 | use std::os::unix::ffi::OsStrExt; 52 | OsStr::from_bytes(slice) 53 | } 54 | 55 | #[cfg(windows)] 56 | fn osstr_from_bytes(slice: &[u8]) -> &OsStr { 57 | OsStr::new(unsafe { str::from_utf8_unchecked(slice) }) 58 | } 59 | 60 | fn get_pcp_root() -> PathBuf { 61 | match env::var_os("PCP_DIR") { 62 | Some(val) => PathBuf::from(val), 63 | None => PathBuf::from(MAIN_SEPARATOR.to_string()) 64 | } 65 | } 66 | 67 | fn init_pcp_conf(pcp_root: &Path) -> io::Result<()> { 68 | /* attempt to load variables from pcp_root/etc/pcp.conf into environment. 69 | if pcp_root/etc/pcp.conf is not a file, can't be read, or parsing it 70 | fails, we *don't* return the error */ 71 | parse_pcp_conf(pcp_root.join("etc").join("pcp.conf")).ok(); 72 | 73 | /* attempt to load variables from pcp_root/$PCP_CONF into environment. 74 | if pcp_root/$PCP_CONF is not a file, can't be read, or parsing it 75 | fails, we *do* return the error */ 76 | let pcp_conf = pcp_root 77 | .join(env::var_os("PCP_CONF").unwrap_or(OsString::new())); 78 | parse_pcp_conf(pcp_conf) 79 | } 80 | 81 | fn parse_pcp_conf>(conf_path: P) -> io::Result<()> { 82 | let pcp_conf = File::open(conf_path)?; 83 | let mut buf_reader = BufReader::new(pcp_conf); 84 | 85 | /* According to man 5 pcp.conf, syntax rules for pcp.conf are 86 | 1. general syntax is PCP_VARIABLE_NAME=value to end of line 87 | 2. blank lines and lines begining with # are ignored 88 | 3. variable names that aren't prefixed with PCP_ are silently ignored 89 | 4. there should be no space between the variable name and the literal = 90 | 5. values may contain spaces and should not be quoted 91 | */ 92 | lazy_static! { 93 | static ref RE: Regex = 94 | Regex::new("(?-u)^(PCP_[[:alnum:]_]+)=([^\"\'].*[^\"\'])\n$") 95 | .unwrap(); 96 | } 97 | 98 | let mut line = Vec::new(); 99 | while buf_reader.read_until(b'\n', &mut line)? > 0 { 100 | match RE.captures(&line) { 101 | Some(caps) => { 102 | match (caps.get(1), caps.get(2)) { 103 | (Some(key), Some(val)) => env::set_var( 104 | osstr_from_bytes(key.as_bytes()), 105 | osstr_from_bytes(val.as_bytes()), 106 | ), 107 | _ => {} 108 | } 109 | } 110 | _ => {} 111 | } 112 | line.clear(); 113 | } 114 | 115 | Ok(()) 116 | } 117 | 118 | fn get_mmv_dir() -> io::Result { 119 | let pcp_root = get_pcp_root(); 120 | let mut mmv_dir = pcp_root.clone(); 121 | 122 | mmv_dir.push(match env::var_os(PCP_TMP_DIR_KEY) { 123 | Some(val) => PathBuf::from(val), 124 | None => { 125 | 126 | init_pcp_conf(&pcp_root).ok(); 127 | 128 | /* re-check if PCP_TMP_DIR is set after parsing (any) conf files 129 | if not, default to OS-specific temp dir and set PCP_TMP_DIR 130 | so we don't enter this block again */ 131 | match env::var_os(PCP_TMP_DIR_KEY) { 132 | Some(val) => PathBuf::from(val), 133 | None => { 134 | let os_tmp_dir = env::temp_dir(); 135 | env::set_var(PCP_TMP_DIR_KEY, os_tmp_dir.as_os_str()); 136 | os_tmp_dir 137 | } 138 | } 139 | } 140 | }); 141 | 142 | mmv_dir.push(MMV_DIR_SUFFIX); 143 | fs::create_dir_all(&mmv_dir)?; 144 | 145 | Ok(mmv_dir) 146 | } 147 | 148 | bitflags! { 149 | /// Flags used to modify how a client exports metrics 150 | pub struct MMVFlags: u32 { 151 | /// Metric names aren't prefixed with MMV filename 152 | const NOPREFIX = 1; 153 | /// PID check is needed 154 | const PROCESS = 2; 155 | /// Allow "no value available" values 156 | const SENTINEL = 4; 157 | } 158 | } 159 | 160 | impl fmt::Display for MMVFlags { 161 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 162 | let mut prev_flag = false; 163 | 164 | if self.contains(NOPREFIX) { 165 | write!(f, "no prefix")?; 166 | prev_flag = true; 167 | } 168 | 169 | if self.contains(PROCESS) { 170 | if prev_flag { 171 | write!(f, ",")?; 172 | } 173 | write!(f, "process")?; 174 | prev_flag = true; 175 | } 176 | 177 | if self.contains(SENTINEL) { 178 | if prev_flag { 179 | write!(f, ",")?; 180 | } 181 | write!(f, "sentinel")?; 182 | prev_flag = true; 183 | } 184 | 185 | if !prev_flag { 186 | write!(f, "(no flags)")?; 187 | } 188 | 189 | write!(f, " (0x{:x})", self.bits()) 190 | } 191 | } 192 | 193 | /// Client used to export metrics 194 | pub struct Client { 195 | flags: MMVFlags, 196 | cluster_id: u32, 197 | mmv_path: PathBuf 198 | } 199 | 200 | impl Client { 201 | /// Creates a new client with `PROCESS` flag and `0` cluster ID 202 | pub fn new(name: &str) -> io::Result { 203 | Client::new_custom(name, PROCESS, 0) 204 | } 205 | 206 | /// Creates a new client with custom flags and cluster ID 207 | /// 208 | /// Note that only the 12 least significant bits of `cluster_id` will be 209 | /// used. 210 | pub fn new_custom(name: &str, flags: MMVFlags, cluster_id: u32) 211 | -> io::Result { 212 | let mmv_path = get_mmv_dir()?.join(name); 213 | let cluster_id = cluster_id & ((1 << CLUSTER_ID_BIT_LEN) - 1); 214 | 215 | Ok(Client { 216 | flags: flags, 217 | cluster_id: cluster_id, 218 | mmv_path: mmv_path 219 | }) 220 | } 221 | 222 | /// Exports metrics to an MMV file at `mmv_path` 223 | /// 224 | /// If an MMV file is already present at `mmv_path`, it's overwritten 225 | /// with the newer metrics. 226 | pub fn export(&self, metrics: &mut [&mut MMVWriter]) -> io::Result<()> { 227 | let mut ws = MMVWriterState::new(); 228 | 229 | let mut mmv_ver = Version::V1; 230 | for m in metrics.iter() { 231 | if m.has_mmv2_string() { 232 | mmv_ver = Version::V2; 233 | break; 234 | } 235 | } 236 | 237 | for m in metrics.iter() { 238 | m.register(&mut ws, mmv_ver); 239 | } 240 | 241 | if ws.n_metrics > 0 { 242 | ws.n_toc += 2 /* Metric and Value TOC */; 243 | } 244 | 245 | if ws.n_strings > 0 { 246 | ws.n_toc += 1 /* String TOC */; 247 | } 248 | 249 | if ws.n_indoms > 0 { 250 | ws.n_toc += 2 /* Indom and Instance TOC */; 251 | } 252 | 253 | /* 254 | MMV layout: 255 | 256 | -- MMV Header 257 | 258 | -- Instance Domain TOC Block 259 | -- Instances TOC Block 260 | -- Metrics TOC Block 261 | -- Values TOC Block 262 | -- Strings TOC Block 263 | 264 | -- Instance Domain section 265 | -- Instances section 266 | -- Metrics section 267 | -- Values section 268 | -- Strings section 269 | 270 | After writing, every metric is given ownership 271 | of the respective memory-mapped slice that contains 272 | the metric's value. This is to ensure that the metric 273 | is *only* able to write to it's value's slice when updating 274 | it's value. 275 | */ 276 | 277 | let hdr_toc_len = HDR_LEN + TOC_BLOCK_LEN*ws.n_toc; 278 | 279 | ws.indom_sec_off = hdr_toc_len; 280 | ws.instance_sec_off = 281 | ws.indom_sec_off 282 | + INDOM_BLOCK_LEN*ws.n_indoms; 283 | 284 | let (instance_blk_len, metric_blk_len) = match mmv_ver { 285 | Version::V1 => (INSTANCE_BLOCK_LEN_MMV1, METRIC_BLOCK_LEN_MMV1), 286 | Version::V2 => (INSTANCE_BLOCK_LEN_MMV2, METRIC_BLOCK_LEN_MMV2) 287 | }; 288 | 289 | ws.metric_sec_off = 290 | ws.instance_sec_off 291 | + instance_blk_len*ws.n_instances; 292 | ws.value_sec_off = 293 | ws.metric_sec_off 294 | + metric_blk_len*ws.n_metrics; 295 | ws.string_sec_off = 296 | ws.value_sec_off 297 | + VALUE_BLOCK_LEN*ws.n_values; 298 | 299 | let mmv_size = ( 300 | ws.string_sec_off 301 | + STRING_BLOCK_LEN*ws.n_strings 302 | ) as usize; 303 | 304 | let mut file = OpenOptions::new() 305 | .read(true) 306 | .write(true) 307 | .create(true) 308 | .truncate(true) 309 | .open(&self.mmv_path)?; 310 | 311 | file.write(&vec![0; mmv_size])?; 312 | 313 | ws.mmap_view = Some( 314 | Mmap::open(&file, Protection::ReadWrite)?.into_view_sync() 315 | ); 316 | 317 | let mut mmap_view = unsafe { ws.mmap_view.as_mut().unwrap().clone() }; 318 | let mut c = Cursor::new(unsafe { mmap_view.as_mut_slice() }); 319 | 320 | ws.flags = self.flags.bits(); 321 | ws.cluster_id = self.cluster_id; 322 | write_mmv_header(&mut ws, &mut c, mmv_ver)?; 323 | 324 | write_toc_block(1, ws.n_indoms as u32, ws.indom_sec_off, &mut c)?; 325 | write_toc_block(2, ws.n_instances as u32, ws.instance_sec_off, &mut c)?; 326 | write_toc_block(3, ws.n_metrics as u32, ws.metric_sec_off, &mut c)?; 327 | write_toc_block(4, ws.n_values as u32, ws.value_sec_off, &mut c)?; 328 | write_toc_block(5, ws.n_strings as u32, ws.string_sec_off, &mut c)?; 329 | 330 | for m in metrics.iter_mut() { 331 | m.write(&mut ws, &mut c, mmv_ver)?; 332 | } 333 | 334 | // unlock header; has to be done last 335 | c.set_position(ws.gen2_off); 336 | c.write_i64::(ws.gen)?; 337 | 338 | Ok(()) 339 | } 340 | 341 | /// Returns the cluster ID of the MMV file 342 | pub fn cluster_id(&self) -> u32 { 343 | self.cluster_id 344 | } 345 | 346 | /// Returns the absolute filesystem path of the MMV file 347 | pub fn mmv_path(&self) -> &Path { 348 | self.mmv_path.as_path() 349 | } 350 | } 351 | 352 | fn write_mmv_header(ws: &mut MMVWriterState, c: &mut Cursor<&mut [u8]>, mmv_ver: Version) -> io::Result<()> { 353 | // MMV\0 354 | c.write_all(b"MMV\0")?; 355 | 356 | // version 357 | match mmv_ver { 358 | Version::V1 => c.write_u32::(1)?, 359 | Version::V2 => c.write_u32::(2)? 360 | } 361 | 362 | // generation1 363 | ws.gen = time::now().to_timespec().sec; 364 | c.write_i64::(ws.gen)?; 365 | // generation2 366 | ws.gen2_off = c.position(); 367 | c.write_i64::(0)?; 368 | // no. of toc blocks 369 | c.write_u32::(ws.n_toc as u32)?; 370 | // flags 371 | c.write_u32::(ws.flags)?; 372 | // pid 373 | c.write_i32::(get_process_id())?; 374 | // cluster id 375 | c.write_u32::(ws.cluster_id) 376 | } 377 | 378 | fn write_toc_block(sec: u32, entries: u32, sec_off: u64, c: &mut Cursor<&mut [u8]>) -> io::Result<()> { 379 | if entries > 0 { 380 | // section type 381 | c.write_u32::(sec)?; 382 | // no. of entries 383 | c.write_u32::(entries)?; 384 | // section offset 385 | c.write_u64::(sec_off)?; 386 | } 387 | Ok(()) 388 | } 389 | 390 | #[test] 391 | fn test_mmv_header() { 392 | use byteorder::ReadBytesExt; 393 | use rand::{thread_rng, Rng}; 394 | 395 | let cluster_id = thread_rng().gen::(); 396 | let flags = PROCESS | SENTINEL; 397 | let client = Client::new_custom("mmv_header_test", flags, cluster_id).unwrap(); 398 | 399 | client.export(&mut[]).unwrap(); 400 | 401 | let mut file = File::open(client.mmv_path()).unwrap(); 402 | let mut header = Vec::new(); 403 | assert!( 404 | HDR_LEN as usize 405 | <= file.read_to_end(&mut header).unwrap() 406 | ); 407 | 408 | let mut cursor = Cursor::new(header); 409 | 410 | // test "MMV\0" 411 | assert_eq!('M' as u8, cursor.read_u8().unwrap()); 412 | assert_eq!('M' as u8, cursor.read_u8().unwrap()); 413 | assert_eq!('V' as u8, cursor.read_u8().unwrap()); 414 | assert_eq!(0, cursor.read_u8().unwrap()); 415 | // test version number 416 | assert_eq!(1, cursor.read_u32::().unwrap()); 417 | // test generation 418 | assert_eq!( 419 | cursor.read_i64::().unwrap(), 420 | cursor.read_i64::().unwrap() 421 | ); 422 | // test no. of toc blocks 423 | assert_eq!(0, cursor.read_i32::().unwrap()); 424 | // test flags 425 | assert_eq!(flags.bits(), cursor.read_u32::().unwrap()); 426 | // test pid 427 | assert_eq!(get_process_id(), cursor.read_i32::().unwrap()); 428 | // cluster id 429 | assert_eq!(client.cluster_id(), cursor.read_u32::().unwrap()); 430 | } 431 | 432 | #[test] 433 | fn test_mmv_dir() { 434 | let pcp_root = get_pcp_root(); 435 | let mmv_dir = get_mmv_dir().unwrap(); 436 | let tmp_dir = PathBuf::from( 437 | env::var_os(PCP_TMP_DIR_KEY) 438 | .expect(&format!("{} not set", PCP_TMP_DIR_KEY)) 439 | ); 440 | 441 | assert!(mmv_dir.is_dir()); 442 | assert_eq!(mmv_dir, pcp_root.join(tmp_dir).join(MMV_DIR_SUFFIX)); 443 | } 444 | 445 | #[test] 446 | fn test_init_pcp_conf() { 447 | let conf_keys = vec!( 448 | "PCP_VERSION", 449 | "PCP_USER", 450 | "PCP_GROUP", 451 | "PCP_PLATFORM", 452 | "PCP_PLATFORM_PATHS", 453 | "PCP_ETC_DIR", 454 | "PCP_SYSCONF_DIR", 455 | "PCP_SYSCONFIG_DIR", 456 | "PCP_RC_DIR", 457 | "PCP_BIN_DIR", 458 | "PCP_BINADM_DIR", 459 | "PCP_LIB_DIR", 460 | "PCP_LIB32_DIR", 461 | "PCP_SHARE_DIR", 462 | "PCP_INC_DIR", 463 | "PCP_MAN_DIR", 464 | "PCP_PMCDCONF_PATH", 465 | "PCP_PMCDOPTIONS_PATH", 466 | "PCP_PMCDRCLOCAL_PATH", 467 | "PCP_PMPROXYOPTIONS_PATH", 468 | "PCP_PMWEBDOPTIONS_PATH", 469 | "PCP_PMMGROPTIONS_PATH", 470 | "PCP_PMIECONTROL_PATH", 471 | "PCP_PMSNAPCONTROL_PATH", 472 | "PCP_PMLOGGERCONTROL_PATH", 473 | "PCP_PMDAS_DIR", 474 | "PCP_RUN_DIR", 475 | "PCP_PMDAS_DIR", 476 | "PCP_LOG_DIR", 477 | "PCP_TMP_DIR", 478 | "PCP_TMPFILE_DIR", 479 | "PCP_DOC_DIR", 480 | "PCP_DEMOS_DIR", 481 | ); 482 | 483 | let pcp_root = get_pcp_root(); 484 | if init_pcp_conf(&pcp_root).is_ok() { 485 | for key in conf_keys.iter() { 486 | env::var(key).expect(&format!("{} not set", key)); 487 | } 488 | } 489 | } 490 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate byteorder; 2 | extern crate hdrsample; 3 | extern crate memmap; 4 | extern crate regex; 5 | extern crate time; 6 | #[macro_use] extern crate bitflags; 7 | #[macro_use] extern crate lazy_static; 8 | #[cfg(test)] extern crate rand; 9 | #[cfg(unix)] extern crate nix; 10 | #[cfg(windows)] extern crate kernel32; 11 | 12 | const CLUSTER_ID_BIT_LEN: usize = 12; 13 | const ITEM_BIT_LEN: usize = 10; 14 | const INDOM_BIT_LEN: usize = 22; 15 | 16 | const HDR_LEN: u64 = 40; 17 | const TOC_BLOCK_LEN: u64 = 16; 18 | const INDOM_BLOCK_LEN: u64 = 32; 19 | const VALUE_BLOCK_LEN: u64 = 32; 20 | const NUMERIC_VALUE_SIZE: usize = 8; 21 | const STRING_BLOCK_LEN: u64 = 256; 22 | 23 | const INSTANCE_BLOCK_LEN_MMV1: u64 = 80; 24 | const METRIC_BLOCK_LEN_MMV1: u64 = 104; 25 | const MMV1_NAME_MAX_LEN: u64 = 64; 26 | 27 | const INSTANCE_BLOCK_LEN_MMV2: u64 = 24; 28 | const METRIC_BLOCK_LEN_MMV2: u64 = 48; 29 | 30 | type Endian = byteorder::LittleEndian; 31 | 32 | #[macro_use] 33 | mod private; 34 | 35 | pub mod client; 36 | pub mod mmv; 37 | -------------------------------------------------------------------------------- /src/mmv/mmvfmt.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use super::super::client::MMVFlags; 3 | use super::super::client::metric::{Semantics, Unit}; 4 | use std::mem; 5 | 6 | impl fmt::Display for Header { 7 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 8 | writeln!(f, "Version = {}", self.version() as u32)?; 9 | writeln!(f, "Generated = {}", self.gen1())?; 10 | writeln!(f, "TOC count = {}", self.toc_count())?; 11 | writeln!(f, "Cluster = {}", self.cluster_id())?; 12 | writeln!(f, "Process = {}", self.pid())?; 13 | writeln!(f, "Flags = {}", MMVFlags::from_bits_truncate(self.flags())) 14 | } 15 | } 16 | 17 | fn write_indoms(f: &mut fmt::Formatter, indom_toc: &TocBlk, mmv: &MMV) -> fmt::Result { 18 | writeln!(f, "TOC[{}]: toc offset {}, indoms offset {} ({} entries)", 19 | indom_toc._toc_index(), indom_toc._mmv_offset(), indom_toc.sec_offset(), indom_toc.entries())?; 20 | 21 | for (offset, indom) in mmv.indom_blks() { 22 | if let Some(ref indom_id) = *indom.indom() { 23 | write!(f, " [{}/{}] {} instances, starting at offset ", 24 | indom_id, offset, indom.instances())?; 25 | match *indom.instances_offset() { 26 | Some(ref instances_offset) => writeln!(f, "{}", instances_offset)?, 27 | None => writeln!(f, "(no instances)")? 28 | } 29 | 30 | write!(f, " ")?; 31 | match *indom.short_help_offset() { 32 | Some(ref short_help_offset) => { 33 | let shortext = mmv.string_blks().get(short_help_offset).unwrap().string(); 34 | writeln!(f, "shorttext={}", shortext)?; 35 | } 36 | None => writeln!(f, "(no shorttext)")? 37 | } 38 | 39 | write!(f, " ")?; 40 | match *indom.long_help_offset() { 41 | Some(ref long_help_offset) => { 42 | let longtext = mmv.string_blks().get(long_help_offset).unwrap().string(); 43 | writeln!(f, "longtext={}", longtext)? 44 | } 45 | None => writeln!(f, "(no longtext)")? 46 | } 47 | } 48 | } 49 | 50 | Ok(()) 51 | } 52 | 53 | // note: doesn't write newline at the end 54 | fn write_version_specific_string(f: &mut fmt::Formatter, string: &VersionSpecificString, mmv: &MMV) -> fmt::Result { 55 | match string { 56 | &VersionSpecificString::String(ref string) => write!(f, "{}", string), 57 | &VersionSpecificString::Offset(ref offset) => { 58 | let string = mmv.string_blks().get(offset).unwrap().string(); 59 | write!(f, "{}", string) 60 | } 61 | } 62 | } 63 | 64 | fn write_instances(f: &mut fmt::Formatter, instance_toc: &TocBlk, mmv: &MMV) -> fmt::Result { 65 | writeln!(f, "TOC[{}]: toc offset {}, instances offset {} ({} entries)", 66 | instance_toc._toc_index(), instance_toc._mmv_offset(), instance_toc.sec_offset(), instance_toc.entries())?; 67 | 68 | for (offset, instance) in mmv.instance_blks() { 69 | write!(f, " ")?; 70 | match *instance.indom_offset() { 71 | Some(ref indom_offset) => { 72 | let indom = mmv.indom_blks().get(indom_offset).unwrap(); 73 | match *indom.indom() { 74 | Some(ref indom_id) => write!(f, "[{}", indom_id)?, 75 | None => write!(f, "[(no indom)")? 76 | } 77 | }, 78 | None => write!(f, "[(no indom)")? 79 | } 80 | write!(f, "/{}] instance = [{} or \"", offset, instance.internal_id())?; 81 | write_version_specific_string(f, instance.external_id(), mmv)?; 82 | writeln!(f, "\"]")?; 83 | } 84 | 85 | Ok(()) 86 | } 87 | 88 | fn write_metrics(f: &mut fmt::Formatter, metric_toc: &TocBlk, mmv: &MMV) -> fmt::Result { 89 | writeln!(f, "TOC[{}]: toc offset {}, metrics offset {} ({} entries)", 90 | metric_toc._toc_index(), metric_toc._mmv_offset(), metric_toc.sec_offset(), metric_toc.entries())?; 91 | 92 | for (offset, metric) in mmv.metric_blks() { 93 | if let Some(item) = *metric.item() { 94 | write!(f, " [{}/{}] ", item, offset)?; 95 | write_version_specific_string(f, metric.name(), mmv)?; 96 | writeln!(f, "")?; 97 | 98 | write!(f, " ")?; 99 | match MTCode::from_u32(metric.typ()) { 100 | Some(mtcode) => write!(f, "type={}", mtcode)?, 101 | None => write!(f, "(invalid type)")? 102 | } 103 | write!(f, ", ")?; 104 | match Semantics::from_u32(metric.sem()) { 105 | Some(sem) => write!(f, "sem={}", sem)?, 106 | None => write!(f, "(invalid semantics)")? 107 | } 108 | write!(f, ", ")?; 109 | writeln!(f, "pad=0x{:x}", metric.pad())?; 110 | 111 | writeln!(f, " unit={}", Unit::from_raw(metric.unit()))?; 112 | 113 | write!(f, " ")?; 114 | match *metric.indom() { 115 | Some(indom) => writeln!(f, "indom={}", indom)?, 116 | None => writeln!(f, "(no indom)")? 117 | } 118 | 119 | write!(f, " ")?; 120 | match *metric.short_help_offset() { 121 | Some(ref short_help_offset) => { 122 | let shortext = mmv.string_blks().get(short_help_offset).unwrap().string(); 123 | writeln!(f, "shorttext={}", shortext)?; 124 | } 125 | None => writeln!(f, "(no shorttext)")? 126 | } 127 | 128 | write!(f, " ")?; 129 | match *metric.long_help_offset() { 130 | Some(ref long_help_offset) => { 131 | let longtext = mmv.string_blks().get(long_help_offset).unwrap().string(); 132 | writeln!(f, "longtext={}", longtext)?; 133 | } 134 | None => writeln!(f, "(no longtext)")? 135 | } 136 | } 137 | } 138 | 139 | Ok(()) 140 | } 141 | 142 | fn write_values(f: &mut fmt::Formatter, value_toc: &TocBlk, mmv: &MMV) -> fmt::Result { 143 | writeln!(f, "TOC[{}]: toc offset {}, values offset {} ({} entries)", 144 | value_toc._toc_index(), value_toc._mmv_offset(), value_toc.sec_offset(), value_toc.entries())?; 145 | 146 | for (offset, value) in mmv.value_blks() { 147 | if let Some(ref metric_offset) = *value.metric_offset() { 148 | let metric = mmv.metric_blks().get(&metric_offset).unwrap(); 149 | if let Some(item) = *metric.item() { 150 | write!(f, " [{}/{}] ", item, offset)?; 151 | write_version_specific_string(f, metric.name(), mmv)?; 152 | 153 | if let Some(ref instance_offset) = *value.instance_offset() { 154 | let instance = mmv.instance_blks().get(&instance_offset).unwrap(); 155 | write!(f, "[{} or \"", instance.internal_id())?; 156 | write_version_specific_string(f, instance.external_id(), mmv)?; 157 | write!(f, "\"]")?; 158 | } 159 | 160 | write!(f, " = ")?; 161 | match *value.string_offset() { 162 | Some(ref string_offset) => { 163 | let string = mmv.string_blks().get(string_offset).unwrap(); 164 | writeln!(f, "\"{}\"", string.string())?; 165 | } 166 | None => { 167 | match MTCode::from_u32(metric.typ()) { 168 | Some(mtcode) => { 169 | match mtcode { 170 | MTCode::U64 | MTCode::U32 => writeln!(f, "{}", value.value())?, 171 | MTCode::I64 => writeln!(f, "{}", value.value() as i64)?, 172 | MTCode::I32 => writeln!(f, "{}", value.value() as i32)?, 173 | MTCode::F32 => { 174 | let float = unsafe { 175 | mem::transmute::(value.value() as u32) 176 | }; 177 | writeln!(f, "{}", float)? 178 | }, 179 | MTCode::F64 => { 180 | let double = unsafe { 181 | mem::transmute::(value.value()) 182 | }; 183 | writeln!(f, "{}", double)? 184 | }, 185 | MTCode::String => writeln!(f, "(no string offset)")?, 186 | } 187 | }, 188 | None => writeln!(f, "{}", value.value())? 189 | } 190 | }, 191 | } 192 | } 193 | } 194 | } 195 | 196 | Ok(()) 197 | } 198 | 199 | fn write_strings(f: &mut fmt::Formatter, string_toc: &TocBlk, mmv: &MMV) -> fmt::Result { 200 | writeln!(f, "TOC[{}]: toc offset {}, strings offset {} ({} entries)", 201 | string_toc._toc_index(), string_toc._mmv_offset(), string_toc.sec_offset(), string_toc.entries())?; 202 | 203 | for (i, (offset, string)) in mmv.string_blks().iter().enumerate() { 204 | writeln!(f, " [{}/{}] {}", i+1, offset, string.string())?; 205 | } 206 | 207 | Ok(()) 208 | } 209 | 210 | impl fmt::Display for MMV { 211 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 212 | writeln!(f, "{}", self.header)?; 213 | 214 | if let Some(ref indom_toc) = self.indom_toc { 215 | write_indoms(f, indom_toc, self)?; 216 | writeln!(f, "")?; 217 | } 218 | 219 | if let Some(ref instance_toc) = self.instance_toc { 220 | write_instances(f, instance_toc, self)?; 221 | writeln!(f, "")?; 222 | } 223 | 224 | write_metrics(f, &self.metric_toc, self)?; 225 | writeln!(f, "")?; 226 | 227 | write_values(f, &self.value_toc, self)?; 228 | writeln!(f, "")?; 229 | 230 | if let Some(ref string_toc) = self.string_toc { 231 | write_strings(f, string_toc, self)?; 232 | writeln!(f, "")?; 233 | } 234 | 235 | Ok(()) 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/mmv/mod.rs: -------------------------------------------------------------------------------- 1 | use byteorder::ReadBytesExt; 2 | use std::collections::BTreeMap; 3 | use std::ffi::CStr; // Used to read null-terminated strings in MMV files 4 | use std::fmt; 5 | use std::fs::File; 6 | use std::io; 7 | use std::io::Cursor; 8 | use std::io::prelude::*; 9 | use std::path::Path; 10 | use std::str; 11 | 12 | mod mmvfmt; 13 | 14 | const INDOM_TOC_CODE: u32 = 1; 15 | const INSTANCE_TOC_CODE: u32 = 2; 16 | const METRIC_TOC_CODE: u32 = 3; 17 | const VALUES_TOC_CODE: u32 = 4; 18 | const STRINGS_TOC_CODE: u32 = 5; 19 | 20 | #[derive(Copy, Clone)] 21 | /// MMV code for a metric type 22 | /// 23 | /// For reference to the C API, see 24 | /// https://github.com/performancecopilot/pcp/blob/master/src/include/pcp/pmapi.h#L113 25 | pub enum MTCode { 26 | /// 32-bit signed integer 27 | I32 = 0, 28 | /// 32-bit unsigned integer 29 | U32, 30 | /// 64-bit signed integer 31 | I64, 32 | /// 64-bit unsigned integer 33 | U64, 34 | /// 32-bit float 35 | F32, 36 | /// 64-bit double 37 | F64, 38 | /// String 39 | String 40 | } 41 | 42 | impl MTCode { 43 | pub fn from_u32(x: u32) -> Option { 44 | match x { 45 | 0 => Some(MTCode::I32), 46 | 1 => Some(MTCode::U32), 47 | 2 => Some(MTCode::I64), 48 | 3 => Some(MTCode::U64), 49 | 4 => Some(MTCode::F32), 50 | 5 => Some(MTCode::F64), 51 | 6 => Some(MTCode::String), 52 | _ => None 53 | } 54 | } 55 | } 56 | 57 | impl fmt::Display for MTCode { 58 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 59 | match *self { 60 | MTCode::I32 => write!(f, "Int32")?, 61 | MTCode::U32 => write!(f, "Uint32")?, 62 | MTCode::I64 => write!(f, "Int64")?, 63 | MTCode::U64 => write!(f, "Uint64")?, 64 | MTCode::F32 => write!(f, "Float32")?, 65 | MTCode::F64 => write!(f, "Double64")?, 66 | MTCode::String => write!(f, "String")? 67 | } 68 | write!(f, " (0x{:x})", *self as u32) 69 | } 70 | } 71 | 72 | use super::{ 73 | Endian, 74 | MMV1_NAME_MAX_LEN, 75 | STRING_BLOCK_LEN, 76 | CLUSTER_ID_BIT_LEN, 77 | ITEM_BIT_LEN, 78 | INDOM_BIT_LEN 79 | }; 80 | 81 | fn is_valid_indom(indom: u32) -> bool { 82 | indom != 0 && (indom >> INDOM_BIT_LEN) == 0 83 | } 84 | 85 | fn is_valid_item(item: u32) -> bool { 86 | item != 0 && (item >> ITEM_BIT_LEN) == 0 87 | } 88 | 89 | fn is_valid_cluster_id(cluster_id: u32) -> bool { 90 | (cluster_id >> CLUSTER_ID_BIT_LEN) == 0 91 | } 92 | 93 | fn is_valid_blk_offset(offset: u64) -> bool { 94 | offset != 0 95 | } 96 | 97 | /// Error encountered while reading and parsing an MMV 98 | #[derive(Debug)] 99 | pub enum MMVDumpError { 100 | /// Invalid bytes in MMV 101 | InvalidMMV(String), 102 | /// IO error while reading MMV 103 | Io(io::Error), 104 | /// UTF-8 error while parsing MMV strings 105 | Utf8(str::Utf8Error) 106 | } 107 | 108 | impl From for MMVDumpError { 109 | fn from(err: io::Error) -> MMVDumpError { 110 | MMVDumpError::Io(err) 111 | } 112 | } 113 | 114 | impl From for MMVDumpError { 115 | fn from(err: str::Utf8Error) -> MMVDumpError { 116 | MMVDumpError::Utf8(err) 117 | } 118 | } 119 | 120 | macro_rules! return_mmvdumperror ( 121 | ($err:expr, $val:expr) => ( 122 | let mut err_str = $err.to_owned(); 123 | err_str.push_str(&format!(": {}", $val)); 124 | return Err(MMVDumpError::InvalidMMV(err_str)); 125 | ) 126 | ); 127 | 128 | /// Top-level MMV structure 129 | /// 130 | /// The various data blocks are stored in BTreeMaps; the key for each 131 | /// block is it's offset in the MMV 132 | pub struct MMV { 133 | header: Header, 134 | metric_toc: TocBlk, 135 | value_toc: TocBlk, 136 | string_toc: Option, 137 | indom_toc: Option, 138 | instance_toc: Option, 139 | metric_blks: BTreeMap, 140 | value_blks: BTreeMap, 141 | string_blks: BTreeMap, 142 | indom_blks: BTreeMap, 143 | instance_blks: BTreeMap 144 | } 145 | 146 | impl MMV { 147 | pub fn header(&self) -> &Header { &self.header } 148 | pub fn metric_toc(&self) -> &TocBlk { &self.metric_toc } 149 | pub fn value_toc(&self) -> &TocBlk { &self.value_toc } 150 | pub fn string_toc(&self) -> &Option { &self.string_toc } 151 | pub fn indom_toc(&self) -> &Option { &self.indom_toc } 152 | pub fn instance_toc(&self) -> &Option { &self.instance_toc } 153 | pub fn metric_blks(&self) -> &BTreeMap { &self.metric_blks } 154 | pub fn value_blks(&self) -> &BTreeMap { &self.value_blks } 155 | pub fn string_blks(&self) -> &BTreeMap { &self.string_blks } 156 | pub fn indom_blks(&self) -> &BTreeMap { &self.indom_blks } 157 | pub fn instance_blks(&self) -> &BTreeMap { &self.instance_blks } 158 | } 159 | 160 | #[derive(Copy, Clone)] 161 | /// MMV version 162 | pub enum Version { 163 | /// Version 1 164 | V1 = 1, 165 | /// Version 2 166 | V2 = 2 167 | } 168 | 169 | impl Version { 170 | pub fn from_u32(x: u32) -> Option { 171 | match x { 172 | 1 => Some(Version::V1), 173 | 2 => Some(Version::V2), 174 | _ => None 175 | } 176 | } 177 | } 178 | 179 | /// MMV header structure 180 | /// 181 | /// For reference to the C API, see 182 | /// https://github.com/performancecopilot/pcp/blob/master/src/include/pcp/mmv_dev.h#L95 183 | pub struct Header { 184 | pub magic: [u8; 4], 185 | pub version: Version, 186 | pub gen1: i64, 187 | pub gen2: i64, 188 | pub toc_count: u32, 189 | pub flags: u32, 190 | pub pid: i32, 191 | pub cluster_id: u32, 192 | } 193 | 194 | impl Header { 195 | pub fn magic(&self) -> &[u8; 4] { &self.magic } 196 | pub fn version(&self) -> Version { self.version } 197 | pub fn gen1(&self) -> i64 { self.gen1 } 198 | pub fn gen2(&self) -> i64 { self.gen2 } 199 | pub fn toc_count(&self) -> u32 { self.toc_count } 200 | pub fn flags(&self) -> u32 { self.flags } 201 | pub fn pid(&self) -> i32 { self.pid } 202 | pub fn cluster_id(&self) -> u32 { self.cluster_id } 203 | } 204 | 205 | impl Header { 206 | fn from_reader(r: &mut R) -> Result { 207 | let mut magic = [0; 4]; 208 | magic[0] = r.read_u8()?; 209 | magic[1] = r.read_u8()?; 210 | magic[2] = r.read_u8()?; 211 | magic[3] = r.read_u8()?; 212 | if magic != [b'M', b'M', b'V', 0] { 213 | return_mmvdumperror!("Invalid MMV", 0); 214 | } 215 | 216 | let version = r.read_u32::()?; 217 | let mmv_ver = match Version::from_u32(version) { 218 | Some(ver) => ver, 219 | None => { 220 | return_mmvdumperror!("Invalid version number", version); 221 | } 222 | }; 223 | 224 | let gen1 = r.read_i64::()?; 225 | let gen2 = r.read_i64::()?; 226 | if gen1 != gen2 { 227 | return_mmvdumperror!("Generation timestamps don't match", 0); 228 | } 229 | 230 | let toc_count = r.read_u32::()?; 231 | if toc_count > 5 || toc_count < 2 { 232 | return_mmvdumperror!("Invalid TOC count", toc_count); 233 | } 234 | 235 | let flags = r.read_u32::()?; 236 | let pid = r.read_i32::()?; 237 | 238 | let cluster_id = r.read_u32::()?; 239 | if !is_valid_cluster_id(cluster_id) { 240 | return_mmvdumperror!("Invalid cluster ID", cluster_id); 241 | } 242 | 243 | Ok(Header { 244 | magic: magic, 245 | version: mmv_ver, 246 | gen1: gen1, 247 | gen2: gen2, 248 | toc_count: toc_count, 249 | flags: flags, 250 | pid: pid, 251 | cluster_id: cluster_id 252 | }) 253 | } 254 | } 255 | 256 | /// MMV Table-of-Contents structure 257 | /// 258 | /// For reference to the C API, see 259 | /// https://github.com/performancecopilot/pcp/blob/master/src/include/pcp/mmv_dev.h#L32 260 | pub struct TocBlk { 261 | _toc_index: u32, 262 | _mmv_offset: u64, 263 | sec: u32, 264 | entries: u32, 265 | sec_offset: u64 266 | } 267 | 268 | impl TocBlk { 269 | pub fn _toc_index(&self) -> u32 { self._toc_index } 270 | pub fn _mmv_offset(&self) -> u64 { self._mmv_offset } 271 | pub fn sec(&self) -> u32 { self.sec } 272 | pub fn entries(&self) -> u32 { self.entries } 273 | pub fn sec_offset(&self) -> u64 { self.sec_offset } 274 | } 275 | 276 | impl TocBlk { 277 | fn from_reader(r: &mut R) -> Result { 278 | let sec = r.read_u32::()?; 279 | if sec > 5 { 280 | return_mmvdumperror!("Invalid TOC type", sec); 281 | } 282 | 283 | let entries = r.read_u32::()?; 284 | 285 | let sec_offset = r.read_u64::()?; 286 | if !is_valid_blk_offset(sec_offset) { 287 | return_mmvdumperror!("Invalid section offset", sec_offset); 288 | } 289 | 290 | Ok(TocBlk { 291 | _toc_index: 0, 292 | _mmv_offset: 0, 293 | sec: sec, 294 | entries: entries, 295 | sec_offset: sec_offset 296 | }) 297 | } 298 | } 299 | 300 | /// String whose MMV representation depends on the MMV version 301 | pub enum VersionSpecificString { 302 | /// MMV version 1 direct string 303 | String(String), 304 | /// MMV version 2 offset to string block 305 | Offset(u64) 306 | } 307 | 308 | /// Metric block structure 309 | /// 310 | /// For reference to the C API, see 311 | /// https://github.com/performancecopilot/pcp/blob/master/src/include/pcp/mmv_dev.h#L64 312 | pub struct MetricBlk { 313 | name: VersionSpecificString, 314 | item: Option, 315 | typ: u32, 316 | sem: u32, 317 | unit: u32, 318 | indom: Option, 319 | pad: u32, 320 | short_help_offset: Option, 321 | long_help_offset: Option 322 | } 323 | 324 | impl MetricBlk { 325 | pub fn name(&self) -> &VersionSpecificString { &self.name } 326 | pub fn item(&self) -> &Option { &self.item } 327 | pub fn typ(&self) -> u32 { self.typ } 328 | pub fn sem(&self) -> u32 { self.sem } 329 | pub fn unit(&self) -> u32 { self.unit } 330 | pub fn indom(&self) -> &Option { &self.indom } 331 | pub fn pad(&self) -> u32 { self.pad } 332 | pub fn short_help_offset(&self) -> &Option { &self.short_help_offset } 333 | pub fn long_help_offset(&self) -> &Option { &self.long_help_offset } 334 | } 335 | 336 | impl MetricBlk { 337 | fn from_reader(r: &mut R, ver: Version) -> Result { 338 | let name = match ver { 339 | Version::V1 => { 340 | let mut name_bytes = [0; MMV1_NAME_MAX_LEN as usize]; 341 | r.read_exact(&mut name_bytes)?; 342 | let cstr = unsafe { 343 | CStr::from_ptr(name_bytes.as_ptr() as *const i8) 344 | }; 345 | VersionSpecificString::String(cstr.to_str()?.to_owned()) 346 | }, 347 | Version::V2 => { 348 | VersionSpecificString::Offset(r.read_u64::()?) 349 | } 350 | }; 351 | 352 | let item = r.read_u32::()?; 353 | let typ = r.read_u32::()?; 354 | let sem = r.read_u32::()?; 355 | let unit = r.read_u32::()?; 356 | let indom = r.read_u32::()?; 357 | 358 | let pad = r.read_u32::()?; 359 | if pad != 0 { 360 | return_mmvdumperror!("Invalid pad bytes", pad); 361 | } 362 | 363 | let short_help_offset = r.read_u64::()?; 364 | let long_help_offset = r.read_u64::()?; 365 | 366 | Ok(MetricBlk { 367 | name: name, 368 | item: { 369 | if is_valid_item(item) { Some(item) } 370 | else { None } 371 | }, 372 | typ: typ, 373 | sem: sem, 374 | unit: unit, 375 | indom: { 376 | if is_valid_indom(indom) { Some(indom) } 377 | else { None } 378 | }, 379 | pad: pad, 380 | short_help_offset: { 381 | if is_valid_blk_offset(short_help_offset) { Some(short_help_offset) } 382 | else { None } 383 | }, 384 | long_help_offset: { 385 | if is_valid_blk_offset(long_help_offset) { Some(long_help_offset) } 386 | else { None } 387 | } 388 | }) 389 | } 390 | } 391 | 392 | /// Value block structure 393 | /// 394 | /// For reference to the C API, see 395 | /// https://github.com/performancecopilot/pcp/blob/master/src/include/pcp/mmv_dev.h#L88 396 | pub struct ValueBlk { 397 | value: u64, 398 | string_offset: Option, 399 | metric_offset: Option, 400 | instance_offset: Option 401 | } 402 | 403 | impl ValueBlk { 404 | pub fn value(&self) -> u64 { self.value } 405 | pub fn string_offset(&self) -> &Option { &self.string_offset } 406 | pub fn metric_offset(&self) -> &Option { &self.metric_offset } 407 | pub fn instance_offset(&self) -> &Option { &self.instance_offset } 408 | } 409 | 410 | impl ValueBlk { 411 | fn from_reader(r: &mut R) -> Result { 412 | let value = r.read_u64::()?; 413 | let string_offset = r.read_u64::()?; 414 | let metric_offset = r.read_u64::()?; 415 | let instance_offset = r.read_u64::()?; 416 | 417 | Ok(ValueBlk { 418 | value: value, 419 | string_offset: { 420 | if is_valid_blk_offset(string_offset) { Some(string_offset) } 421 | else { None } 422 | }, 423 | metric_offset: { 424 | if is_valid_blk_offset(metric_offset) { Some(metric_offset) } 425 | else { None } 426 | }, 427 | instance_offset: { 428 | if is_valid_blk_offset(instance_offset) { Some(instance_offset) } 429 | else { None } 430 | }, 431 | }) 432 | } 433 | } 434 | 435 | /// Indom block structure 436 | /// 437 | /// For reference to the C API, see 438 | /// https://github.com/performancecopilot/pcp/blob/master/src/include/pcp/mmv_dev.h#L38 439 | pub struct IndomBlk { 440 | indom: Option, 441 | instances: u32, 442 | instances_offset: Option, 443 | short_help_offset: Option, 444 | long_help_offset: Option 445 | } 446 | 447 | impl IndomBlk { 448 | pub fn indom(&self) -> &Option { &self.indom } 449 | pub fn instances(&self) -> u32 { self.instances } 450 | pub fn instances_offset(&self) -> &Option { &self.instances_offset } 451 | pub fn short_help_offset(&self) -> &Option { &self.short_help_offset } 452 | pub fn long_help_offset(&self) -> &Option { &self.long_help_offset } 453 | } 454 | 455 | impl IndomBlk { 456 | fn from_reader(r: &mut R) -> Result { 457 | let indom = r.read_u32::()?; 458 | let instances = r.read_u32::()?; 459 | let instances_offset = r.read_u64::()?; 460 | let short_help_offset = r.read_u64::()?; 461 | let long_help_offset = r.read_u64::()?; 462 | 463 | Ok(IndomBlk { 464 | indom: { 465 | if is_valid_indom(indom) { Some(indom) } 466 | else { None } 467 | }, 468 | instances: instances, 469 | instances_offset: { 470 | if is_valid_blk_offset(instances_offset) { Some(instances_offset) } 471 | else { None } 472 | }, 473 | short_help_offset: { 474 | if is_valid_blk_offset(short_help_offset) { Some(short_help_offset) } 475 | else { None } 476 | }, 477 | long_help_offset: { 478 | if is_valid_blk_offset(long_help_offset) { Some(long_help_offset) } 479 | else { None } 480 | } 481 | }) 482 | } 483 | } 484 | 485 | /// Instance block structure 486 | /// 487 | /// For reference to the C API, see 488 | /// https://github.com/performancecopilot/pcp/blob/master/src/include/pcp/mmv_dev.h#L46 489 | pub struct InstanceBlk { 490 | indom_offset: Option, 491 | pad: u32, 492 | internal_id: i32, 493 | external_id: VersionSpecificString 494 | } 495 | 496 | impl InstanceBlk { 497 | pub fn indom_offset(&self) -> &Option { &self.indom_offset } 498 | pub fn pad(&self) -> u32 { self.pad } 499 | pub fn internal_id(&self) -> i32 { self.internal_id } 500 | pub fn external_id(&self) -> &VersionSpecificString { &self.external_id } 501 | } 502 | 503 | impl InstanceBlk { 504 | fn from_reader(r: &mut R, ver: Version) -> Result { 505 | let indom_offset = r.read_u64::()?; 506 | 507 | let pad = r.read_u32::()?; 508 | if pad != 0 { 509 | return_mmvdumperror!("Invalid pad bytes", pad); 510 | } 511 | 512 | let internal_id = r.read_i32::()?; 513 | 514 | let external_id = match ver { 515 | Version::V1 => { 516 | let mut external_id_bytes = [0; MMV1_NAME_MAX_LEN as usize]; 517 | r.read_exact(&mut external_id_bytes)?; 518 | let cstr = unsafe { 519 | CStr::from_ptr(external_id_bytes.as_ptr() as *const i8) 520 | }; 521 | VersionSpecificString::String(cstr.to_str()?.to_owned()) 522 | }, 523 | Version::V2 => { 524 | VersionSpecificString::Offset(r.read_u64::()?) 525 | } 526 | }; 527 | 528 | 529 | Ok(InstanceBlk { 530 | indom_offset: { 531 | if is_valid_blk_offset(indom_offset) { Some(indom_offset) } 532 | else { None } 533 | }, 534 | pad: pad, 535 | internal_id: internal_id, 536 | external_id: external_id 537 | }) 538 | } 539 | } 540 | 541 | /// String block structure 542 | /// 543 | /// For reference to the C API, see 544 | /// https://github.com/performancecopilot/pcp/blob/master/src/include/pcp/mmv_dev.h#L60 545 | pub struct StringBlk { 546 | string: String 547 | } 548 | 549 | impl StringBlk { 550 | pub fn string(&self) -> &str { &self.string } 551 | } 552 | 553 | impl StringBlk { 554 | fn from_reader(r: &mut R) -> Result { 555 | let mut bytes = [0; STRING_BLOCK_LEN as usize]; 556 | r.read_exact(&mut bytes)?; 557 | let cstr = unsafe { 558 | CStr::from_ptr(bytes.as_ptr() as *const i8) 559 | }; 560 | let string = cstr.to_str()?.to_owned(); 561 | 562 | Ok(StringBlk { 563 | string: string 564 | }) 565 | } 566 | } 567 | 568 | macro_rules! blks_from_toc ( 569 | ($toc:expr, $blk_typ:tt, $cursor:expr) => { 570 | if let Some(ref toc) = $toc { 571 | let mut blks = BTreeMap::new(); 572 | 573 | $cursor.set_position(toc.sec_offset); 574 | for _ in 0..toc.entries as usize { 575 | let blk_offset = $cursor.position(); 576 | let blk = $blk_typ::from_reader(&mut $cursor)?; 577 | blks.insert(blk_offset, blk); 578 | } 579 | 580 | blks 581 | } else { 582 | BTreeMap::new() 583 | } 584 | }; 585 | ($toc:expr, $blk_typ:tt, $mmv_ver:expr, $cursor:expr) => { 586 | if let Some(ref toc) = $toc { 587 | let mut blks = BTreeMap::new(); 588 | 589 | $cursor.set_position(toc.sec_offset); 590 | for _ in 0..toc.entries as usize { 591 | let blk_offset = $cursor.position(); 592 | let blk = $blk_typ::from_reader(&mut $cursor, $mmv_ver)?; 593 | blks.insert(blk_offset, blk); 594 | } 595 | 596 | blks 597 | } else { 598 | BTreeMap::new() 599 | } 600 | }; 601 | ); 602 | 603 | /// Returns an `MMV` structure by reading and parsing the MMV 604 | /// file stored at `mmv_path` 605 | pub fn dump(mmv_path: &Path) -> Result { 606 | let mut mmv_bytes = Vec::new(); 607 | let mut file = File::open(mmv_path)?; 608 | file.read_to_end(&mut mmv_bytes)?; 609 | 610 | let mut cursor = Cursor::new(mmv_bytes); 611 | 612 | let hdr = Header::from_reader(&mut cursor)?; 613 | 614 | let mut indom_toc = None; 615 | let mut instance_toc = None; 616 | let mut metric_toc = None; 617 | let mut value_toc = None; 618 | let mut string_toc = None; 619 | 620 | for i in 0..hdr.toc_count { 621 | let toc_position = cursor.position(); 622 | let mut toc = TocBlk::from_reader(&mut cursor)?; 623 | toc._toc_index = i; 624 | toc._mmv_offset = toc_position; 625 | 626 | if toc.sec == INDOM_TOC_CODE { indom_toc = Some(toc); } 627 | else if toc.sec == INSTANCE_TOC_CODE { instance_toc = Some(toc); } 628 | else if toc.sec == METRIC_TOC_CODE { metric_toc = Some(toc); } 629 | else if toc.sec == VALUES_TOC_CODE { value_toc = Some(toc); } 630 | else if toc.sec == STRINGS_TOC_CODE { string_toc = Some(toc); } 631 | } 632 | 633 | if metric_toc.is_none() { 634 | return_mmvdumperror!("Metric TOC absent", 0); 635 | } 636 | if value_toc.is_none() { 637 | return_mmvdumperror!("String TOC absent", 0); 638 | } 639 | 640 | let indom_blks = blks_from_toc!(indom_toc, IndomBlk, cursor); 641 | let instance_blks = blks_from_toc!(instance_toc, InstanceBlk, hdr.version, cursor); 642 | let metric_blks = blks_from_toc!(metric_toc, MetricBlk, hdr.version, cursor); 643 | let value_blks = blks_from_toc!(value_toc, ValueBlk, cursor); 644 | let string_blks = blks_from_toc!(string_toc, StringBlk, cursor); 645 | 646 | Ok( 647 | MMV { 648 | header: hdr, 649 | metric_toc: metric_toc.unwrap(), 650 | value_toc: value_toc.unwrap(), 651 | string_toc: string_toc, 652 | indom_toc: indom_toc, 653 | instance_toc: instance_toc, 654 | indom_blks: indom_blks, 655 | instance_blks: instance_blks, 656 | metric_blks: metric_blks, 657 | value_blks: value_blks, 658 | string_blks: string_blks 659 | } 660 | ) 661 | } 662 | -------------------------------------------------------------------------------- /src/private.rs: -------------------------------------------------------------------------------- 1 | // This file was borrowed from https://github.com/nikomatsakis/rayon/blob/master/src/private.rs 2 | // and is subject to the license and copyright from that project. 3 | 4 | //! The public parts of this private module are used to create traits 5 | //! that cannot be implemented outside of our own crate. This way we 6 | //! can feel free to extend those traits without worrying about it 7 | //! being a breaking change for other implementations. 8 | 9 | /// If this type is pub but not publicly reachable, third parties 10 | /// can't name it and can't implement traits using it. 11 | pub struct PrivateMarker; 12 | 13 | macro_rules! private_decl { 14 | () => { 15 | /// This trait is private; this method exists to make it 16 | /// impossible to implement outside the crate. 17 | #[doc(hidden)] 18 | fn __rayon_private__(&self) -> ::private::PrivateMarker; 19 | } 20 | } 21 | 22 | macro_rules! private_impl { 23 | () => { 24 | fn __rayon_private__(&self) -> ::private::PrivateMarker { 25 | ::private::PrivateMarker 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/data/mmvdump_ip1.mmv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/performancecopilot/hornet/330c7ebb21d1364dac7f6806b847341049a2dea0/tests/data/mmvdump_ip1.mmv -------------------------------------------------------------------------------- /tests/data/mmvdump_ip2.mmv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/performancecopilot/hornet/330c7ebb21d1364dac7f6806b847341049a2dea0/tests/data/mmvdump_ip2.mmv -------------------------------------------------------------------------------- /tests/data/mmvdump_ip3.mmv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/performancecopilot/hornet/330c7ebb21d1364dac7f6806b847341049a2dea0/tests/data/mmvdump_ip3.mmv -------------------------------------------------------------------------------- /tests/data/mmvdump_ip4.mmv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/performancecopilot/hornet/330c7ebb21d1364dac7f6806b847341049a2dea0/tests/data/mmvdump_ip4.mmv -------------------------------------------------------------------------------- /tests/data/mmvdump_ip5.mmv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/performancecopilot/hornet/330c7ebb21d1364dac7f6806b847341049a2dea0/tests/data/mmvdump_ip5.mmv -------------------------------------------------------------------------------- /tests/data/mmvdump_ip6.mmv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/performancecopilot/hornet/330c7ebb21d1364dac7f6806b847341049a2dea0/tests/data/mmvdump_ip6.mmv -------------------------------------------------------------------------------- /tests/data/mmvdump_op1.golden: -------------------------------------------------------------------------------- 1 | Version = 1 2 | Generated = 1468770536 3 | TOC count = 3 4 | Cluster = 127 5 | Process = 29956 6 | Flags = process (0x2) 7 | 8 | TOC[0]: toc offset 40, metrics offset 88 (1 entries) 9 | [725/88] simple.counter 10 | type=Int32 (0x0), sem=counter (0x1), pad=0x0 11 | unit=count (0x100000) 12 | (no indom) 13 | shorttext=A Simple Metric 14 | longtext=This is a simple counter metric to demonstrate the speed API 15 | 16 | TOC[1]: toc offset 56, values offset 192 (1 entries) 17 | [725/192] simple.counter = 42 18 | 19 | TOC[2]: toc offset 72, strings offset 224 (2 entries) 20 | [1/224] A Simple Metric 21 | [2/480] This is a simple counter metric to demonstrate the speed API 22 | 23 | -------------------------------------------------------------------------------- /tests/data/mmvdump_op2.golden: -------------------------------------------------------------------------------- 1 | Version = 1 2 | Generated = 1469335238 3 | TOC count = 4 4 | Cluster = 1297 5 | Process = 6410 6 | Flags = process (0x2) 7 | 8 | TOC[0]: toc offset 40, indoms offset 104 (1 entries) 9 | [3094651/104] 3 instances, starting at offset 136 10 | (no shorttext) 11 | (no longtext) 12 | 13 | TOC[1]: toc offset 56, instances offset 136 (3 entries) 14 | [3094651/136] instance = [-2122300086 or "javascript"] 15 | [3094651/216] instance = [1531230383 or "php"] 16 | [3094651/296] instance = [1109423947 or "go"] 17 | 18 | TOC[2]: toc offset 72, metrics offset 376 (1 entries) 19 | [1021/376] language.users 20 | type=Uint64 (0x3), sem=counter (0x1), pad=0x0 21 | unit=count (0x100000) 22 | indom=3094651 23 | (no shorttext) 24 | (no longtext) 25 | 26 | TOC[3]: toc offset 88, values offset 480 (3 entries) 27 | [1021/480] language.users[1109423947 or "go"] = 8388608 28 | [1021/512] language.users[-2122300086 or "javascript"] = 330 29 | [1021/544] language.users[1531230383 or "php"] = 33 30 | 31 | -------------------------------------------------------------------------------- /tests/data/mmvdump_op3.golden: -------------------------------------------------------------------------------- 1 | Version = 1 2 | Generated = 1469564258 3 | TOC count = 3 4 | Cluster = 764 5 | Process = 8672 6 | Flags = process (0x2) 7 | 8 | TOC[0]: toc offset 40, metrics offset 88 (1 entries) 9 | [1022/88] bat.names 10 | type=String (0x6), sem=instant (0x3), pad=0x0 11 | unit=count (0x100000) 12 | (no indom) 13 | (no shorttext) 14 | (no longtext) 15 | 16 | TOC[1]: toc offset 56, values offset 192 (1 entries) 17 | [1022/192] bat.names = "Robin" 18 | 19 | TOC[2]: toc offset 72, strings offset 224 (1 entries) 20 | [1/224] Robin 21 | 22 | -------------------------------------------------------------------------------- /tests/data/mmvdump_op4.golden: -------------------------------------------------------------------------------- 1 | Version = 1 2 | Generated = 1469590299 3 | TOC count = 3 4 | Cluster = 764 5 | Process = 22340 6 | Flags = process (0x2) 7 | 8 | TOC[0]: toc offset 40, metrics offset 88 (1 entries) 9 | [1022/88] bat.names 10 | type=String (0x6), sem=instant (0x3), pad=0x0 11 | unit=count (0x100000) 12 | (no indom) 13 | (no shorttext) 14 | (no longtext) 15 | 16 | TOC[1]: toc offset 56, values offset 192 (1 entries) 17 | [1022/192] bat.names = "" 18 | 19 | TOC[2]: toc offset 72, strings offset 224 (1 entries) 20 | [1/224] 21 | 22 | -------------------------------------------------------------------------------- /tests/data/mmvdump_op5.golden: -------------------------------------------------------------------------------- 1 | Version = 1 2 | Generated = 1501135556 3 | TOC count = 3 4 | Cluster = 0 5 | Process = 15673 6 | Flags = no prefix,process,sentinel (0x7) 7 | 8 | TOC[0]: toc offset 40, metrics offset 88 (3 entries) 9 | [150/88] download_speed 10 | type=Double64 (0x5), sem=instant (0x3), pad=0x0 11 | unit=MiB / sec (0x1f023000) 12 | (no indom) 13 | shorttext=Download speed in MiB/sec 14 | (no longtext) 15 | [372/192] frequency 16 | type=Float32 (0x4), sem=instant (0x3), pad=0x0 17 | unit=/ sec (0xf003000) 18 | (no indom) 19 | shorttext=Frequency in Hz 20 | (no longtext) 21 | [433/296] time 22 | type=Int32 (0x0), sem=instant (0x3), pad=0x0 23 | unit=hr (0x1005000) 24 | (no indom) 25 | (no shorttext) 26 | (no longtext) 27 | 28 | TOC[1]: toc offset 56, values offset 400 (3 entries) 29 | [150/400] download_speed = 0.3333333333333333 30 | [372/432] frequency = 0.33333334 31 | [433/464] time = -6 32 | 33 | TOC[2]: toc offset 72, strings offset 496 (2 entries) 34 | [1/496] Download speed in MiB/sec 35 | [2/752] Frequency in Hz 36 | 37 | -------------------------------------------------------------------------------- /tests/data/mmvdump_op6.golden: -------------------------------------------------------------------------------- 1 | Version = 2 2 | Generated = 1501952945 3 | TOC count = 5 4 | Cluster = 0 5 | Process = 4579 6 | Flags = process (0x2) 7 | 8 | TOC[0]: toc offset 40, indoms offset 120 (1 entries) 9 | [369409/120] 3 instances, starting at offset 152 10 | shorttext=Caches 11 | longtext=Different levels of CPU caches 12 | 13 | TOC[1]: toc offset 56, instances offset 152 (3 entries) 14 | [369409/152] instance = [-242639604 or "L2"] 15 | [369409/176] instance = [-331227094 or "L1"] 16 | [369409/200] instance = [-1524654670 or "L3"] 17 | 18 | TOC[2]: toc offset 72, metrics offset 224 (2 entries) 19 | [36/224] cache_size 20 | type=Int32 (0x0), sem=discrete (0x4), pad=0x0 21 | unit=KiB (0x10010000) 22 | indom=369409 23 | shorttext=Cache sizes 24 | longtext=Sizes of different CPU caches 25 | [239/272] cpu 26 | type=String (0x6), sem=discrete (0x4), pad=0x0 27 | unit=(0x0) 28 | (no indom) 29 | shorttext=CPU family 30 | (no longtext) 31 | 32 | TOC[3]: toc offset 88, values offset 320 (4 entries) 33 | [36/320] cache_size[-242639604 or "L2"] = 8192 34 | [36/352] cache_size[-331227094 or "L1"] = 0 35 | [36/384] cache_size[-1524654670 or "L3"] = 0 36 | [239/416] cpu = "kabylake" 37 | 38 | TOC[4]: toc offset 104, strings offset 448 (11 entries) 39 | [1/448] cache_size 40 | [2/704] Cache sizes 41 | [3/960] Sizes of different CPU caches 42 | [4/1216] Caches 43 | [5/1472] Different levels of CPU caches 44 | [6/1728] L2 45 | [7/1984] L1 46 | [8/2240] L3 47 | [9/2496] cpu 48 | [10/2752] CPU family 49 | [11/3008] kabylake 50 | 51 | -------------------------------------------------------------------------------- /tests/mmvfmt.rs: -------------------------------------------------------------------------------- 1 | extern crate hornet; 2 | 3 | use hornet::mmv; 4 | use std::fs; 5 | use std::fs::File; 6 | use std::io::prelude::*; 7 | use std::path::PathBuf; 8 | 9 | #[test] 10 | fn test_mmvfmt() { 11 | let mut testdata_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 12 | testdata_dir.push("tests/data"); 13 | let tests = fs::read_dir(&testdata_dir).unwrap().count() / 2; 14 | 15 | let input_prefix = "mmvdump_ip"; 16 | let input_suffix = ".mmv"; 17 | let output_prefix = "mmvdump_op"; 18 | let output_suffix = ".golden"; 19 | 20 | for i in 1..tests+1 { 21 | let mut output_path = testdata_dir.clone(); 22 | output_path.push(&format!("{}{}{}", output_prefix, i, output_suffix)); 23 | let mut golden_output = Vec::new(); 24 | File::open(output_path).unwrap() 25 | .read_to_end(&mut golden_output).unwrap(); 26 | 27 | let mut input_path = testdata_dir.clone(); 28 | input_path.push(&format!("{}{}{}", input_prefix, i, input_suffix)); 29 | let mmv = mmv::dump(&input_path).unwrap(); 30 | let mut mmvdumm_output = Vec::new(); 31 | write!(&mut mmvdumm_output, "{}", mmv).unwrap(); 32 | 33 | assert_eq!(mmvdumm_output, golden_output); 34 | } 35 | } 36 | --------------------------------------------------------------------------------