├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── something-else.md ├── PULL_REQUEST_TEMPLATE │ ├── bug_fix.md │ ├── feature.md │ └── something-else.md ├── dependabot.yml └── workflows │ └── rust.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── benches ├── atomic.rs ├── counter.rs ├── desc.rs ├── gauge.rs ├── histogram.rs └── text_encoder.rs ├── build.rs ├── docs └── release.md ├── examples ├── example_custom_registry.rs ├── example_edition_2018.rs ├── example_embed.rs ├── example_hyper.rs ├── example_int_metrics.rs ├── example_process_collector.rs └── example_push.rs ├── proto ├── mod.rs ├── proto_model.proto └── proto_model.rs ├── src ├── atomic64.rs ├── auto_flush.rs ├── counter.rs ├── desc.rs ├── encoder │ ├── mod.rs │ ├── pb.rs │ └── text.rs ├── errors.rs ├── gauge.rs ├── histogram.rs ├── lib.rs ├── macros.rs ├── metrics.rs ├── nohash.rs ├── plain_model.rs ├── process_collector.rs ├── proto_ext.rs ├── pulling_gauge.rs ├── push.rs ├── registry.rs ├── timer.rs ├── value.rs └── vec.rs └── static-metric ├── CHANGELOG.md ├── Cargo.toml ├── Makefile ├── README.md ├── benches └── benches.rs ├── examples ├── advanced.rs ├── local.rs ├── make_auto_flush_static_counter.rs ├── make_auto_flush_static_metric_histogram.rs ├── metric_enum.rs ├── register_integration.rs ├── simple.rs └── with_lazy_static.rs ├── rustfmt.toml ├── src ├── auto_flush_builder.rs ├── auto_flush_from.rs ├── builder.rs ├── lib.rs ├── parser.rs ├── register_macro.rs └── util.rs └── tests ├── label_enum.rs └── metric.rs /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Found a bug? Help us squash it! 4 | --- 5 | 6 | **Describe the bug** 7 | A clear and concise description of what the bug is. 8 | 9 | **To Reproduce** 10 | Steps to reproduce the behavior: 11 | 1. Go to '...' 12 | 2. Click on '....' 13 | 3. Scroll down to '....' 14 | 4. See error 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **System information** 20 | * CPU architecture: 21 | * Distribution and kernel version: 22 | * SELinux on?: 23 | * Any other system details we should know?: 24 | 25 | **Additional context** 26 | Add any other context about the problem here. 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Help us develop our roadmap and direction. 4 | --- 5 | 6 | **Is your feature request related to a problem? Please describe.** 7 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 8 | 9 | **Describe the solution you'd like** 10 | A clear and concise description of what you want to happen. 11 | 12 | **Describe alternatives you've considered** 13 | A clear and concise description of any alternative solutions or features you've considered. 14 | 15 | **Additional context** 16 | Add any other context or screenshots about the feature request here. 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/something-else.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Something Else 3 | about: Just give me a text box! 4 | --- 5 | 6 | 7 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/bug_fix.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug fix 3 | about: A bug squashed. 4 | 5 | --- 6 | 7 | **Related bugs:** 8 | This bug fix closes issue #???. 9 | 10 | **Description of problem:** 11 | Describe what was causing the related issue to happen. 12 | 13 | **Description of solution:** 14 | Describe the rationale behind the fix. 15 | 16 | **Checklist:** 17 | The CI will check all of these, but you'll need to have done them: 18 | 19 | * [ ] `cargo fmt -- --check` passes. 20 | * [ ] `cargo +nightly clippy` has no warnings. 21 | * [ ] `cargo test` passes. 22 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature 3 | about: The dawn of a new era. 4 | 5 | --- 6 | 7 | **Related features:** 8 | This feature resolves issue #???. 9 | 10 | **Description of feature:** 11 | A short description of the feature implemented. 12 | 13 | **Implementation:** 14 | Describe any pieces of the implementation that deserve further explanation. 15 | Detail any gotchas, uncertainties, or open questions about the implementation. 16 | 17 | **Checklist:** 18 | 19 | The CI will check all of these, but you'll need to have done them: 20 | 21 | * [ ] `cargo fmt -- --check` passes. 22 | * [ ] `cargo +nightly clippy` has no warnings. 23 | * [ ] `cargo test` passes. 24 | 25 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/something-else.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Something Else 3 | about: Just give me a text box! 4 | 5 | --- 6 | 7 | 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Rust 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | # Pinned toolchain for linting and benchmarks 12 | ACTIONS_LINTS_TOOLCHAIN: 1.81.0 13 | # Minimum supported Rust version (MSRV) 14 | ACTION_MSRV_TOOLCHAIN: 1.81.0 15 | EXTRA_FEATURES: "protobuf push process" 16 | 17 | jobs: 18 | tests-stable: 19 | name: "Tests, stable toolchain" 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@v4 24 | - name: Install toolchain 25 | uses: dtolnay/rust-toolchain@stable 26 | - name: cargo build 27 | run: cargo build 28 | - name: cargo test 29 | run: cargo test 30 | - name: cargo test (no default features) 31 | run: cargo test --no-default-features 32 | - name: cargo test (extra features) 33 | run: cargo test --no-default-features --features="${{ env['EXTRA_FEATURES'] }}" 34 | - name: cargo package 35 | run : cargo package && cargo package --manifest-path static-metric/Cargo.toml 36 | tests-other-channels: 37 | name: "Tests, unstable toolchain" 38 | runs-on: ubuntu-latest 39 | continue-on-error: true 40 | strategy: 41 | matrix: 42 | channel: 43 | - "beta" 44 | - "nightly" 45 | steps: 46 | - name: Checkout repository 47 | uses: actions/checkout@v4 48 | - name: Install toolchain 49 | uses: dtolnay/rust-toolchain@master 50 | with: 51 | toolchain: ${{ matrix.channel }} 52 | - name: cargo build 53 | run: cargo build 54 | - name: cargo test 55 | run: cargo test 56 | - name: cargo build (static-metric) 57 | run: cargo build -p prometheus-static-metric --examples --no-default-features --features="${{ env['EXTRA_FEATURES'] }}" 58 | - name: cargo test (static-metric) 59 | run: cargo test -p prometheus-static-metric --no-default-features --features="${{ env['EXTRA_FEATURES'] }}" 60 | build-msrv: 61 | name: "Build, minimum toolchain" 62 | runs-on: ubuntu-latest 63 | steps: 64 | - name: Checkout repository 65 | uses: actions/checkout@v4 66 | - name: Install toolchain 67 | uses: dtolnay/rust-toolchain@master 68 | with: 69 | toolchain: ${{ env['ACTION_MSRV_TOOLCHAIN'] }} 70 | - run: cargo build 71 | - run: cargo test --no-run 72 | - run: cargo build --no-default-features 73 | - run: cargo test --no-default-features --no-run 74 | - run: cargo build --no-default-features --features="${{ env['EXTRA_FEATURES'] }}" 75 | - run: cargo test --no-default-features --features="${{ env['EXTRA_FEATURES'] }}" 76 | linting: 77 | name: "Lints, pinned toolchain" 78 | runs-on: ubuntu-latest 79 | steps: 80 | - name: Checkout repository 81 | uses: actions/checkout@v4 82 | - name: Install toolchain 83 | uses: dtolnay/rust-toolchain@master 84 | with: 85 | toolchain: ${{ env['ACTIONS_LINTS_TOOLCHAIN'] }} 86 | components: rustfmt, clippy 87 | - name: cargo fmt (check) 88 | run: cargo fmt --all -- --check -l 89 | - name: cargo clippy 90 | run: cargo clippy --all 91 | - name: cargo clippy (no default features) 92 | run: cargo clippy --all --no-default-features 93 | - name: cargo clippy (extra features) 94 | run: cargo clippy --all --no-default-features --features="${{ env['EXTRA_FEATURES'] }}" 95 | criterion: 96 | name: "Benchmarks (criterion)" 97 | runs-on: ubuntu-latest 98 | steps: 99 | - name: Checkout repository 100 | uses: actions/checkout@v4 101 | - name: Install toolchain 102 | uses: dtolnay/rust-toolchain@master 103 | with: 104 | toolchain: ${{ env['ACTIONS_LINTS_TOOLCHAIN'] }} 105 | - name: cargo bench (prometheus) 106 | run: cargo bench -p prometheus 107 | - name: cargo bench (prometheus-static-metric) 108 | run: cargo bench -p prometheus-static-metric 109 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # OSX leaves these everywhere on SMB shares 3 | ._* 4 | 5 | # OSX trash 6 | .DS_Store 7 | 8 | # Eclipse files 9 | .classpath 10 | .project 11 | .settings/** 12 | 13 | # Vim swap files 14 | *.swp 15 | 16 | # Files generated by JetBrains IDEs, e.g. IntelliJ IDEA 17 | .idea/ 18 | *.iml 19 | out/ 20 | 21 | # Vscode files 22 | .vscode/** 23 | 24 | target 25 | tmp 26 | /bin 27 | 28 | Cargo.lock 29 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.14.0 4 | 5 | - API change: Use `AsRef` for owned label values (#537) 6 | 7 | - Improvement: Hashing improvements (#532) 8 | 9 | - Dependency upgrade: Update `hyper` to 1.6 (#524) 10 | 11 | - Dependency upgrade: Update `procfs` to 0.17 (#543) 12 | 13 | - Dependency upgrade: Update `protobuf` to 3.7.2 for RUSTSEC-2024-0437 (#541) 14 | 15 | - Dependency upgrade: Update `thiserror` to 2.0 (#534) 16 | 17 | - Internal change: Fix LSP and Clippy warnings (#540) 18 | 19 | - Internal change: Bump MSRV to 1.81 (#539) 20 | 21 | - Documentation: Fix `register_histogram_vec_with_registry` docstring (#528) 22 | 23 | - Documentation: Fix typos in static-metric docstrings (#479) 24 | 25 | - Documentation: Add missing `protobuf` feature to README list (#531) 26 | 27 | ## 0.13.4 28 | 29 | - Improvement: Add PullingGauge (#405) 30 | 31 | - Improvement: Let cargo know which example requires which features (#511) 32 | 33 | - Bug fix: Prevent `clippy::ignored_unit_patterns` in macro expansions (#497) 34 | 35 | - Internal change: Add CI job for minimum toolchain (MSRV) (#467) 36 | 37 | - Internal change: Update CI to `actions/checkout@v4` (#499) 38 | 39 | - Internal change: Update dependencies 40 | 41 | ## 0.13.3 42 | 43 | - Bug fix: Prevent ProcessCollector underflow with CPU time counter (#465) 44 | 45 | - Internal change: Update dependencies 46 | 47 | ## 0.13.2 48 | 49 | - Bug fix: Fix compilation on 32-bit targets (#446) 50 | 51 | ## 0.13.1 52 | 53 | - Improvement: ProcessCollector use IntGauge to provide better performance (#430) 54 | 55 | - Bug fix: Fix re-export of TEXT_FORMAT to not require protobuf (#416) 56 | 57 | - Bug fix: Fix doc for encode (#433) 58 | 59 | - Bug fix: Fix broken doc links (#426) 60 | 61 | - Bug fix: Fix crates.io badge (#436) 62 | 63 | - Internal change: Derive default instead of obvious manual impl (#437) 64 | 65 | - Internal change: Remove needless borrow (#427) 66 | 67 | - Internal change: Update dependencies 68 | 69 | ## 0.13.0 70 | 71 | - Bug fix: Avoid panics from `Instant::elapsed` (#406) 72 | 73 | - Improvement: Allow trailing comma on macros (#390) 74 | 75 | - Improvement: Add macros for custom registry (#396) 76 | 77 | - Improvement: Export thread count from `process_collector` (#401) 78 | 79 | - Improvement: Add convenience TextEncoder functions to encode directly to string (#402) 80 | 81 | - Internal change: Clean up the use of macro_use and extern crate (#398) 82 | 83 | - Internal change: Update dependencies 84 | 85 | ## 0.12.0 86 | 87 | - Improvement: Fix format string in panic!() calls (#391) 88 | 89 | - Improvement: Replace regex with memchr (#385) 90 | 91 | - Improvement: Update reqwest requirement from ^0.10 to ^0.11 (#379) 92 | 93 | ## 0.11.0 94 | 95 | - Improvement: Switch to more efficient `fd_count()` for `process_open_fds` (#357). 96 | 97 | - API change: Change Integer Counter type from AtomicI64 to AtomicU64 (#365). 98 | 99 | - Internal change: Update dependencies. 100 | 101 | ## 0.10.0 102 | 103 | - Improvement: Use different generic parameters for name and help at metric construction (#324). 104 | 105 | - Bug fix: Error instead of panic when constructing histogram with unequal label key and label value length (#326). 106 | 107 | - Bug fix: Return `Error::AlreadyReg` on duplicate collector registration (#333). 108 | 109 | - Bug fix: Make Histogram::observe atomic across collects (#314). 110 | 111 | - Internal change: Replace spin with parking_lot (#318). 112 | 113 | - Internal change: Optimize metric formatting (#327). 114 | 115 | - Internal change: Update parking_lot and procfs dependencies (#337). 116 | 117 | ## 0.9.0 118 | 119 | - Add: Implement `encode` function for summary type metrics. 120 | 121 | ## 0.8.0 122 | 123 | - Add: Reset Counters (#261) 124 | 125 | - Add: Discard Histogram timers (#257) 126 | 127 | - Add: `observe_closure_duration` for better observing closure duration for local histogram (#296) 128 | 129 | - Fix a bug that global labels are not handled correctly (#269) 130 | 131 | - Improve linear bucket accuracy by avoiding accumulating error (#276) 132 | 133 | - Internal change: Use [thiserror](https://docs.rs/thiserror) to generate the error structure (#285) 134 | 135 | - Internal change: Switch from procinfo to [procfs](https://docs.rs/procfs) (#290) 136 | 137 | - Internal change: Update to newer dependencies 138 | 139 | ## 0.7.0 140 | 141 | - Provide immutable interface for local counters. 142 | 143 | ## 0.6.1 144 | 145 | - Fix compile error when ProtoBuf feature is not enabled (#240). 146 | 147 | ## 0.6.0 148 | 149 | - Add: Expose the default registry (#231). 150 | 151 | - Add: Support common namespace prefix and common labels (#233). 152 | 153 | ## 0.5.0 154 | 155 | - Change: Added TLS and BasicAuthentication support to `push` client. 156 | 157 | ## 0.4.2 158 | 159 | - Change: Update to use protobuf 2.0. 160 | 161 | ## 0.4.1 162 | 163 | - Change: `(Local)(Int)Counter.inc_by` only panics in debug build if the given value is < 0 (#168). 164 | 165 | ## 0.4.0 166 | 167 | - Add: Provides `IntCounter`, `IntCounterVec`, `IntGauge`, `IntGaugeVec`, `LocalIntCounter`, `LocalIntCounterVec` for better performance when metric values are all integers (#158). 168 | 169 | - Change: When the given value is < 0, `(Local)Counter.inc_by` no longer return errors, instead it will panic (#156). 170 | 171 | - Improve performance (#161). 172 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at coc@pingcap.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["overvenus@gmail.com", "siddontang@gmail.com", "vistaswx@gmail.com"] 3 | description = "Prometheus instrumentation library for Rust applications." 4 | documentation = "https://docs.rs/prometheus" 5 | edition = "2018" 6 | homepage = "https://github.com/tikv/rust-prometheus" 7 | keywords = ["prometheus", "metrics"] 8 | license = "Apache-2.0" 9 | name = "prometheus" 10 | readme = "README.md" 11 | repository = "https://github.com/tikv/rust-prometheus" 12 | rust-version = "1.81" 13 | version = "0.14.0" 14 | 15 | [package.metadata.docs.rs] 16 | features = ["nightly"] 17 | 18 | [features] 19 | default = ["protobuf"] 20 | gen = ["protobuf-codegen"] 21 | nightly = ["libc"] 22 | process = ["libc", "procfs"] 23 | push = ["reqwest", "libc", "protobuf"] 24 | 25 | [dependencies] 26 | cfg-if = "^1.0" 27 | fnv = "^1.0" 28 | lazy_static = "^1.4" 29 | libc = { version = "^0.2", optional = true } 30 | parking_lot = "^0.12" 31 | protobuf = { version = "^3.7.2", optional = true } 32 | memchr = "^2.3" 33 | reqwest = { version = "^0.12", features = ["blocking"], optional = true } 34 | thiserror = "^2.0" 35 | 36 | [target.'cfg(target_os = "linux")'.dependencies] 37 | procfs = { version = "^0.17", optional = true, default-features = false } 38 | 39 | [dev-dependencies] 40 | criterion = "0.5" 41 | getopts = "^0.2" 42 | hyper = { version = "^1.6", features = ["http1", "server"] } 43 | hyper-util = { version = "^0.1", features = ["http1", "server", "tokio"] } 44 | tokio = { version = "^1.0", features = ["macros", "net", "rt-multi-thread"] } 45 | 46 | [build-dependencies] 47 | protobuf-codegen = { version = "^3.7.2", optional = true } 48 | 49 | [workspace] 50 | members = ["static-metric"] 51 | 52 | [[bench]] 53 | name = "atomic" 54 | harness = false 55 | 56 | [[bench]] 57 | name = "counter" 58 | harness = false 59 | 60 | [[bench]] 61 | name = "desc" 62 | harness = false 63 | 64 | [[bench]] 65 | name = "gauge" 66 | harness = false 67 | 68 | [[bench]] 69 | name = "histogram" 70 | harness = false 71 | 72 | [[bench]] 73 | name = "text_encoder" 74 | harness = false 75 | 76 | [[example]] 77 | name = "example_push" 78 | required-features = ["push"] 79 | 80 | [[example]] 81 | name = "example_process_collector" 82 | required-features = ["process"] 83 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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 2019 TiKV Project Authors. 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all build test dev bench format clean examples gen_proto 2 | 3 | ENABLE_FEATURES ?= default 4 | 5 | all: format build test examples 6 | 7 | build: 8 | cargo build --features="${ENABLE_FEATURES}" 9 | 10 | test: 11 | cargo test --features="${ENABLE_FEATURES}" -- --nocapture 12 | 13 | dev: format test 14 | 15 | bench: format 16 | cargo bench --features=${ENABLE_FEATURES} -- --nocapture 17 | 18 | format: 19 | @cargo fmt --all -- --check >/dev/null || cargo fmt --all 20 | 21 | clean: 22 | cargo clean 23 | 24 | examples: 25 | cargo build --example example_embed 26 | cargo build --example example_hyper 27 | cargo build --features="push" --example example_push 28 | cargo build --features="process" --example example_process_collector 29 | 30 | gen_proto: 31 | @ which protoc >/dev/null || { echo "Please install protoc first"; exit 1; } 32 | @ which protoc-gen-rust >/dev/null || { echo "Please install protobuf rust plugin, cargo install protobuf"; exit 1; } 33 | protoc --rust_out proto proto/metrics.proto 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Prometheus Rust client library 2 | 3 | [![Build Status](https://github.com/tikv/rust-prometheus/actions/workflows/rust.yml/badge.svg)](https://github.com/tikv/rust-prometheus/actions/workflows/rust.yml) 4 | [![docs.rs](https://docs.rs/prometheus/badge.svg)](https://docs.rs/prometheus) 5 | [![crates.io](https://img.shields.io/crates/v/prometheus.svg)](https://crates.io/crates/prometheus) 6 | 7 | This is the [Rust](https://www.rust-lang.org) client library for 8 | [Prometheus](http://prometheus.io). The main data structures and APIs are ported 9 | from [Go client](https://github.com/prometheus/client_golang). 10 | 11 | ## Documentation 12 | 13 | Find the latest documentation at . 14 | 15 | ## Advanced 16 | 17 | ### Crate features 18 | 19 | This crate provides several optional components which can be enabled via [Cargo `[features]`](https://doc.rust-lang.org/cargo/reference/features.html): 20 | 21 | - `protobuf`: Protobuf support, enabled by default. 22 | 23 | - `gen`: To generate protobuf client with the latest protobuf version instead of 24 | using the pre-generated client. 25 | 26 | - `nightly`: Enable nightly only features. 27 | 28 | - `process`: Enable [process metrics](https://prometheus.io/docs/instrumenting/writing_clientlibs/#process-metrics) support. 29 | 30 | - `push`: Enable [push metrics](https://prometheus.io/docs/instrumenting/pushing/) support. 31 | 32 | ### Static Metric 33 | 34 | When using a `MetricVec` with label values known at compile time 35 | prometheus-static-metric reduces the overhead of retrieving the concrete 36 | `Metric` from a `MetricVec`. 37 | 38 | See [static-metric](./static-metric) directory for details. 39 | 40 | ## Thanks 41 | 42 | - [brian-brazil](https://github.com/brian-brazil) 43 | - [ccmtaylor](https://github.com/ccmtaylor) 44 | - [kamalmarhubi](https://github.com/kamalmarhubi) 45 | - [lucab](https://github.com/lucab) 46 | - [koushiro](https://github.com/koushiro) 47 | -------------------------------------------------------------------------------- /benches/atomic.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | use criterion::{criterion_group, criterion_main, Criterion}; 15 | use prometheus::core::*; 16 | 17 | fn bench_atomic_f64(c: &mut Criterion) { 18 | let val = AtomicF64::new(0.0); 19 | c.bench_function("atomic_f64", |b| { 20 | b.iter(|| { 21 | val.inc_by(12.0); 22 | }) 23 | }); 24 | } 25 | 26 | fn bench_atomic_i64(c: &mut Criterion) { 27 | let val = AtomicI64::new(0); 28 | c.bench_function("atomic_i64", |b| { 29 | b.iter(|| { 30 | val.inc_by(12); 31 | }) 32 | }); 33 | } 34 | 35 | criterion_group!(benches, bench_atomic_f64, bench_atomic_i64); 36 | criterion_main!(benches); 37 | -------------------------------------------------------------------------------- /benches/counter.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 15 | use fnv::FnvBuildHasher; 16 | use prometheus::{Counter, CounterVec, IntCounter, Opts}; 17 | use std::collections::HashMap; 18 | use std::sync::{atomic, Arc}; 19 | use std::thread; 20 | 21 | fn bench_counter_with_label_values(c: &mut Criterion) { 22 | let counter = CounterVec::new( 23 | Opts::new("benchmark_counter", "A counter to benchmark it."), 24 | &["one", "two", "three"], 25 | ) 26 | .unwrap(); 27 | c.bench_function("counter_with_label_values", |b| { 28 | b.iter(|| { 29 | counter 30 | .with_label_values(&black_box(["eins", "zwei", "drei"])) 31 | .inc() 32 | }) 33 | }); 34 | } 35 | 36 | fn bench_counter_with_mapped_labels(c: &mut Criterion) { 37 | let counter = CounterVec::new( 38 | Opts::new("benchmark_counter", "A counter to benchmark it."), 39 | &["one", "two", "three"], 40 | ) 41 | .unwrap(); 42 | 43 | c.bench_function("counter_with_mapped_labels", |b| { 44 | b.iter(|| { 45 | let mut labels = HashMap::with_capacity(3); 46 | labels.insert("two", "zwei"); 47 | labels.insert("one", "eins"); 48 | labels.insert("three", "drei"); 49 | counter.with(&black_box(labels)).inc(); 50 | }) 51 | }); 52 | } 53 | 54 | fn bench_counter_with_mapped_labels_fnv(c: &mut Criterion) { 55 | let counter = CounterVec::new( 56 | Opts::new("benchmark_counter", "A counter to benchmark it."), 57 | &["one", "two", "three"], 58 | ) 59 | .unwrap(); 60 | 61 | c.bench_function("counter_with_mapped_labels_fnv", |b| { 62 | b.iter(|| { 63 | let mut labels = HashMap::with_capacity_and_hasher(3, FnvBuildHasher::default()); 64 | labels.insert("two", "zwei"); 65 | labels.insert("one", "eins"); 66 | labels.insert("three", "drei"); 67 | counter.with(&black_box(labels)).inc(); 68 | }) 69 | }); 70 | } 71 | 72 | fn bench_counter_with_prepared_mapped_labels(c: &mut Criterion) { 73 | let counter = CounterVec::new( 74 | Opts::new("benchmark_counter", "A counter to benchmark it."), 75 | &["one", "two", "three"], 76 | ) 77 | .unwrap(); 78 | 79 | let mut labels = HashMap::with_capacity(3); 80 | labels.insert("two", "zwei"); 81 | labels.insert("one", "eins"); 82 | labels.insert("three", "drei"); 83 | 84 | c.bench_function("counter_with_prepared_mapped_labels", |b| { 85 | b.iter(|| { 86 | counter.with(&labels).inc(); 87 | }) 88 | }); 89 | } 90 | 91 | fn bench_counter_no_labels(c: &mut Criterion) { 92 | let counter = Counter::new("benchmark_counter", "A counter to benchmark.").unwrap(); 93 | c.bench_function("counter_no_labels", |b| b.iter(|| counter.inc())); 94 | } 95 | 96 | fn bench_int_counter_no_labels(c: &mut Criterion) { 97 | let counter = IntCounter::new("benchmark_int_counter", "A int_counter to benchmark.").unwrap(); 98 | c.bench_function("int_counter_no_labels", |b| b.iter(|| counter.inc())); 99 | } 100 | 101 | fn bench_counter_no_labels_concurrent_nop(c: &mut Criterion) { 102 | let signal_exit = Arc::new(atomic::AtomicBool::new(false)); 103 | let counter = Counter::new("foo", "bar").unwrap(); 104 | 105 | let thread_handles: Vec<_> = (0..4) 106 | .map(|_| { 107 | let signal_exit2 = signal_exit.clone(); 108 | thread::spawn(move || { 109 | while !signal_exit2.load(atomic::Ordering::Relaxed) { 110 | // Do nothing as the control group. 111 | } 112 | }) 113 | }) 114 | .collect(); 115 | 116 | c.bench_function("counter_no_labels_concurrent_nop", |b| { 117 | b.iter(|| counter.inc()); 118 | }); 119 | 120 | // Wait for accompanying thread to exit. 121 | signal_exit.store(true, atomic::Ordering::Relaxed); 122 | for h in thread_handles { 123 | h.join().unwrap(); 124 | } 125 | } 126 | 127 | fn bench_counter_no_labels_concurrent_write(c: &mut Criterion) { 128 | let signal_exit = Arc::new(atomic::AtomicBool::new(false)); 129 | let counter = Counter::new("foo", "bar").unwrap(); 130 | 131 | let thread_handles: Vec<_> = (0..4) 132 | .map(|_| { 133 | let signal_exit2 = signal_exit.clone(); 134 | let counter2 = counter.clone(); 135 | thread::spawn(move || { 136 | while !signal_exit2.load(atomic::Ordering::Relaxed) { 137 | // Update counter concurrently as the normal group. 138 | counter2.inc(); 139 | } 140 | }) 141 | }) 142 | .collect(); 143 | 144 | c.bench_function("counter_no_labels_concurrent_write", |b| { 145 | b.iter(|| counter.inc()); 146 | }); 147 | 148 | // Wait for accompanying thread to exit. 149 | signal_exit.store(true, atomic::Ordering::Relaxed); 150 | for h in thread_handles { 151 | h.join().unwrap(); 152 | } 153 | } 154 | 155 | fn bench_int_counter_no_labels_concurrent_write(c: &mut Criterion) { 156 | let signal_exit = Arc::new(atomic::AtomicBool::new(false)); 157 | let counter = IntCounter::new("foo", "bar").unwrap(); 158 | 159 | let thread_handles: Vec<_> = (0..4) 160 | .map(|_| { 161 | let signal_exit2 = signal_exit.clone(); 162 | let counter2 = counter.clone(); 163 | thread::spawn(move || { 164 | while !signal_exit2.load(atomic::Ordering::Relaxed) { 165 | // Update counter concurrently as the normal group. 166 | counter2.inc(); 167 | } 168 | }) 169 | }) 170 | .collect(); 171 | 172 | c.bench_function("int_counter_no_labels_concurrent_write", |b| { 173 | b.iter(|| counter.inc()); 174 | }); 175 | 176 | // Wait for accompanying thread to exit. 177 | signal_exit.store(true, atomic::Ordering::Relaxed); 178 | for h in thread_handles { 179 | h.join().unwrap(); 180 | } 181 | } 182 | 183 | fn bench_counter_with_label_values_concurrent_write(c: &mut Criterion) { 184 | let signal_exit = Arc::new(atomic::AtomicBool::new(false)); 185 | let counter = CounterVec::new(Opts::new("foo", "bar"), &["one", "two", "three"]).unwrap(); 186 | 187 | let thread_handles: Vec<_> = (0..4) 188 | .map(|_| { 189 | let signal_exit2 = signal_exit.clone(); 190 | let counter2 = counter.clone(); 191 | thread::spawn(move || { 192 | while !signal_exit2.load(atomic::Ordering::Relaxed) { 193 | counter2.with_label_values(&["eins", "zwei", "drei"]).inc(); 194 | } 195 | }) 196 | }) 197 | .collect(); 198 | 199 | c.bench_function("counter_with_label_values_concurrent_write", |b| { 200 | b.iter(|| counter.with_label_values(&["eins", "zwei", "drei"]).inc()); 201 | }); 202 | 203 | // Wait for accompanying thread to exit. 204 | signal_exit.store(true, atomic::Ordering::Relaxed); 205 | for h in thread_handles { 206 | h.join().unwrap(); 207 | } 208 | } 209 | 210 | criterion_group!( 211 | benches, 212 | bench_counter_no_labels, 213 | bench_counter_no_labels_concurrent_nop, 214 | bench_counter_no_labels_concurrent_write, 215 | bench_counter_with_label_values, 216 | bench_counter_with_label_values_concurrent_write, 217 | bench_counter_with_mapped_labels, 218 | bench_counter_with_mapped_labels_fnv, 219 | bench_counter_with_prepared_mapped_labels, 220 | bench_int_counter_no_labels, 221 | bench_int_counter_no_labels_concurrent_write, 222 | ); 223 | criterion_main!(benches); 224 | -------------------------------------------------------------------------------- /benches/desc.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 15 | use prometheus::core::Desc; 16 | 17 | fn description_validation(c: &mut Criterion) { 18 | c.bench_function("description_validation", |b| { 19 | b.iter(|| { 20 | black_box(Desc::new( 21 | "api_http_requests_total".to_string(), 22 | "not empty help".to_string(), 23 | vec!["method".to_string(), "handler".to_string()], 24 | Default::default(), 25 | )) 26 | }); 27 | }); 28 | } 29 | 30 | criterion_group!(benches, description_validation); 31 | criterion_main!(benches); 32 | -------------------------------------------------------------------------------- /benches/gauge.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | use criterion::{criterion_group, criterion_main, Criterion}; 15 | use prometheus::{Gauge, GaugeVec, IntGauge, Opts}; 16 | 17 | fn bench_gauge_with_label_values(c: &mut Criterion) { 18 | let gauge = GaugeVec::new( 19 | Opts::new("benchmark_gauge", "A gauge to benchmark it."), 20 | &["one", "two", "three"], 21 | ) 22 | .unwrap(); 23 | c.bench_function("gauge_with_label_values", |b| { 24 | b.iter(|| gauge.with_label_values(&["eins", "zwei", "drei"]).inc()) 25 | }); 26 | } 27 | 28 | fn bench_gauge_no_labels(c: &mut Criterion) { 29 | let gauge = Gauge::new("benchmark_gauge", "A gauge to benchmark.").unwrap(); 30 | c.bench_function("gauge_no_labels", |b| b.iter(|| gauge.inc())); 31 | } 32 | 33 | fn bench_int_gauge_no_labels(c: &mut Criterion) { 34 | let gauge = IntGauge::new("benchmark_int_gauge", "A int_gauge to benchmark.").unwrap(); 35 | c.bench_function("int_gauge_no_labels", |b| b.iter(|| gauge.inc())); 36 | } 37 | 38 | criterion_group!( 39 | benches, 40 | bench_gauge_with_label_values, 41 | bench_gauge_no_labels, 42 | bench_int_gauge_no_labels, 43 | ); 44 | criterion_main!(benches); 45 | -------------------------------------------------------------------------------- /benches/histogram.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | use criterion::{criterion_group, criterion_main, Criterion}; 15 | use prometheus::{core::Collector, Histogram, HistogramOpts, HistogramVec}; 16 | use std::sync::{atomic, Arc}; 17 | use std::thread; 18 | 19 | fn bench_histogram_with_label_values(c: &mut Criterion) { 20 | let histogram = HistogramVec::new( 21 | HistogramOpts::new("benchmark_histogram", "A histogram to benchmark it."), 22 | &["one", "two", "three"], 23 | ) 24 | .unwrap(); 25 | c.bench_function("bench_histogram_with_label_values", |b| { 26 | b.iter(|| { 27 | histogram 28 | .with_label_values(&["eins", "zwei", "drei"]) 29 | .observe(3.1415) 30 | }) 31 | }); 32 | } 33 | 34 | fn bench_histogram_no_labels(c: &mut Criterion) { 35 | let histogram = Histogram::with_opts(HistogramOpts::new( 36 | "benchmark_histogram", 37 | "A histogram to benchmark it.", 38 | )) 39 | .unwrap(); 40 | c.bench_function("bench_histogram_no_labels", |b| { 41 | b.iter(|| histogram.observe(3.1415)) 42 | }); 43 | } 44 | 45 | fn bench_histogram_timer(c: &mut Criterion) { 46 | let histogram = Histogram::with_opts(HistogramOpts::new( 47 | "benchmark_histogram_timer", 48 | "A histogram to benchmark it.", 49 | )) 50 | .unwrap(); 51 | c.bench_function("bench_histogram_timer", |b| { 52 | b.iter(|| histogram.start_timer()) 53 | }); 54 | } 55 | fn bench_histogram_local(c: &mut Criterion) { 56 | let histogram = Histogram::with_opts(HistogramOpts::new( 57 | "benchmark_histogram_local", 58 | "A histogram to benchmark it.", 59 | )) 60 | .unwrap(); 61 | let local = histogram.local(); 62 | c.bench_function("bench_histogram_local", |b| { 63 | b.iter(|| local.observe(3.1415)); 64 | }); 65 | local.flush(); 66 | } 67 | 68 | fn bench_local_histogram_timer(c: &mut Criterion) { 69 | let histogram = Histogram::with_opts(HistogramOpts::new( 70 | "benchmark_histogram_local_timer", 71 | "A histogram to benchmark it.", 72 | )) 73 | .unwrap(); 74 | let local = histogram.local(); 75 | c.bench_function("bench_local_histogram_timer", |b| { 76 | b.iter(|| local.start_timer()); 77 | }); 78 | local.flush(); 79 | } 80 | 81 | fn concurrent_observe_and_collect(c: &mut Criterion) { 82 | let signal_exit = Arc::new(atomic::AtomicBool::new(false)); 83 | let opts = HistogramOpts::new("test_name", "test help").buckets(vec![1.0]); 84 | let histogram = Histogram::with_opts(opts).unwrap(); 85 | 86 | let mut handlers = vec![]; 87 | 88 | for _ in 0..4 { 89 | let histogram = histogram.clone(); 90 | let signal_exit = signal_exit.clone(); 91 | handlers.push(thread::spawn(move || { 92 | while !signal_exit.load(atomic::Ordering::Relaxed) { 93 | for _ in 0..1_000 { 94 | histogram.observe(1.0); 95 | } 96 | 97 | histogram.collect(); 98 | } 99 | })); 100 | } 101 | 102 | c.bench_function("concurrent_observe_and_collect", |b| { 103 | b.iter(|| histogram.observe(1.0)); 104 | }); 105 | 106 | signal_exit.store(true, atomic::Ordering::Relaxed); 107 | for handler in handlers { 108 | handler.join().unwrap(); 109 | } 110 | } 111 | 112 | criterion_group!( 113 | benches, 114 | bench_histogram_with_label_values, 115 | bench_histogram_no_labels, 116 | bench_histogram_timer, 117 | bench_histogram_local, 118 | bench_local_histogram_timer, 119 | concurrent_observe_and_collect, 120 | ); 121 | criterion_main!(benches); 122 | 123 | /* 124 | #[bench] 125 | #[cfg(feature = "nightly")] 126 | fn bench_histogram_coarse_timer(c: &mut Criterion) { 127 | let histogram = Histogram::with_opts(HistogramOpts::new( 128 | "benchmark_histogram_timer", 129 | "A histogram to benchmark it.", 130 | )) 131 | .unwrap(); 132 | b.iter(|| histogram.start_coarse_timer()) 133 | } 134 | 135 | #[bench] 136 | #[cfg(feature = "nightly")] 137 | fn bench_local_histogram_coarse_timer(c: &mut Criterion) { 138 | let histogram = Histogram::with_opts(HistogramOpts::new( 139 | "benchmark_histogram_timer", 140 | "A histogram to benchmark it.", 141 | )) 142 | .unwrap(); 143 | let local = histogram.local(); 144 | b.iter(|| local.start_coarse_timer()); 145 | local.flush(); 146 | } 147 | */ 148 | -------------------------------------------------------------------------------- /benches/text_encoder.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 PingCAP, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | use criterion::{criterion_group, criterion_main, Criterion}; 15 | use prometheus::{CounterVec, Encoder, HistogramOpts, HistogramVec, Opts, Registry, TextEncoder}; 16 | 17 | fn bench_text_encoder_without_escaping(c: &mut Criterion) { 18 | let registry = registry_with_test_metrics(false); 19 | run_text_encoder(c, "text_encoder_without_escaping", registry) 20 | } 21 | 22 | fn bench_text_encoder_with_escaping(c: &mut Criterion) { 23 | let registry = registry_with_test_metrics(true); 24 | run_text_encoder(c, "text_encoder_with_escaping", registry) 25 | } 26 | 27 | fn registry_with_test_metrics(with_escaping: bool) -> Registry { 28 | let registry = Registry::new(); 29 | 30 | for i in 0..100 { 31 | let counter = CounterVec::new( 32 | Opts::new( 33 | format!("benchmark_counter_{}", i), 34 | "A counter to benchmark it.", 35 | ), 36 | &["one", "two", "three"], 37 | ) 38 | .unwrap(); 39 | registry.register(Box::new(counter.clone())).unwrap(); 40 | 41 | let histogram = HistogramVec::new( 42 | HistogramOpts::new( 43 | format!("benchmark_histogram_{}", i), 44 | "A histogram to benchmark it.", 45 | ), 46 | &["one", "two", "three"], 47 | ) 48 | .unwrap(); 49 | registry.register(Box::new(histogram.clone())).unwrap(); 50 | 51 | for j in 0..100 { 52 | let j_string = j.to_string(); 53 | let label_values = if with_escaping { 54 | ["ei\\ns\n", "zw\"e\"i", &j_string] 55 | } else { 56 | ["eins", "zwei", &j_string] 57 | }; 58 | 59 | counter.with_label_values(&label_values).inc(); 60 | histogram.with_label_values(&label_values).observe(j.into()); 61 | } 62 | } 63 | 64 | registry 65 | } 66 | 67 | fn run_text_encoder(c: &mut Criterion, label: &str, registry: Registry) { 68 | let mut buffer = vec![]; 69 | let encoder = TextEncoder::new(); 70 | let metric_families = registry.gather(); 71 | c.bench_function(label, |b| { 72 | b.iter(|| encoder.encode(&metric_families, &mut buffer).unwrap()); 73 | }); 74 | } 75 | 76 | criterion_group!( 77 | benches, 78 | bench_text_encoder_without_escaping, 79 | bench_text_encoder_with_escaping, 80 | ); 81 | criterion_main!(benches); 82 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | #[cfg(feature = "gen")] 4 | fn generate_protobuf_binding_file() { 5 | protobuf_codegen::Codegen::new() 6 | .out_dir("proto") 7 | .inputs(["proto/proto_model.proto"]) 8 | .includes(["proto"]) 9 | .run() 10 | .expect("Protobuf codegen failed"); 11 | } 12 | 13 | #[cfg(not(feature = "gen"))] 14 | fn generate_protobuf_binding_file() {} 15 | 16 | fn main() { 17 | generate_protobuf_binding_file() 18 | } 19 | -------------------------------------------------------------------------------- /docs/release.md: -------------------------------------------------------------------------------- 1 | # Release process 2 | 3 | ## Crate prometheus 4 | 5 | 1. Create pull request with bumped `version` in `Cargo.toml` and updated `CHANGELOG.md`. 6 | 7 | 2. Once merged clean your local environment. 8 | 9 | ```bash 10 | cargo clean 11 | git clean -fd 12 | ``` 13 | 14 | 3. Tag the release. 15 | 16 | ```bash 17 | tag="v$(sed -En 's/^version = \"(.*)\"$/\1/p' Cargo.toml)" 18 | git tag -s "${tag}" -m "${tag}" 19 | ``` 20 | 21 | 4. Publish the release. 22 | 23 | ```bash 24 | cargo publish 25 | ``` 26 | 27 | 5. Push the tag. 28 | 29 | ```bash 30 | git push origin $tag 31 | ``` 32 | 33 | ## Crate prometheus-static-metric 34 | 35 | 1. Create pull request with bumped `version` in `static-metric/Cargo.toml` and updated `static-metric/CHANGELOG.md`. 36 | 37 | 2. Once merged clean your local environment. 38 | 39 | ```bash 40 | cd static-metric 41 | cargo clean 42 | git clean -fd 43 | ``` 44 | 45 | 3. Tag the release. 46 | 47 | ```bash 48 | tag="$(sed -En 's/^name = \"(.*)\"$/\1/p' Cargo.toml | head -n 1)-v$(sed -En 's/^version = \"(.*)\"$/\1/p' Cargo.toml)" 49 | git tag -s "${tag}" -m "${tag}" 50 | ``` 51 | 52 | 4. Publish the release. 53 | 54 | ```bash 55 | cargo publish 56 | ``` 57 | 58 | 5. Push the tag. 59 | 60 | ```bash 61 | git push origin $tag 62 | ``` 63 | 64 | -------------------------------------------------------------------------------- /examples/example_custom_registry.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | //! This examples shows how to use multiple and custom registries, 4 | //! and how to perform registration across function boundaries. 5 | 6 | use std::collections::HashMap; 7 | 8 | use prometheus::{Encoder, IntCounter, Registry}; 9 | 10 | use lazy_static::lazy_static; 11 | 12 | lazy_static! { 13 | static ref DEFAULT_COUNTER: IntCounter = IntCounter::new("default", "generic counter").unwrap(); 14 | static ref CUSTOM_COUNTER: IntCounter = IntCounter::new("custom", "dedicated counter").unwrap(); 15 | } 16 | 17 | fn main() { 18 | // Register default metrics. 19 | default_metrics(prometheus::default_registry()); 20 | 21 | // Register custom metrics to a custom registry. 22 | let mut labels = HashMap::new(); 23 | labels.insert("mykey".to_string(), "myvalue".to_string()); 24 | let custom_registry = Registry::new_custom(Some("myprefix".to_string()), Some(labels)).unwrap(); 25 | custom_metrics(&custom_registry); 26 | 27 | // Print metrics for the default registry. 28 | let mut buffer = Vec::::new(); 29 | let encoder = prometheus::TextEncoder::new(); 30 | encoder.encode(&prometheus::gather(), &mut buffer).unwrap(); 31 | println!("## Default registry"); 32 | println!("{}", String::from_utf8(buffer.clone()).unwrap()); 33 | 34 | // Print metrics for the custom registry. 35 | let mut buffer = Vec::::new(); 36 | let encoder = prometheus::TextEncoder::new(); 37 | encoder 38 | .encode(&custom_registry.gather(), &mut buffer) 39 | .unwrap(); 40 | println!("## Custom registry"); 41 | println!("{}", String::from_utf8(buffer.clone()).unwrap()); 42 | } 43 | 44 | /// Default metrics, to be collected by the default registry. 45 | fn default_metrics(registry: &Registry) { 46 | registry 47 | .register(Box::new(DEFAULT_COUNTER.clone())) 48 | .unwrap(); 49 | 50 | DEFAULT_COUNTER.inc(); 51 | assert_eq!(DEFAULT_COUNTER.get(), 1); 52 | } 53 | 54 | /// Custom metrics, to be collected by a dedicated registry. 55 | fn custom_metrics(registry: &Registry) { 56 | registry.register(Box::new(CUSTOM_COUNTER.clone())).unwrap(); 57 | 58 | CUSTOM_COUNTER.inc_by(42); 59 | assert_eq!(CUSTOM_COUNTER.get(), 42); 60 | } 61 | -------------------------------------------------------------------------------- /examples/example_edition_2018.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | use prometheus::register_counter; 4 | 5 | /// small example that uses rust 2018 style macro imports 6 | 7 | #[allow(unused)] 8 | fn main() { 9 | register_counter!("test_macro_3", "help"); 10 | prometheus::register_counter_vec!("errorz", "errors", &["error"]).unwrap(); 11 | prometheus::register_gauge!("test_macro_gauge_2", "help"); 12 | prometheus::register_gauge_vec!("test_macro_gauge_vec_3", "help", &["a", "b"]); 13 | prometheus::register_histogram!("test_macro_histogram_4", "help", vec![1.0, 2.0]); 14 | prometheus::register_histogram_vec!( 15 | "test_macro_histogram_4", 16 | "help", 17 | &["a", "b"], 18 | vec![1.0, 2.0] 19 | ); 20 | prometheus::register_int_counter!("meh", "foo"); 21 | prometheus::register_int_counter_vec!("errorz", "errors", &["error"]).unwrap(); 22 | prometheus::register_int_gauge!("test_macro_gauge_2", "help"); 23 | prometheus::register_int_gauge_vec!("test_macro_gauge_vec_4", "help", &["a", "b"]); 24 | } 25 | -------------------------------------------------------------------------------- /examples/example_embed.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | use std::thread; 4 | use std::time::Duration; 5 | 6 | use prometheus::{Counter, CounterVec, Encoder, Gauge, GaugeVec, Opts, Registry, TextEncoder}; 7 | 8 | fn main() { 9 | let r = Registry::new(); 10 | 11 | let counter_opts = Opts::new("test_counter", "test counter help") 12 | .const_label("a", "1") 13 | .const_label("b", "2"); 14 | let counter = Counter::with_opts(counter_opts).unwrap(); 15 | let counter_vec_opts = Opts::new("test_counter_vec", "test counter vector help") 16 | .const_label("a", "1") 17 | .const_label("b", "2"); 18 | let counter_vec = CounterVec::new(counter_vec_opts, &["c", "d"]).unwrap(); 19 | 20 | r.register(Box::new(counter.clone())).unwrap(); 21 | r.register(Box::new(counter_vec.clone())).unwrap(); 22 | 23 | let gauge_opts = Opts::new("test_gauge", "test gauge help") 24 | .const_label("a", "1") 25 | .const_label("b", "2"); 26 | let gauge = Gauge::with_opts(gauge_opts).unwrap(); 27 | let gauge_vec_opts = Opts::new("test_gauge_vec", "test gauge vector help") 28 | .const_label("a", "1") 29 | .const_label("b", "2"); 30 | let gauge_vec = GaugeVec::new(gauge_vec_opts, &["c", "d"]).unwrap(); 31 | 32 | r.register(Box::new(gauge.clone())).unwrap(); 33 | r.register(Box::new(gauge_vec.clone())).unwrap(); 34 | 35 | counter.inc(); 36 | assert_eq!(counter.get() as u64, 1); 37 | counter.inc_by(42.0); 38 | assert_eq!(counter.get() as u64, 43); 39 | 40 | counter_vec.with_label_values(&["3", "4"]).inc(); 41 | assert_eq!(counter_vec.with_label_values(&["3", "4"]).get() as u64, 1); 42 | 43 | counter_vec.with_label_values(&["3", "4"]).inc_by(42.0); 44 | assert_eq!(counter_vec.with_label_values(&["3", "4"]).get() as u64, 43); 45 | 46 | gauge.inc(); 47 | assert_eq!(gauge.get() as u64, 1); 48 | gauge.add(42.0); 49 | assert_eq!(gauge.get() as u64, 43); 50 | 51 | gauge_vec.with_label_values(&["3", "4"]).inc(); 52 | assert_eq!(gauge_vec.with_label_values(&["3", "4"]).get() as u64, 1); 53 | 54 | gauge_vec.with_label_values(&["3", "4"]).set(42.0); 55 | assert_eq!(gauge_vec.with_label_values(&["3", "4"]).get() as u64, 42); 56 | 57 | let c2 = counter.clone(); 58 | let cv2 = counter_vec.clone(); 59 | let g2 = gauge.clone(); 60 | let gv2 = gauge_vec.clone(); 61 | thread::spawn(move || { 62 | for _ in 0..10 { 63 | thread::sleep(Duration::from_millis(500)); 64 | c2.inc(); 65 | cv2.with_label_values(&["3", "4"]).inc(); 66 | g2.inc(); 67 | gv2.with_label_values(&["3", "4"]).inc(); 68 | } 69 | }); 70 | 71 | thread::spawn(move || { 72 | for _ in 0..5 { 73 | thread::sleep(Duration::from_secs(1)); 74 | counter.inc(); 75 | counter_vec.with_label_values(&["3", "4"]).inc(); 76 | gauge.dec(); 77 | gauge_vec.with_label_values(&["3", "4"]).set(42.0); 78 | } 79 | }); 80 | 81 | // Choose your writer and encoder. 82 | let mut buffer = Vec::::new(); 83 | let encoder = TextEncoder::new(); 84 | for _ in 0..5 { 85 | let metric_families = r.gather(); 86 | encoder.encode(&metric_families, &mut buffer).unwrap(); 87 | 88 | // Output to the standard output. 89 | println!("{}", String::from_utf8(buffer.clone()).unwrap()); 90 | 91 | buffer.clear(); 92 | thread::sleep(Duration::from_secs(1)); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /examples/example_hyper.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | use std::net::SocketAddr; 4 | 5 | use hyper::body::Incoming; 6 | use hyper::header::CONTENT_TYPE; 7 | use hyper::server::conn::http1; 8 | use hyper::service::service_fn; 9 | use hyper::Request; 10 | use hyper::Response; 11 | use hyper_util::rt::TokioIo; 12 | use lazy_static::lazy_static; 13 | use prometheus::{labels, opts, register_counter, register_gauge, register_histogram_vec}; 14 | use prometheus::{Counter, Encoder, Gauge, HistogramVec, TextEncoder}; 15 | use tokio::net::TcpListener; 16 | 17 | type BoxedErr = Box; 18 | 19 | lazy_static! { 20 | static ref HTTP_COUNTER: Counter = register_counter!(opts!( 21 | "example_http_requests_total", 22 | "Number of HTTP requests made.", 23 | labels! {"handler" => "all",} 24 | )) 25 | .unwrap(); 26 | static ref HTTP_BODY_GAUGE: Gauge = register_gauge!(opts!( 27 | "example_http_response_size_bytes", 28 | "The HTTP response sizes in bytes.", 29 | labels! {"handler" => "all",} 30 | )) 31 | .unwrap(); 32 | static ref HTTP_REQ_HISTOGRAM: HistogramVec = register_histogram_vec!( 33 | "example_http_request_duration_seconds", 34 | "The HTTP request latencies in seconds.", 35 | &["handler"] 36 | ) 37 | .unwrap(); 38 | } 39 | 40 | async fn serve_req(_req: Request) -> Result, BoxedErr> { 41 | let encoder = TextEncoder::new(); 42 | 43 | HTTP_COUNTER.inc(); 44 | let timer = HTTP_REQ_HISTOGRAM.with_label_values(&["all"]).start_timer(); 45 | 46 | let metric_families = prometheus::gather(); 47 | let body = encoder.encode_to_string(&metric_families)?; 48 | HTTP_BODY_GAUGE.set(body.len() as f64); 49 | 50 | let response = Response::builder() 51 | .status(200) 52 | .header(CONTENT_TYPE, encoder.format_type()) 53 | .body(body)?; 54 | 55 | timer.observe_duration(); 56 | 57 | Ok(response) 58 | } 59 | 60 | #[tokio::main] 61 | async fn main() -> Result<(), BoxedErr> { 62 | let addr: SocketAddr = ([127, 0, 0, 1], 9898).into(); 63 | println!("Listening on http://{}", addr); 64 | let listener = TcpListener::bind(addr).await?; 65 | 66 | loop { 67 | let (stream, _) = listener.accept().await?; 68 | let io = TokioIo::new(stream); 69 | 70 | let service = service_fn(serve_req); 71 | if let Err(err) = http1::Builder::new().serve_connection(io, service).await { 72 | eprintln!("server error: {:?}", err); 73 | }; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /examples/example_int_metrics.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | use prometheus::{IntCounter, IntCounterVec, IntGauge, IntGaugeVec}; 4 | 5 | use lazy_static::lazy_static; 6 | use prometheus::{ 7 | register_int_counter, register_int_counter_vec, register_int_gauge, register_int_gauge_vec, 8 | }; 9 | 10 | lazy_static! { 11 | static ref A_INT_COUNTER: IntCounter = 12 | register_int_counter!("A_int_counter", "foobar").unwrap(); 13 | static ref A_INT_COUNTER_VEC: IntCounterVec = 14 | register_int_counter_vec!("A_int_counter_vec", "foobar", &["a", "b"]).unwrap(); 15 | static ref A_INT_GAUGE: IntGauge = register_int_gauge!("A_int_gauge", "foobar").unwrap(); 16 | static ref A_INT_GAUGE_VEC: IntGaugeVec = 17 | register_int_gauge_vec!("A_int_gauge_vec", "foobar", &["a", "b"]).unwrap(); 18 | } 19 | 20 | fn main() { 21 | A_INT_COUNTER.inc(); 22 | A_INT_COUNTER.inc_by(10); 23 | assert_eq!(A_INT_COUNTER.get(), 11); 24 | 25 | A_INT_COUNTER_VEC.with_label_values(&["a", "b"]).inc_by(5); 26 | assert_eq!(A_INT_COUNTER_VEC.with_label_values(&["a", "b"]).get(), 5); 27 | 28 | A_INT_COUNTER_VEC.with_label_values(&["c", "d"]).inc(); 29 | assert_eq!(A_INT_COUNTER_VEC.with_label_values(&["c", "d"]).get(), 1); 30 | 31 | A_INT_GAUGE.set(5); 32 | assert_eq!(A_INT_GAUGE.get(), 5); 33 | A_INT_GAUGE.dec(); 34 | assert_eq!(A_INT_GAUGE.get(), 4); 35 | A_INT_GAUGE.add(2); 36 | assert_eq!(A_INT_GAUGE.get(), 6); 37 | 38 | A_INT_GAUGE_VEC.with_label_values(&["a", "b"]).set(10); 39 | A_INT_GAUGE_VEC.with_label_values(&["a", "b"]).dec(); 40 | A_INT_GAUGE_VEC.with_label_values(&["a", "b"]).sub(2); 41 | assert_eq!(A_INT_GAUGE_VEC.with_label_values(&["a", "b"]).get(), 7); 42 | } 43 | -------------------------------------------------------------------------------- /examples/example_process_collector.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | fn main() { 4 | use std::thread; 5 | use std::time::Duration; 6 | 7 | use prometheus::Encoder; 8 | 9 | // A default ProcessCollector is registered automatically. 10 | let mut buffer = Vec::new(); 11 | let encoder = prometheus::TextEncoder::new(); 12 | for _ in 0..5 { 13 | let metric_families = prometheus::gather(); 14 | encoder.encode(&metric_families, &mut buffer).unwrap(); 15 | 16 | // Output to the standard output. 17 | println!("{}", String::from_utf8(buffer.clone()).unwrap()); 18 | 19 | buffer.clear(); 20 | thread::sleep(Duration::from_secs(1)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/example_push.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | use std::env; 4 | use std::thread; 5 | use std::time; 6 | 7 | use getopts::Options; 8 | use prometheus::{Counter, Histogram}; 9 | 10 | use lazy_static::lazy_static; 11 | use prometheus::{labels, register_counter, register_histogram}; 12 | 13 | lazy_static! { 14 | static ref PUSH_COUNTER: Counter = register_counter!( 15 | "example_push_total", 16 | "Total number of prometheus client pushed." 17 | ) 18 | .unwrap(); 19 | static ref PUSH_REQ_HISTOGRAM: Histogram = register_histogram!( 20 | "example_push_request_duration_seconds", 21 | "The push request latencies in seconds." 22 | ) 23 | .unwrap(); 24 | } 25 | 26 | fn main() { 27 | let args: Vec = env::args().collect(); 28 | let program = args[0].clone(); 29 | 30 | let mut opts = Options::new(); 31 | opts.optopt( 32 | "A", 33 | "addr", 34 | "prometheus pushgateway address", 35 | "default is 127.0.0.1:9091", 36 | ); 37 | opts.optflag("h", "help", "print this help menu"); 38 | 39 | let matches = opts.parse(&args).unwrap(); 40 | if matches.opt_present("h") || !matches.opt_present("A") { 41 | let brief = format!("Usage: {} [options]", program); 42 | print!("{}", opts.usage(&brief)); 43 | return; 44 | } 45 | println!("Pushing, please start Pushgateway first."); 46 | 47 | let address = matches.opt_str("A").unwrap_or("127.0.0.1:9091".to_owned()); 48 | for _ in 0..5 { 49 | thread::sleep(time::Duration::from_secs(2)); 50 | PUSH_COUNTER.inc(); 51 | let metric_families = prometheus::gather(); 52 | let _timer = PUSH_REQ_HISTOGRAM.start_timer(); // drop as observe 53 | prometheus::push_metrics( 54 | "example_push", 55 | labels! {"instance".to_owned() => "HAL-9000".to_owned(),}, 56 | &address, 57 | metric_families, 58 | Some(prometheus::BasicAuthentication { 59 | username: "user".to_owned(), 60 | password: "pass".to_owned(), 61 | }), 62 | ) 63 | .unwrap(); 64 | } 65 | 66 | println!("Okay, please check the Pushgateway."); 67 | } 68 | -------------------------------------------------------------------------------- /proto/mod.rs: -------------------------------------------------------------------------------- 1 | // @generated 2 | 3 | pub mod proto_model; 4 | -------------------------------------------------------------------------------- /proto/proto_model.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Prometheus Team 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | syntax = "proto2"; 15 | 16 | package io.prometheus.client; 17 | option java_package = "io.prometheus.client"; 18 | 19 | message LabelPair { 20 | optional string name = 1; 21 | optional string value = 2; 22 | } 23 | 24 | enum MetricType { 25 | COUNTER = 0; 26 | GAUGE = 1; 27 | SUMMARY = 2; 28 | UNTYPED = 3; 29 | HISTOGRAM = 4; 30 | } 31 | 32 | message Gauge { 33 | optional double value = 1; 34 | } 35 | 36 | message Counter { 37 | optional double value = 1; 38 | } 39 | 40 | message Quantile { 41 | optional double quantile = 1; 42 | optional double value = 2; 43 | } 44 | 45 | message Summary { 46 | optional uint64 sample_count = 1; 47 | optional double sample_sum = 2; 48 | repeated Quantile quantile = 3; 49 | } 50 | 51 | message Untyped { 52 | optional double value = 1; 53 | } 54 | 55 | message Histogram { 56 | optional uint64 sample_count = 1; 57 | optional double sample_sum = 2; 58 | repeated Bucket bucket = 3; // Ordered in increasing order of upper_bound, +Inf bucket is optional. 59 | } 60 | 61 | message Bucket { 62 | optional uint64 cumulative_count = 1; // Cumulative in increasing order. 63 | optional double upper_bound = 2; // Inclusive. 64 | } 65 | 66 | message Metric { 67 | repeated LabelPair label = 1; 68 | optional Gauge gauge = 2; 69 | optional Counter counter = 3; 70 | optional Summary summary = 4; 71 | optional Untyped untyped = 5; 72 | optional Histogram histogram = 7; 73 | optional int64 timestamp_ms = 6; 74 | } 75 | 76 | message MetricFamily { 77 | optional string name = 1; 78 | optional string help = 2; 79 | optional MetricType type = 3; 80 | repeated Metric metric = 4; 81 | } 82 | -------------------------------------------------------------------------------- /src/atomic64.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Prometheus Authors 2 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 3 | 4 | use std::cmp::*; 5 | use std::f64; 6 | use std::ops::*; 7 | use std::sync::atomic::{AtomicI64 as StdAtomicI64, AtomicU64 as StdAtomicU64, Ordering}; 8 | 9 | /// An interface for numbers. Used to generically model float metrics and integer metrics, i.e. 10 | /// [`Counter`](crate::Counter) and [`IntCounter`](crate::Counter). 11 | pub trait Number: 12 | Sized + AddAssign + SubAssign + PartialOrd + PartialEq + Copy + Send + Sync 13 | { 14 | /// `std::convert::From for f64` is not implemented, so that we need to implement our own. 15 | fn from_i64(v: i64) -> Self; 16 | /// Convert to a f64. 17 | fn into_f64(self) -> f64; 18 | } 19 | 20 | impl Number for i64 { 21 | #[inline] 22 | fn from_i64(v: i64) -> Self { 23 | v 24 | } 25 | 26 | #[inline] 27 | fn into_f64(self) -> f64 { 28 | self as f64 29 | } 30 | } 31 | 32 | impl Number for u64 { 33 | #[inline] 34 | fn from_i64(v: i64) -> Self { 35 | v as u64 36 | } 37 | 38 | #[inline] 39 | fn into_f64(self) -> f64 { 40 | self as f64 41 | } 42 | } 43 | 44 | impl Number for f64 { 45 | #[inline] 46 | fn from_i64(v: i64) -> Self { 47 | v as f64 48 | } 49 | 50 | #[inline] 51 | fn into_f64(self) -> f64 { 52 | self 53 | } 54 | } 55 | 56 | /// An interface for atomics. Used to generically model float metrics and integer metrics, i.e. 57 | /// [`Counter`](crate::Counter) and [`IntCounter`](crate::IntCounter). 58 | pub trait Atomic: Send + Sync { 59 | /// The numeric type associated with this atomic. 60 | type T: Number; 61 | /// Create a new atomic value. 62 | fn new(val: Self::T) -> Self; 63 | /// Set the value to the provided value. 64 | fn set(&self, val: Self::T); 65 | /// Get the value. 66 | fn get(&self) -> Self::T; 67 | /// Increment the value by a given amount. 68 | fn inc_by(&self, delta: Self::T); 69 | /// Decrement the value by a given amount. 70 | fn dec_by(&self, delta: Self::T); 71 | } 72 | 73 | /// A atomic float. 74 | #[derive(Debug)] 75 | pub struct AtomicF64 { 76 | inner: StdAtomicU64, 77 | } 78 | 79 | #[inline] 80 | fn u64_to_f64(val: u64) -> f64 { 81 | f64::from_bits(val) 82 | } 83 | 84 | #[inline] 85 | fn f64_to_u64(val: f64) -> u64 { 86 | f64::to_bits(val) 87 | } 88 | 89 | impl Atomic for AtomicF64 { 90 | type T = f64; 91 | 92 | fn new(val: Self::T) -> AtomicF64 { 93 | AtomicF64 { 94 | inner: StdAtomicU64::new(f64_to_u64(val)), 95 | } 96 | } 97 | 98 | #[inline] 99 | fn set(&self, val: Self::T) { 100 | self.inner.store(f64_to_u64(val), Ordering::Relaxed); 101 | } 102 | 103 | #[inline] 104 | fn get(&self) -> Self::T { 105 | u64_to_f64(self.inner.load(Ordering::Relaxed)) 106 | } 107 | 108 | #[inline] 109 | fn inc_by(&self, delta: Self::T) { 110 | loop { 111 | let current = self.inner.load(Ordering::Acquire); 112 | let new = u64_to_f64(current) + delta; 113 | let result = self.inner.compare_exchange_weak( 114 | current, 115 | f64_to_u64(new), 116 | Ordering::Release, 117 | Ordering::Relaxed, 118 | ); 119 | if result.is_ok() { 120 | return; 121 | } 122 | } 123 | } 124 | 125 | #[inline] 126 | fn dec_by(&self, delta: Self::T) { 127 | self.inc_by(-delta); 128 | } 129 | } 130 | 131 | impl AtomicF64 { 132 | /// Store the value, returning the previous value. 133 | pub fn swap(&self, val: f64, ordering: Ordering) -> f64 { 134 | u64_to_f64(self.inner.swap(f64_to_u64(val), ordering)) 135 | } 136 | } 137 | 138 | /// A atomic signed integer. 139 | #[derive(Debug)] 140 | pub struct AtomicI64 { 141 | inner: StdAtomicI64, 142 | } 143 | 144 | impl Atomic for AtomicI64 { 145 | type T = i64; 146 | 147 | fn new(val: Self::T) -> AtomicI64 { 148 | AtomicI64 { 149 | inner: StdAtomicI64::new(val), 150 | } 151 | } 152 | 153 | #[inline] 154 | fn set(&self, val: Self::T) { 155 | self.inner.store(val, Ordering::Relaxed); 156 | } 157 | 158 | #[inline] 159 | fn get(&self) -> Self::T { 160 | self.inner.load(Ordering::Relaxed) 161 | } 162 | 163 | #[inline] 164 | fn inc_by(&self, delta: Self::T) { 165 | self.inner.fetch_add(delta, Ordering::Relaxed); 166 | } 167 | 168 | #[inline] 169 | fn dec_by(&self, delta: Self::T) { 170 | self.inner.fetch_sub(delta, Ordering::Relaxed); 171 | } 172 | } 173 | 174 | /// A atomic unsigned integer. 175 | #[derive(Debug)] 176 | pub struct AtomicU64 { 177 | inner: StdAtomicU64, 178 | } 179 | 180 | impl Atomic for AtomicU64 { 181 | type T = u64; 182 | 183 | fn new(val: Self::T) -> AtomicU64 { 184 | AtomicU64 { 185 | inner: StdAtomicU64::new(val), 186 | } 187 | } 188 | 189 | #[inline] 190 | fn set(&self, val: Self::T) { 191 | self.inner.store(val, Ordering::Relaxed); 192 | } 193 | 194 | #[inline] 195 | fn get(&self) -> Self::T { 196 | self.inner.load(Ordering::Relaxed) 197 | } 198 | 199 | #[inline] 200 | fn inc_by(&self, delta: Self::T) { 201 | self.inc_by_with_ordering(delta, Ordering::Relaxed); 202 | } 203 | 204 | #[inline] 205 | fn dec_by(&self, delta: Self::T) { 206 | self.inner.fetch_sub(delta, Ordering::Relaxed); 207 | } 208 | } 209 | 210 | impl AtomicU64 { 211 | /// Stores a value into the atomic integer if the current value is the same 212 | /// as the current value. 213 | /// 214 | /// This function is allowed to spuriously fail even when the comparison 215 | /// succeeds, which can result in more efficient code on some platforms. The 216 | /// return value is a result indicating whether the new value was written 217 | /// and containing the previous value. 218 | /// 219 | /// See [`StdAtomicU64`] for details. 220 | pub(crate) fn compare_exchange_weak( 221 | &self, 222 | current: u64, 223 | new: u64, 224 | success: Ordering, 225 | failure: Ordering, 226 | ) -> Result { 227 | self.inner 228 | .compare_exchange_weak(current, new, success, failure) 229 | } 230 | 231 | /// Increment the value by a given amount with the provided memory ordering. 232 | pub fn inc_by_with_ordering(&self, delta: u64, ordering: Ordering) { 233 | self.inner.fetch_add(delta, ordering); 234 | } 235 | 236 | /// Stores a value into the atomic integer, returning the previous value. 237 | pub fn swap(&self, val: u64, ordering: Ordering) -> u64 { 238 | self.inner.swap(val, ordering) 239 | } 240 | } 241 | 242 | #[cfg(test)] 243 | mod test { 244 | use std::f64; 245 | use std::f64::consts::PI; 246 | 247 | use super::*; 248 | 249 | #[test] 250 | fn test_atomic_f64() { 251 | let table: Vec = vec![0.0, 1.0, PI, f64::MIN, f64::MAX]; 252 | 253 | for f in table { 254 | assert!((f - AtomicF64::new(f).get()).abs() < f64::EPSILON); 255 | } 256 | } 257 | 258 | #[test] 259 | fn test_atomic_i64() { 260 | let ai64 = AtomicI64::new(0); 261 | assert_eq!(ai64.get(), 0); 262 | 263 | ai64.inc_by(1); 264 | assert_eq!(ai64.get(), 1); 265 | 266 | ai64.inc_by(-5); 267 | assert_eq!(ai64.get(), -4); 268 | } 269 | 270 | #[test] 271 | fn test_atomic_u64() { 272 | let au64 = AtomicU64::new(0); 273 | assert_eq!(au64.get(), 0); 274 | 275 | au64.inc_by(123); 276 | assert_eq!(au64.get(), 123); 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /src/auto_flush.rs: -------------------------------------------------------------------------------- 1 | use crate::core::Atomic; 2 | use crate::counter::{CounterWithValueType, GenericLocalCounter}; 3 | use crate::histogram::{Instant, LocalHistogram}; 4 | use crate::metrics::MayFlush; 5 | use crate::timer; 6 | use parking_lot::Mutex; 7 | use std::thread::LocalKey; 8 | 9 | /// Delegator for auto flush-able local counter 10 | pub trait CounterDelegator { 11 | /// Get the root local metric for delegate 12 | fn get_root_metric(&self) -> &'static LocalKey; 13 | 14 | /// Get the final counter for delegate 15 | fn get_local<'a>(&self, root_metric: &'a T) -> &'a GenericLocalCounter; 16 | } 17 | 18 | /// Delegator for auto flush-able local counter 19 | pub trait HistogramDelegator { 20 | /// Get the root local metric for delegate 21 | fn get_root_metric(&self) -> &'static LocalKey; 22 | 23 | /// Get the final counter for delegate 24 | fn get_local<'a>(&self, root_metric: &'a T) -> &'a LocalHistogram; 25 | } 26 | 27 | /// Auto flush-able local counter 28 | #[derive(Debug)] 29 | pub struct AFLocalCounter> 30 | { 31 | /// Delegator to get thread local metric 32 | delegator: D, 33 | /// Phantomdata marker 34 | _p: std::marker::PhantomData<(Mutex, Mutex)>, 35 | } 36 | 37 | impl> 38 | AFLocalCounter 39 | { 40 | /// Construct a new AFLocalCounter from delegator. 41 | pub fn new(delegator: D) -> AFLocalCounter { 42 | timer::ensure_updater(); 43 | AFLocalCounter { 44 | delegator, 45 | _p: std::marker::PhantomData, 46 | } 47 | } 48 | } 49 | 50 | /// Auto flush-able local counter 51 | impl> 52 | AFLocalCounter 53 | { 54 | #[inline] 55 | /// Get the root local metric for delegate 56 | fn get_root_metric(&self) -> &'static LocalKey { 57 | self.delegator.get_root_metric() 58 | } 59 | 60 | #[inline] 61 | /// Get the final counter for delegate 62 | fn get_counter<'a>(&self, root_metric: &'a T) -> &'a GenericLocalCounter { 63 | self.delegator.get_local(root_metric) 64 | } 65 | 66 | /// Increase the given value to the local counter, 67 | /// and try to flush to global 68 | /// # Panics 69 | /// 70 | /// Panics in debug build if the value is < 0. 71 | #[inline] 72 | pub fn inc_by(&self, v: ::T) { 73 | self.get_root_metric().with(|m| { 74 | let counter = self.get_counter(m); 75 | counter.inc_by(v); 76 | m.may_flush(); 77 | }) 78 | } 79 | 80 | /// Increase the local counter by 1, 81 | /// and try to flush to global. 82 | #[inline] 83 | pub fn inc(&self) { 84 | self.get_root_metric().with(|m| { 85 | let counter = self.get_counter(m); 86 | counter.inc(); 87 | m.may_flush(); 88 | }) 89 | } 90 | 91 | /// Return the local counter value. 92 | #[inline] 93 | pub fn get(&self) -> ::T { 94 | self.get_root_metric().with(|m| { 95 | let counter = self.get_counter(m); 96 | counter.get() 97 | }) 98 | } 99 | 100 | /// Restart the counter, resetting its value back to 0. 101 | #[inline] 102 | pub fn reset(&self) { 103 | self.get_root_metric().with(|m| { 104 | let counter = self.get_counter(m); 105 | counter.reset(); 106 | }) 107 | } 108 | 109 | /// trigger flush of LocalKey 110 | #[inline] 111 | pub fn flush(&self) { 112 | self.get_root_metric().with(|m| m.flush()) 113 | } 114 | } 115 | 116 | /// Auto flush-able local counter 117 | #[derive(Debug)] 118 | pub struct AFLocalHistogram> { 119 | /// Delegator to get thread local metric 120 | delegator: D, 121 | /// Phantomdata marker 122 | _p: std::marker::PhantomData>, 123 | } 124 | 125 | impl> AFLocalHistogram { 126 | /// Construct a new AFLocalHistogram from delegator 127 | pub fn new(delegator: D) -> AFLocalHistogram { 128 | timer::ensure_updater(); 129 | AFLocalHistogram { 130 | delegator, 131 | _p: std::marker::PhantomData, 132 | } 133 | } 134 | } 135 | 136 | impl> AFLocalHistogram { 137 | /// Add a single observation to the [`Histogram`](crate::Histogram). 138 | pub fn observe(&self, v: f64) { 139 | self.delegator.get_root_metric().with(|m| { 140 | let local = self.delegator.get_local(m); 141 | local.observe(v); 142 | m.may_flush(); 143 | }) 144 | } 145 | 146 | /// Observe execution time of a closure, in second. 147 | pub fn observe_closure_duration(&self, f: F) -> T 148 | where 149 | F: FnOnce() -> T, 150 | { 151 | let instant = Instant::now(); 152 | let res = f(); 153 | let elapsed = instant.elapsed_sec(); 154 | self.observe(elapsed); 155 | res 156 | } 157 | 158 | /// Observe execution time of a closure, in second. 159 | #[cfg(feature = "nightly")] 160 | pub fn observe_closure_duration_coarse(&self, f: F) -> T 161 | where 162 | F: FnOnce() -> T, 163 | { 164 | let instant = Instant::now_coarse(); 165 | let res = f(); 166 | let elapsed = instant.elapsed_sec(); 167 | self.observe(elapsed); 168 | res 169 | } 170 | 171 | /// Clear the local metric. 172 | pub fn clear(&self) { 173 | self.delegator 174 | .get_root_metric() 175 | .with(|m| self.delegator.get_local(m).clear()) 176 | } 177 | 178 | /// Flush the local metrics to the [`Histogram`](crate::Histogram) metric. 179 | pub fn flush(&self) { 180 | self.delegator 181 | .get_root_metric() 182 | .with(|m| self.delegator.get_local(m).flush()); 183 | } 184 | 185 | /// Return accumulated sum of local samples. 186 | pub fn get_sample_sum(&self) -> f64 { 187 | self.delegator 188 | .get_root_metric() 189 | .with(|m| self.delegator.get_local(m).get_sample_sum()) 190 | } 191 | 192 | /// Return count of local samples. 193 | pub fn get_sample_count(&self) -> u64 { 194 | self.delegator 195 | .get_root_metric() 196 | .with(|m| self.delegator.get_local(m).get_sample_count()) 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/desc.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Prometheus Authors 2 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 3 | 4 | use std::collections::{BTreeSet, HashMap}; 5 | use std::hash::Hasher; 6 | 7 | use fnv::FnvHasher; 8 | 9 | use crate::errors::{Error, Result}; 10 | use crate::metrics::SEPARATOR_BYTE; 11 | use crate::proto::LabelPair; 12 | 13 | // [a-zA-Z_] 14 | fn matches_charset_without_colon(c: char) -> bool { 15 | c.is_ascii_alphabetic() || c == '_' 16 | } 17 | 18 | // [a-zA-Z_:] 19 | fn matches_charset_with_colon(c: char) -> bool { 20 | matches_charset_without_colon(c) || c == ':' 21 | } 22 | 23 | // Equivalent to regex ^[?][?0-9]*$ where ? denotes char set as validated by charset_validator 24 | fn is_valid_ident bool>(input: &str, mut charset_validator: F) -> bool { 25 | let mut chars = input.chars(); 26 | let zeroth = chars.next(); 27 | zeroth 28 | .and_then(|zeroth| { 29 | if charset_validator(zeroth) { 30 | Some(chars.all(|c| charset_validator(c) || c.is_ascii_digit())) 31 | } else { 32 | None 33 | } 34 | }) 35 | .unwrap_or(false) 36 | } 37 | 38 | // Details of required format are at 39 | // https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels 40 | pub(super) fn is_valid_metric_name(name: &str) -> bool { 41 | is_valid_ident(name, matches_charset_with_colon) 42 | } 43 | 44 | pub(super) fn is_valid_label_name(name: &str) -> bool { 45 | is_valid_ident(name, matches_charset_without_colon) 46 | } 47 | 48 | /// The descriptor used by every Prometheus [`Metric`](crate::core::Metric). It is essentially 49 | /// the immutable meta-data of a metric. The normal metric implementations 50 | /// included in this package manage their [`Desc`] under the hood. 51 | /// 52 | /// Descriptors registered with the same registry have to fulfill certain 53 | /// consistency and uniqueness criteria if they share the same fully-qualified 54 | /// name: They must have the same help string and the same label names (aka label 55 | /// dimensions) in each, constLabels and variableLabels, but they must differ in 56 | /// the values of the constLabels. 57 | /// 58 | /// Descriptors that share the same fully-qualified names and the same label 59 | /// values of their constLabels are considered equal. 60 | #[derive(Clone, Debug)] 61 | pub struct Desc { 62 | /// fq_name has been built from Namespace, Subsystem, and Name. 63 | pub fq_name: String, 64 | /// help provides some helpful information about this metric. 65 | pub help: String, 66 | /// const_label_pairs contains precalculated DTO label pairs based on 67 | /// the constant labels. 68 | pub const_label_pairs: Vec, 69 | /// variable_labels contains names of labels for which the metric 70 | /// maintains variable values. 71 | pub variable_labels: Vec, 72 | /// id is a hash of the values of the ConstLabels and fqName. This 73 | /// must be unique among all registered descriptors and can therefore be 74 | /// used as an identifier of the descriptor. 75 | pub id: u64, 76 | /// dim_hash is a hash of the label names (preset and variable) and the 77 | /// Help string. Each Desc with the same fqName must have the same 78 | /// dimHash. 79 | pub dim_hash: u64, 80 | } 81 | 82 | impl Desc { 83 | /// Initializes a new [`Desc`]. Errors are recorded in the Desc 84 | /// and will be reported on registration time. variableLabels and constLabels can 85 | /// be nil if no such labels should be set. fqName and help must not be empty. 86 | pub fn new( 87 | fq_name: String, 88 | help: String, 89 | variable_labels: Vec, 90 | const_labels: HashMap, 91 | ) -> Result { 92 | let mut desc = Desc { 93 | fq_name: fq_name.clone(), 94 | help, 95 | const_label_pairs: Vec::with_capacity(const_labels.len()), 96 | variable_labels, 97 | id: 0, 98 | dim_hash: 0, 99 | }; 100 | 101 | if desc.help.is_empty() { 102 | return Err(Error::Msg("empty help string".into())); 103 | } 104 | 105 | if !is_valid_metric_name(&desc.fq_name) { 106 | return Err(Error::Msg(format!( 107 | "'{}' is not a valid metric name", 108 | desc.fq_name 109 | ))); 110 | } 111 | 112 | let mut label_values = Vec::with_capacity(const_labels.len() + 1); 113 | label_values.push(fq_name); 114 | 115 | let mut label_names = BTreeSet::new(); 116 | 117 | for label_name in const_labels.keys() { 118 | if !is_valid_label_name(label_name) { 119 | return Err(Error::Msg(format!( 120 | "'{}' is not a valid label name", 121 | &label_name 122 | ))); 123 | } 124 | 125 | if !label_names.insert(label_name.clone()) { 126 | return Err(Error::Msg(format!( 127 | "duplicate const label name {}", 128 | label_name 129 | ))); 130 | } 131 | } 132 | 133 | // ... so that we can now add const label values in the order of their names. 134 | for label_name in &label_names { 135 | label_values.push(const_labels.get(label_name).cloned().unwrap()); 136 | } 137 | 138 | // Now add the variable label names, but prefix them with something that 139 | // cannot be in a regular label name. That prevents matching the label 140 | // dimension with a different mix between preset and variable labels. 141 | for label_name in &desc.variable_labels { 142 | if !is_valid_label_name(label_name) { 143 | return Err(Error::Msg(format!( 144 | "'{}' is not a valid label name", 145 | &label_name 146 | ))); 147 | } 148 | 149 | if !label_names.insert(format!("${}", label_name)) { 150 | return Err(Error::Msg(format!( 151 | "duplicate variable label name {}", 152 | label_name 153 | ))); 154 | } 155 | } 156 | 157 | let mut vh = FnvHasher::default(); 158 | for val in &label_values { 159 | vh.write(val.as_bytes()); 160 | vh.write_u8(SEPARATOR_BYTE); 161 | } 162 | 163 | desc.id = vh.finish(); 164 | 165 | // Now hash together (in this order) the help string and the sorted 166 | // label names. 167 | let mut lh = FnvHasher::default(); 168 | lh.write(desc.help.as_bytes()); 169 | lh.write_u8(SEPARATOR_BYTE); 170 | for label_name in &label_names { 171 | lh.write(label_name.as_bytes()); 172 | lh.write_u8(SEPARATOR_BYTE); 173 | } 174 | desc.dim_hash = lh.finish(); 175 | 176 | for (key, value) in const_labels { 177 | let mut label_pair = LabelPair::default(); 178 | label_pair.set_name(key); 179 | label_pair.set_value(value); 180 | desc.const_label_pairs.push(label_pair); 181 | } 182 | 183 | desc.const_label_pairs.sort(); 184 | 185 | Ok(desc) 186 | } 187 | } 188 | 189 | /// An interface for describing the immutable meta-data of a [`Metric`](crate::core::Metric). 190 | pub trait Describer { 191 | /// `describe` returns a [`Desc`]. 192 | fn describe(&self) -> Result; 193 | } 194 | 195 | #[cfg(test)] 196 | mod tests { 197 | use std::collections::HashMap; 198 | 199 | use crate::desc::{is_valid_label_name, is_valid_metric_name, Desc}; 200 | use crate::errors::Error; 201 | 202 | #[test] 203 | fn test_is_valid_metric_name() { 204 | let tbl = [ 205 | (":", true), 206 | ("_", true), 207 | ("a", true), 208 | (":9", true), 209 | ("_9", true), 210 | ("a9", true), 211 | ("a_b_9_d:x_", true), 212 | ("9", false), 213 | ("9:", false), 214 | ("9_", false), 215 | ("9a", false), 216 | ("a-", false), 217 | ]; 218 | 219 | for &(name, expected) in &tbl { 220 | assert_eq!(is_valid_metric_name(name), expected); 221 | } 222 | } 223 | 224 | #[test] 225 | fn test_is_valid_label_name() { 226 | let tbl = [ 227 | ("_", true), 228 | ("a", true), 229 | ("_9", true), 230 | ("a9", true), 231 | ("a_b_9_dx_", true), 232 | (":", false), 233 | (":9", false), 234 | ("9", false), 235 | ("9:", false), 236 | ("9_", false), 237 | ("9a", false), 238 | ("a-", false), 239 | ("a_b_9_d:x_", false), 240 | ]; 241 | 242 | for &(name, expected) in &tbl { 243 | assert_eq!(is_valid_label_name(name), expected); 244 | } 245 | } 246 | 247 | #[test] 248 | fn test_invalid_const_label_name() { 249 | for &name in &["-dash", "9gag", ":colon", "colon:", "has space"] { 250 | let res = Desc::new( 251 | "name".into(), 252 | "help".into(), 253 | vec![name.into()], 254 | HashMap::new(), 255 | ) 256 | .expect_err(format!("expected error for {}", name).as_ref()); 257 | match res { 258 | Error::Msg(msg) => assert_eq!(msg, format!("'{}' is not a valid label name", name)), 259 | other => panic!("{}", other), 260 | }; 261 | } 262 | } 263 | 264 | #[test] 265 | fn test_invalid_variable_label_name() { 266 | for &name in &["-dash", "9gag", ":colon", "colon:", "has space"] { 267 | let mut labels = HashMap::new(); 268 | labels.insert(name.into(), "value".into()); 269 | let res = Desc::new("name".into(), "help".into(), vec![], labels) 270 | .expect_err(format!("expected error for {}", name).as_ref()); 271 | match res { 272 | Error::Msg(msg) => assert_eq!(msg, format!("'{}' is not a valid label name", name)), 273 | other => panic!("{}", other), 274 | }; 275 | } 276 | } 277 | 278 | #[test] 279 | fn test_invalid_metric_name() { 280 | for &name in &["-dash", "9gag", "has space"] { 281 | let res = Desc::new(name.into(), "help".into(), vec![], HashMap::new()) 282 | .expect_err(format!("expected error for {}", name).as_ref()); 283 | match res { 284 | Error::Msg(msg) => { 285 | assert_eq!(msg, format!("'{}' is not a valid metric name", name)) 286 | } 287 | other => panic!("{}", other), 288 | }; 289 | } 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/encoder/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | #[cfg(feature = "protobuf")] 4 | mod pb; 5 | mod text; 6 | 7 | #[cfg(feature = "protobuf")] 8 | pub use self::pb::{ProtobufEncoder, PROTOBUF_FORMAT}; 9 | pub use self::text::{TextEncoder, TEXT_FORMAT}; 10 | 11 | use std::io::Write; 12 | 13 | use crate::errors::{Error, Result}; 14 | use crate::proto::MetricFamily; 15 | 16 | /// An interface for encoding metric families into an underlying wire protocol. 17 | pub trait Encoder { 18 | /// `encode` converts a slice of MetricFamily proto messages into target 19 | /// format and writes the resulting lines to `writer`. This function does not 20 | /// perform checks on the content of the metrics and label names, 21 | /// i.e. invalid metrics or label names will result in invalid text format 22 | /// output. 23 | fn encode(&self, mfs: &[MetricFamily], writer: &mut W) -> Result<()>; 24 | 25 | /// `format_type` returns target format. 26 | fn format_type(&self) -> &str; 27 | } 28 | 29 | fn check_metric_family(mf: &MetricFamily) -> Result<()> { 30 | if mf.get_metric().is_empty() { 31 | return Err(Error::Msg(format!("MetricFamily has no metrics: {:?}", mf))); 32 | } 33 | if mf.name().is_empty() { 34 | return Err(Error::Msg(format!("MetricFamily has no name: {:?}", mf))); 35 | } 36 | Ok(()) 37 | } 38 | 39 | #[cfg(test)] 40 | mod tests { 41 | use super::*; 42 | use crate::counter::CounterVec; 43 | use crate::encoder::Encoder; 44 | use crate::metrics::Collector; 45 | use crate::metrics::Opts; 46 | 47 | #[test] 48 | #[cfg(feature = "protobuf")] 49 | fn test_bad_proto_metrics() { 50 | let mut writer = Vec::::new(); 51 | let pb_encoder = ProtobufEncoder::new(); 52 | let cv = CounterVec::new( 53 | Opts::new("test_counter_vec", "help information"), 54 | &["labelname"], 55 | ) 56 | .unwrap(); 57 | 58 | // Empty metrics 59 | let mfs = cv.collect(); 60 | check_metric_family(&mfs[0]).unwrap_err(); 61 | pb_encoder.encode(&mfs, &mut writer).unwrap_err(); 62 | assert_eq!(writer.len(), 0); 63 | 64 | // Add a sub metric 65 | cv.with_label_values(&["foo"]).inc(); 66 | let mut mfs = cv.collect(); 67 | 68 | // Empty name 69 | (&mut mfs[0]).clear_name(); 70 | check_metric_family(&mfs[0]).unwrap_err(); 71 | pb_encoder.encode(&mfs, &mut writer).unwrap_err(); 72 | assert_eq!(writer.len(), 0); 73 | } 74 | 75 | #[test] 76 | fn test_bad_text_metrics() { 77 | let mut writer = Vec::::new(); 78 | let text_encoder = TextEncoder::new(); 79 | let cv = CounterVec::new( 80 | Opts::new("test_counter_vec", "help information"), 81 | &["labelname"], 82 | ) 83 | .unwrap(); 84 | 85 | // Empty metrics 86 | let mfs = cv.collect(); 87 | check_metric_family(&mfs[0]).unwrap_err(); 88 | text_encoder.encode(&mfs, &mut writer).unwrap_err(); 89 | assert_eq!(writer.len(), 0); 90 | 91 | // Add a sub metric 92 | cv.with_label_values(&["foo"]).inc(); 93 | let mut mfs = cv.collect(); 94 | 95 | // Empty name 96 | (&mut mfs[0]).clear_name(); 97 | check_metric_family(&mfs[0]).unwrap_err(); 98 | text_encoder.encode(&mfs, &mut writer).unwrap_err(); 99 | assert_eq!(writer.len(), 0); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/encoder/pb.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | use std::io::Write; 4 | 5 | use protobuf::Message; 6 | 7 | use crate::errors::Result; 8 | use crate::proto::MetricFamily; 9 | 10 | use super::{check_metric_family, Encoder}; 11 | 12 | /// The protocol buffer format of metric family. 13 | pub const PROTOBUF_FORMAT: &str = "application/vnd.google.protobuf; \ 14 | proto=io.prometheus.client.MetricFamily; \ 15 | encoding=delimited"; 16 | 17 | /// An implementation of an [`Encoder`] that converts a [`MetricFamily`] proto 18 | /// message into the binary wire format of protobuf. 19 | #[derive(Debug, Default)] 20 | pub struct ProtobufEncoder; 21 | 22 | impl ProtobufEncoder { 23 | /// Create a new protobuf encoder. 24 | pub fn new() -> ProtobufEncoder { 25 | ProtobufEncoder 26 | } 27 | } 28 | 29 | impl Encoder for ProtobufEncoder { 30 | fn encode(&self, metric_families: &[MetricFamily], writer: &mut W) -> Result<()> { 31 | for mf in metric_families { 32 | // Fail-fast checks. 33 | check_metric_family(mf)?; 34 | mf.write_length_delimited_to_writer(writer)?; 35 | } 36 | Ok(()) 37 | } 38 | 39 | fn format_type(&self) -> &str { 40 | PROTOBUF_FORMAT 41 | } 42 | } 43 | 44 | #[cfg(test)] 45 | mod tests { 46 | use crate::counter::CounterVec; 47 | use crate::encoder::Encoder; 48 | use crate::metrics::Opts; 49 | use crate::registry; 50 | 51 | // TODO: add more tests. 52 | #[rustfmt::skip] 53 | #[test] 54 | fn test_protobuf_encoder() { 55 | let cv = CounterVec::new(Opts::new("test_counter_vec", "help information"), 56 | &["labelname"]) 57 | .unwrap(); 58 | let reg = registry::Registry::new(); 59 | reg.register(Box::new(cv.clone())).unwrap(); 60 | 61 | cv.get_metric_with_label_values(&["2230"]).unwrap().inc(); 62 | let mf = reg.gather(); 63 | let mut writer = Vec::::new(); 64 | let encoder = super::ProtobufEncoder::new(); 65 | let res = encoder.encode(&mf, &mut writer); 66 | assert!(res.is_ok()); 67 | 68 | // Generated by a golang demo, 69 | // see more: https://gist.github.com/overvenus/bd39bde014b0cba87c9bde20dbea6ce0 70 | let ans = vec![70, 10, 16, 116, 101, 115, 116, 95, 99, 111, 117, 110, 116, 101, 114, 95, 71 | 118, 101, 99, 18, 16, 104, 101, 108, 112, 32, 105, 110, 102, 111, 114, 109, 72 | 97, 116, 105, 111, 110, 24, 0, 34, 30, 10, 17, 10, 9, 108, 97, 98, 101, 73 | 108, 110, 97, 109, 101, 18, 4, 50, 50, 51, 48, 26, 9, 9, 0, 0, 0, 0, 0, 0, 74 | 240, 63]; 75 | assert_eq!(ans, writer); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | use thiserror::Error; 4 | 5 | /// The error types for prometheus. 6 | #[derive(Debug, Error)] 7 | pub enum Error { 8 | /// A duplicate metric collector has already been registered. 9 | #[error("Duplicate metrics collector registration attempted")] 10 | AlreadyReg, 11 | /// The label cardinality was inconsistent. 12 | #[error("Inconsistent label cardinality, expect {expect} label values, but got {got}")] 13 | InconsistentCardinality { 14 | /// The expected number of labels. 15 | expect: usize, 16 | /// The actual number of labels. 17 | got: usize, 18 | }, 19 | /// An error message which is only a string. 20 | #[error("Error: {0}")] 21 | Msg(String), 22 | /// An error containing a [`std::io::Error`]. 23 | #[error("Io error: {0}")] 24 | Io(#[from] std::io::Error), 25 | /// An error containing a [`protobuf::Error`]. 26 | #[cfg(feature = "protobuf")] 27 | #[error("Protobuf error: {0}")] 28 | Protobuf(#[from] protobuf::Error), 29 | } 30 | 31 | /// A specialized Result type for prometheus. 32 | pub type Result = std::result::Result; 33 | -------------------------------------------------------------------------------- /src/gauge.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Prometheus Authors 2 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 3 | 4 | use std::marker::PhantomData; 5 | use std::sync::Arc; 6 | 7 | use crate::atomic64::{Atomic, AtomicF64, AtomicI64, Number}; 8 | use crate::desc::Desc; 9 | use crate::errors::Result; 10 | use crate::metrics::{Collector, Metric, Opts}; 11 | use crate::proto; 12 | use crate::value::{Value, ValueType}; 13 | use crate::vec::{MetricVec, MetricVecBuilder}; 14 | 15 | /// The underlying implementation for [`Gauge`] and [`IntGauge`]. 16 | #[derive(Debug)] 17 | pub struct GenericGauge { 18 | v: Arc>, 19 | } 20 | 21 | /// A [`Metric`] represents a single numerical value that can arbitrarily go up 22 | /// and down. 23 | pub type Gauge = GenericGauge; 24 | 25 | /// The integer version of [`Gauge`]. Provides better performance if metric values are 26 | /// all integers. 27 | pub type IntGauge = GenericGauge; 28 | 29 | impl Clone for GenericGauge

{ 30 | fn clone(&self) -> Self { 31 | Self { 32 | v: Arc::clone(&self.v), 33 | } 34 | } 35 | } 36 | 37 | impl GenericGauge

{ 38 | /// Create a [`GenericGauge`] with the `name` and `help` arguments. 39 | pub fn new, S2: Into>(name: S1, help: S2) -> Result { 40 | let opts = Opts::new(name, help); 41 | Self::with_opts(opts) 42 | } 43 | 44 | /// Create a [`GenericGauge`] with the `opts` options. 45 | pub fn with_opts(opts: Opts) -> Result { 46 | Self::with_opts_and_label_values::<&str>(&opts, &[]) 47 | } 48 | 49 | fn with_opts_and_label_values>(opts: &Opts, label_values: &[V]) -> Result { 50 | let v = Value::new(opts, ValueType::Gauge, P::T::from_i64(0), label_values)?; 51 | Ok(Self { v: Arc::new(v) }) 52 | } 53 | 54 | /// Set the gauge to an arbitrary value. 55 | #[inline] 56 | pub fn set(&self, v: P::T) { 57 | self.v.set(v); 58 | } 59 | 60 | /// Increase the gauge by 1. 61 | #[inline] 62 | pub fn inc(&self) { 63 | self.v.inc(); 64 | } 65 | 66 | /// Decrease the gauge by 1. 67 | #[inline] 68 | pub fn dec(&self) { 69 | self.v.dec(); 70 | } 71 | 72 | /// Add the given value to the gauge. (The value can be 73 | /// negative, resulting in a decrement of the gauge.) 74 | #[inline] 75 | pub fn add(&self, v: P::T) { 76 | self.v.inc_by(v); 77 | } 78 | 79 | /// Subtract the given value from the gauge. (The value can be 80 | /// negative, resulting in an increment of the gauge.) 81 | #[inline] 82 | pub fn sub(&self, v: P::T) { 83 | self.v.dec_by(v); 84 | } 85 | 86 | /// Return the gauge value. 87 | #[inline] 88 | pub fn get(&self) -> P::T { 89 | self.v.get() 90 | } 91 | } 92 | 93 | impl Collector for GenericGauge

{ 94 | fn desc(&self) -> Vec<&Desc> { 95 | vec![&self.v.desc] 96 | } 97 | 98 | fn collect(&self) -> Vec { 99 | vec![self.v.collect()] 100 | } 101 | } 102 | 103 | impl Metric for GenericGauge

{ 104 | fn metric(&self) -> proto::Metric { 105 | self.v.metric() 106 | } 107 | } 108 | 109 | #[derive(Debug)] 110 | pub struct GaugeVecBuilder { 111 | _phantom: PhantomData

, 112 | } 113 | 114 | impl GaugeVecBuilder

{ 115 | pub fn new() -> Self { 116 | Self { 117 | _phantom: PhantomData, 118 | } 119 | } 120 | } 121 | 122 | impl Clone for GaugeVecBuilder

{ 123 | fn clone(&self) -> Self { 124 | Self::new() 125 | } 126 | } 127 | 128 | impl MetricVecBuilder for GaugeVecBuilder

{ 129 | type M = GenericGauge

; 130 | type P = Opts; 131 | 132 | fn build>(&self, opts: &Opts, vals: &[V]) -> Result { 133 | Self::M::with_opts_and_label_values(opts, vals) 134 | } 135 | } 136 | 137 | /// The underlying implementation for [`GaugeVec`] and [`IntGaugeVec`]. 138 | pub type GenericGaugeVec

= MetricVec>; 139 | 140 | /// A [`Collector`] that bundles a set of [`Gauge`]s that all share 141 | /// the same [`Desc`], but have different values for their variable labels. This is 142 | /// used if you want to count the same thing partitioned by various dimensions 143 | /// (e.g. number of operations queued, partitioned by user and operation type). 144 | pub type GaugeVec = GenericGaugeVec; 145 | 146 | /// The integer version of [`GaugeVec`]. Provides better performance if metric values 147 | /// are all integers. 148 | pub type IntGaugeVec = GenericGaugeVec; 149 | 150 | impl GenericGaugeVec

{ 151 | /// Create a new [`GenericGaugeVec`] based on the provided 152 | /// [`Opts`] and partitioned by the given label names. At least one label name must 153 | /// be provided. 154 | pub fn new(opts: Opts, label_names: &[&str]) -> Result { 155 | let variable_names = label_names.iter().map(|s| (*s).to_owned()).collect(); 156 | let opts = opts.variable_labels(variable_names); 157 | let metric_vec = MetricVec::create(proto::MetricType::GAUGE, GaugeVecBuilder::new(), opts)?; 158 | 159 | Ok(metric_vec as Self) 160 | } 161 | } 162 | 163 | #[cfg(test)] 164 | mod tests { 165 | use std::collections::HashMap; 166 | 167 | use super::*; 168 | use crate::metrics::{Collector, Opts}; 169 | #[cfg(feature = "protobuf")] 170 | use crate::proto_ext::MessageFieldExt; 171 | 172 | #[test] 173 | fn test_gauge() { 174 | let opts = Opts::new("test", "test help") 175 | .const_label("a", "1") 176 | .const_label("b", "2"); 177 | let gauge = Gauge::with_opts(opts).unwrap(); 178 | gauge.inc(); 179 | assert_eq!(gauge.get() as u64, 1); 180 | gauge.add(42.0); 181 | assert_eq!(gauge.get() as u64, 43); 182 | gauge.sub(42.0); 183 | assert_eq!(gauge.get() as u64, 1); 184 | gauge.dec(); 185 | assert_eq!(gauge.get() as u64, 0); 186 | gauge.set(42.0); 187 | assert_eq!(gauge.get() as u64, 42); 188 | 189 | let mut mfs = gauge.collect(); 190 | assert_eq!(mfs.len(), 1); 191 | 192 | let mf = mfs.pop().unwrap(); 193 | let m = mf.get_metric().first().unwrap(); 194 | assert_eq!(m.get_label().len(), 2); 195 | assert_eq!(m.get_gauge().get_value() as u64, 42); 196 | } 197 | 198 | #[test] 199 | fn test_gauge_vec_with_labels() { 200 | let vec = GaugeVec::new( 201 | Opts::new("test_gauge_vec", "test gauge vec help"), 202 | &["l1", "l2"], 203 | ) 204 | .unwrap(); 205 | 206 | let mut labels = HashMap::new(); 207 | labels.insert("l1", "v1"); 208 | labels.insert("l2", "v2"); 209 | assert!(vec.remove(&labels).is_err()); 210 | 211 | vec.with(&labels).inc(); 212 | vec.with(&labels).dec(); 213 | vec.with(&labels).add(42.0); 214 | vec.with(&labels).sub(42.0); 215 | vec.with(&labels).set(42.0); 216 | 217 | assert!(vec.remove(&labels).is_ok()); 218 | assert!(vec.remove(&labels).is_err()); 219 | } 220 | 221 | #[test] 222 | fn test_gauge_vec_with_owned_labels() { 223 | let vec = GaugeVec::new( 224 | Opts::new("test_gauge_vec", "test gauge vec help"), 225 | &["l1", "l2"], 226 | ) 227 | .unwrap(); 228 | 229 | let mut labels = HashMap::new(); 230 | labels.insert("l1", "v1"); 231 | labels.insert("l2", "v2"); 232 | assert!(vec.remove(&labels).is_err()); 233 | 234 | vec.with(&labels).inc(); 235 | vec.with(&labels).dec(); 236 | vec.with(&labels).add(42.0); 237 | vec.with(&labels).sub(42.0); 238 | vec.with(&labels).set(42.0); 239 | 240 | assert!(vec.remove(&labels).is_ok()); 241 | assert!(vec.remove(&labels).is_err()); 242 | } 243 | 244 | #[test] 245 | fn test_gauge_vec_with_label_values() { 246 | let vec = GaugeVec::new( 247 | Opts::new("test_gauge_vec", "test gauge vec help"), 248 | &["l1", "l2"], 249 | ) 250 | .unwrap(); 251 | 252 | assert!(vec.remove_label_values(&["v1", "v2"]).is_err()); 253 | vec.with_label_values(&["v1", "v2"]).inc(); 254 | assert!(vec.remove_label_values(&["v1", "v2"]).is_ok()); 255 | 256 | vec.with_label_values(&["v1", "v2"]).inc(); 257 | vec.with_label_values(&["v1", "v2"]).dec(); 258 | vec.with_label_values(&["v1", "v2"]).add(42.0); 259 | vec.with_label_values(&["v1", "v2"]).sub(42.0); 260 | vec.with_label_values(&["v1", "v2"]).set(42.0); 261 | 262 | assert!(vec.remove_label_values(&["v1"]).is_err()); 263 | assert!(vec.remove_label_values(&["v1", "v3"]).is_err()); 264 | } 265 | 266 | #[test] 267 | fn test_gauge_vec_with_owned_label_values() { 268 | let vec = GaugeVec::new( 269 | Opts::new("test_gauge_vec", "test gauge vec help"), 270 | &["l1", "l2"], 271 | ) 272 | .unwrap(); 273 | 274 | let v1 = "v1".to_string(); 275 | let v2 = "v2".to_string(); 276 | let v3 = "v3".to_string(); 277 | 278 | assert!(vec.remove_label_values(&[v1.clone(), v2.clone()]).is_err()); 279 | vec.with_label_values(&[v1.clone(), v2.clone()]).inc(); 280 | assert!(vec.remove_label_values(&[v1.clone(), v2.clone()]).is_ok()); 281 | 282 | vec.with_label_values(&[v1.clone(), v2.clone()]).inc(); 283 | vec.with_label_values(&[v1.clone(), v2.clone()]).dec(); 284 | vec.with_label_values(&[v1.clone(), v2.clone()]).add(42.0); 285 | vec.with_label_values(&[v1.clone(), v2.clone()]).sub(42.0); 286 | vec.with_label_values(&[v1.clone(), v2.clone()]).set(42.0); 287 | 288 | assert!(vec.remove_label_values(&[v1.clone()]).is_err()); 289 | assert!(vec.remove_label_values(&[v1.clone(), v3.clone()]).is_err()); 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | /*! 4 | The Rust client library for [Prometheus](https://prometheus.io/). 5 | 6 | Use of this library involves a few core concepts: 7 | 8 | * [`Metric`s](core/trait.Metric.html) like [`Counter`s](type.Counter.html) that 9 | represent information about your system. 10 | 11 | * A [`Registry`](struct.Registry.html) that [`Metric`s](core/trait.Metric.html) 12 | are registered with. 13 | 14 | * An endpoint that calls [`gather`](fn.gather.html) which returns 15 | [`MetricFamily`s](proto/struct.MetricFamily.html) through an 16 | [`Encoder`](trait.Encoder.html). 17 | 18 | 19 | # Basic Example 20 | 21 | ```rust 22 | use prometheus::{Opts, Registry, Counter, TextEncoder, Encoder}; 23 | 24 | // Create a Counter. 25 | let counter_opts = Opts::new("test_counter", "test counter help"); 26 | let counter = Counter::with_opts(counter_opts).unwrap(); 27 | 28 | // Create a Registry and register Counter. 29 | let r = Registry::new(); 30 | r.register(Box::new(counter.clone())).unwrap(); 31 | 32 | // Inc. 33 | counter.inc(); 34 | 35 | // Gather the metrics. 36 | let mut buffer = vec![]; 37 | let encoder = TextEncoder::new(); 38 | let metric_families = r.gather(); 39 | encoder.encode(&metric_families, &mut buffer).unwrap(); 40 | 41 | // Output to the standard output. 42 | println!("{}", String::from_utf8(buffer).unwrap()); 43 | ``` 44 | 45 | You can find more examples within 46 | [`/examples`](https://github.com/tikv/rust-prometheus/tree/master/examples). 47 | 48 | 49 | # Static Metrics 50 | 51 | This crate supports staticly built metrics. You can use it with 52 | [`lazy_static`](https://docs.rs/lazy_static/) to quickly build up and collect 53 | some metrics. 54 | 55 | ```rust 56 | use prometheus::{self, IntCounter, TextEncoder, Encoder}; 57 | 58 | use lazy_static::lazy_static; 59 | use prometheus::register_int_counter; 60 | 61 | lazy_static! { 62 | static ref HIGH_FIVE_COUNTER: IntCounter = 63 | register_int_counter!("highfives", "Number of high fives received").unwrap(); 64 | } 65 | 66 | HIGH_FIVE_COUNTER.inc(); 67 | assert_eq!(HIGH_FIVE_COUNTER.get(), 1); 68 | ``` 69 | 70 | By default, this registers with a default registry. To make a report, you can call 71 | [`gather`](fn.gather.html). This will return a family of metrics you can then feed through an 72 | [`Encoder`](trait.Encoder.html) and report to Promethus. 73 | 74 | ``` 75 | # use prometheus::IntCounter; 76 | use prometheus::{self, TextEncoder, Encoder}; 77 | 78 | use lazy_static::lazy_static; 79 | use prometheus::register_int_counter; 80 | 81 | // Register & measure some metrics. 82 | # lazy_static! { 83 | # static ref HIGH_FIVE_COUNTER: IntCounter = 84 | # register_int_counter!("highfives", "Number of high fives received").unwrap(); 85 | # } 86 | # HIGH_FIVE_COUNTER.inc(); 87 | 88 | let mut buffer = Vec::new(); 89 | let encoder = TextEncoder::new(); 90 | 91 | // Gather the metrics. 92 | let metric_families = prometheus::gather(); 93 | // Encode them to send. 94 | encoder.encode(&metric_families, &mut buffer).unwrap(); 95 | 96 | let output = String::from_utf8(buffer.clone()).unwrap(); 97 | const EXPECTED_OUTPUT: &'static str = "# HELP highfives Number of high fives received\n# TYPE highfives counter\nhighfives 1\n"; 98 | assert!(output.starts_with(EXPECTED_OUTPUT)); 99 | ``` 100 | 101 | See [prometheus_static_metric](https://docs.rs/prometheus-static-metric) for 102 | additional functionality. 103 | 104 | 105 | # Features 106 | 107 | This library supports four features: 108 | 109 | * `gen`: To generate protobuf client with the latest protobuf version instead of 110 | using the pre-generated client. 111 | * `nightly`: Enable nightly only features. 112 | * `process`: For collecting process info. 113 | * `push`: Enable push support. 114 | 115 | */ 116 | 117 | #![allow( 118 | clippy::needless_pass_by_value, 119 | clippy::new_without_default, 120 | clippy::new_ret_no_self 121 | )] 122 | #![deny(missing_docs)] 123 | #![deny(missing_debug_implementations)] 124 | 125 | /// Protocol buffers format of metrics. 126 | #[cfg(feature = "protobuf")] 127 | #[allow(warnings)] 128 | #[rustfmt::skip] 129 | #[path = "../proto/proto_model.rs"] 130 | pub mod proto; 131 | 132 | #[cfg(not(feature = "protobuf"))] 133 | #[path = "plain_model.rs"] 134 | pub mod proto; 135 | 136 | #[cfg(feature = "protobuf")] 137 | mod proto_ext; 138 | 139 | #[macro_use] 140 | mod macros; 141 | mod atomic64; 142 | mod auto_flush; 143 | mod counter; 144 | mod desc; 145 | mod encoder; 146 | mod errors; 147 | mod gauge; 148 | mod histogram; 149 | mod metrics; 150 | mod nohash; 151 | mod pulling_gauge; 152 | #[cfg(feature = "push")] 153 | mod push; 154 | mod registry; 155 | mod value; 156 | mod vec; 157 | 158 | // Public for generated code. 159 | #[doc(hidden)] 160 | pub mod timer; 161 | 162 | #[cfg(all(feature = "process", target_os = "linux"))] 163 | pub mod process_collector; 164 | 165 | pub mod local { 166 | /*! 167 | 168 | Unsync local metrics, provides better performance. 169 | 170 | */ 171 | pub use super::counter::{ 172 | CounterWithValueType, LocalCounter, LocalCounterVec, LocalIntCounter, LocalIntCounterVec, 173 | }; 174 | pub use super::histogram::{LocalHistogram, LocalHistogramTimer, LocalHistogramVec}; 175 | pub use super::metrics::{LocalMetric, MayFlush}; 176 | 177 | pub use super::auto_flush::{ 178 | AFLocalCounter, AFLocalHistogram, CounterDelegator, HistogramDelegator, 179 | }; 180 | } 181 | 182 | pub mod core { 183 | /*! 184 | 185 | Core traits and types. 186 | 187 | */ 188 | 189 | pub use super::atomic64::*; 190 | pub use super::counter::{ 191 | GenericCounter, GenericCounterVec, GenericLocalCounter, GenericLocalCounterVec, 192 | }; 193 | pub use super::desc::{Desc, Describer}; 194 | pub use super::gauge::{GenericGauge, GenericGaugeVec}; 195 | pub use super::metrics::{Collector, Metric, Opts}; 196 | pub use super::vec::{MetricVec, MetricVecBuilder}; 197 | } 198 | 199 | pub use self::counter::{Counter, CounterVec, IntCounter, IntCounterVec}; 200 | pub use self::encoder::Encoder; 201 | #[cfg(feature = "protobuf")] 202 | pub use self::encoder::ProtobufEncoder; 203 | pub use self::encoder::TextEncoder; 204 | #[cfg(feature = "protobuf")] 205 | pub use self::encoder::PROTOBUF_FORMAT; 206 | pub use self::encoder::TEXT_FORMAT; 207 | pub use self::errors::{Error, Result}; 208 | pub use self::gauge::{Gauge, GaugeVec, IntGauge, IntGaugeVec}; 209 | pub use self::histogram::DEFAULT_BUCKETS; 210 | pub use self::histogram::{exponential_buckets, linear_buckets}; 211 | pub use self::histogram::{Histogram, HistogramOpts, HistogramTimer, HistogramVec}; 212 | pub use self::metrics::Opts; 213 | pub use self::pulling_gauge::PullingGauge; 214 | #[cfg(feature = "push")] 215 | pub use self::push::{ 216 | hostname_grouping_key, push_add_collector, push_add_metrics, push_collector, push_metrics, 217 | BasicAuthentication, 218 | }; 219 | pub use self::registry::Registry; 220 | pub use self::registry::{default_registry, gather, register, unregister}; 221 | -------------------------------------------------------------------------------- /src/metrics.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Prometheus Authors 2 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 3 | 4 | use std::cmp::{Eq, Ord, Ordering, PartialOrd}; 5 | use std::collections::HashMap; 6 | 7 | use crate::desc::{Desc, Describer}; 8 | use crate::errors::Result; 9 | use crate::proto::{self, LabelPair}; 10 | use crate::timer; 11 | use std::cell::Cell; 12 | 13 | pub const SEPARATOR_BYTE: u8 = 0xFF; 14 | 15 | /// An interface for collecting metrics. 16 | pub trait Collector: Sync + Send { 17 | /// Return descriptors for metrics. 18 | fn desc(&self) -> Vec<&Desc>; 19 | 20 | /// Collect metrics. 21 | fn collect(&self) -> Vec; 22 | } 23 | 24 | /// An interface models a single sample value with its meta data being exported to Prometheus. 25 | pub trait Metric: Sync + Send + Clone { 26 | /// Return the protocol Metric. 27 | fn metric(&self) -> proto::Metric; 28 | } 29 | 30 | /// An interface models a Metric only usable in single thread environment. 31 | pub trait LocalMetric { 32 | /// Flush the local metrics to the global one. 33 | fn flush(&self); 34 | } 35 | 36 | /// An interface models a LocalMetric with try to flush functions. 37 | /// Not intend to be implemented by user manually, used in macro generated code. 38 | pub trait MayFlush: LocalMetric { 39 | /// If the LocalMetric is already flushed in last `flush_interval_sec` seconds, then do nothing, 40 | /// else flush and update last flush time. 41 | fn try_flush(&self, last_flush: &Cell, flush_interval_millis: u64) { 42 | let now = timer::recent_millis(); 43 | let last_tick = last_flush.get(); 44 | if now < last_tick + flush_interval_millis { 45 | return; 46 | } 47 | self.flush(); 48 | last_flush.set(now); 49 | } 50 | 51 | /// Open to implementation to fill try_flush parameters 52 | fn may_flush(&self); 53 | } 54 | 55 | /// A struct that bundles the options for creating most [`Metric`] types. 56 | #[derive(Debug, Clone)] 57 | pub struct Opts { 58 | /// namespace, subsystem, and name are components of the fully-qualified 59 | /// name of the [`Metric`] (created by joining these components with 60 | /// "_"). Only Name is mandatory, the others merely help structuring the 61 | /// name. Note that the fully-qualified name of the metric must be a 62 | /// valid Prometheus metric name. 63 | pub namespace: String, 64 | /// namespace, subsystem, and name are components of the fully-qualified 65 | /// name of the [`Metric`] (created by joining these components with 66 | /// "_"). Only Name is mandatory, the others merely help structuring the 67 | /// name. Note that the fully-qualified name of the metric must be a 68 | /// valid Prometheus metric name. 69 | pub subsystem: String, 70 | /// namespace, subsystem, and name are components of the fully-qualified 71 | /// name of the [`Metric`] (created by joining these components with 72 | /// "_"). Only Name is mandatory, the others merely help structuring the 73 | /// name. Note that the fully-qualified name of the metric must be a 74 | /// valid Prometheus metric name. 75 | pub name: String, 76 | 77 | /// help provides information about this metric. Mandatory! 78 | /// 79 | /// Metrics with the same fully-qualified name must have the same Help 80 | /// string. 81 | pub help: String, 82 | 83 | /// const_labels are used to attach fixed labels to this metric. Metrics 84 | /// with the same fully-qualified name must have the same label names in 85 | /// their ConstLabels. 86 | /// 87 | /// Note that in most cases, labels have a value that varies during the 88 | /// lifetime of a process. Those labels are usually managed with a metric 89 | /// vector collector (like CounterVec, GaugeVec). ConstLabels 90 | /// serve only special purposes. One is for the special case where the 91 | /// value of a label does not change during the lifetime of a process, 92 | /// e.g. if the revision of the running binary is put into a 93 | /// label. Another, more advanced purpose is if more than one [`Collector`] 94 | /// needs to collect Metrics with the same fully-qualified name. In that 95 | /// case, those Metrics must differ in the values of their 96 | /// ConstLabels. See the [`Collector`] examples. 97 | /// 98 | /// If the value of a label never changes (not even between binaries), 99 | /// that label most likely should not be a label at all (but part of the 100 | /// metric name). 101 | pub const_labels: HashMap, 102 | 103 | /// variable_labels contains names of labels for which the metric maintains 104 | /// variable values. Metrics with the same fully-qualified name must have 105 | /// the same label names in their variable_labels. 106 | /// 107 | /// Note that variable_labels is used in `MetricVec`. To create a single 108 | /// metric must leave it empty. 109 | pub variable_labels: Vec, 110 | } 111 | 112 | impl Opts { 113 | /// `new` creates the Opts with the `name` and `help` arguments. 114 | pub fn new, S2: Into>(name: S1, help: S2) -> Opts { 115 | Opts { 116 | namespace: "".to_owned(), 117 | subsystem: "".to_owned(), 118 | name: name.into(), 119 | help: help.into(), 120 | const_labels: HashMap::new(), 121 | variable_labels: Vec::new(), 122 | } 123 | } 124 | 125 | /// `namespace` sets the namespace. 126 | pub fn namespace>(mut self, namespace: S) -> Self { 127 | self.namespace = namespace.into(); 128 | self 129 | } 130 | 131 | /// `subsystem` sets the sub system. 132 | pub fn subsystem>(mut self, subsystem: S) -> Self { 133 | self.subsystem = subsystem.into(); 134 | self 135 | } 136 | 137 | /// `const_labels` sets the const labels. 138 | pub fn const_labels(mut self, const_labels: HashMap) -> Self { 139 | self.const_labels = const_labels; 140 | self 141 | } 142 | 143 | /// `const_label` adds a const label. 144 | pub fn const_label, S2: Into>(mut self, name: S1, value: S2) -> Self { 145 | self.const_labels.insert(name.into(), value.into()); 146 | self 147 | } 148 | 149 | /// `variable_labels` sets the variable labels. 150 | pub fn variable_labels(mut self, variable_labels: Vec) -> Self { 151 | self.variable_labels = variable_labels; 152 | self 153 | } 154 | 155 | /// `variable_label` adds a variable label. 156 | pub fn variable_label>(mut self, name: S) -> Self { 157 | self.variable_labels.push(name.into()); 158 | self 159 | } 160 | 161 | /// `fq_name` returns the fq_name. 162 | pub fn fq_name(&self) -> String { 163 | build_fq_name(&self.namespace, &self.subsystem, &self.name) 164 | } 165 | } 166 | 167 | impl Describer for Opts { 168 | fn describe(&self) -> Result { 169 | Desc::new( 170 | self.fq_name(), 171 | self.help.clone(), 172 | self.variable_labels.clone(), 173 | self.const_labels.clone(), 174 | ) 175 | } 176 | } 177 | 178 | impl Ord for LabelPair { 179 | fn cmp(&self, other: &LabelPair) -> Ordering { 180 | self.name().cmp(other.name()) 181 | } 182 | } 183 | 184 | impl Eq for LabelPair {} 185 | 186 | impl PartialOrd for LabelPair { 187 | fn partial_cmp(&self, other: &LabelPair) -> Option { 188 | Some(self.cmp(other)) 189 | } 190 | } 191 | 192 | /// `build_fq_name` joins the given three name components by "_". Empty name 193 | /// components are ignored. If the name parameter itself is empty, an empty 194 | /// string is returned, no matter what. [`Metric`] implementations included in this 195 | /// library use this function internally to generate the fully-qualified metric 196 | /// name from the name component in their Opts. Users of the library will only 197 | /// need this function if they implement their own [`Metric`] or instantiate a Desc 198 | /// directly. 199 | fn build_fq_name(namespace: &str, subsystem: &str, name: &str) -> String { 200 | if name.is_empty() { 201 | return "".to_owned(); 202 | } 203 | 204 | if !namespace.is_empty() && !subsystem.is_empty() { 205 | return format!("{}_{}_{}", namespace, subsystem, name); 206 | } else if !namespace.is_empty() { 207 | return format!("{}_{}", namespace, name); 208 | } else if !subsystem.is_empty() { 209 | return format!("{}_{}", subsystem, name); 210 | } 211 | 212 | name.to_owned() 213 | } 214 | 215 | #[cfg(test)] 216 | mod tests { 217 | use std::cmp::{Ord, Ordering}; 218 | 219 | use super::*; 220 | use crate::proto::LabelPair; 221 | 222 | fn new_label_pair(name: &str, value: &str) -> LabelPair { 223 | let mut l = LabelPair::default(); 224 | l.set_name(name.to_owned()); 225 | l.set_value(value.to_owned()); 226 | l 227 | } 228 | 229 | #[test] 230 | fn test_label_cmp() { 231 | let tbl = vec![ 232 | ("k1", "k2", Ordering::Less), 233 | ("k1", "k1", Ordering::Equal), 234 | ("k1", "k0", Ordering::Greater), 235 | ]; 236 | 237 | for (l1, l2, order) in tbl { 238 | let lhs = new_label_pair(l1, l1); 239 | let rhs = new_label_pair(l2, l2); 240 | assert_eq!(lhs.cmp(&rhs), order); 241 | } 242 | } 243 | 244 | #[test] 245 | fn test_build_fq_name() { 246 | let tbl = vec![ 247 | ("a", "b", "c", "a_b_c"), 248 | ("", "b", "c", "b_c"), 249 | ("a", "", "c", "a_c"), 250 | ("", "", "c", "c"), 251 | ("a", "b", "", ""), 252 | ("a", "", "", ""), 253 | ("", "b", "", ""), 254 | (" ", "", "", ""), 255 | ]; 256 | 257 | for (namespace, subsystem, name, res) in tbl { 258 | assert_eq!(&build_fq_name(namespace, subsystem, name), res); 259 | } 260 | } 261 | 262 | #[test] 263 | fn test_different_generic_types() { 264 | Opts::new(format!("{}_{}", "string", "label"), "&str_label"); 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /src/nohash.rs: -------------------------------------------------------------------------------- 1 | use std::hash::{BuildHasherDefault, Hasher}; 2 | 3 | /// Inspired by nohash-hasher, but we avoid the crate dependency because it's in public archive. 4 | #[derive(Copy, Clone, Debug, Default)] 5 | pub(crate) struct NoHashHasher(u64); 6 | 7 | pub(crate) type BuildNoHashHasher = BuildHasherDefault; 8 | 9 | impl Hasher for NoHashHasher { 10 | #[inline] 11 | fn finish(&self) -> u64 { 12 | self.0 13 | } 14 | 15 | fn write(&mut self, _bytes: &[u8]) { 16 | panic!("Invalid use of NoHashHasher"); 17 | } 18 | 19 | #[inline] 20 | fn write_u64(&mut self, i: u64) { 21 | self.0 = i; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/process_collector.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | //! Monitor a process. 4 | //! 5 | //! This module only supports **Linux** platform. 6 | 7 | use lazy_static::lazy_static; 8 | 9 | use crate::counter::IntCounter; 10 | use crate::desc::Desc; 11 | use crate::gauge::IntGauge; 12 | use crate::metrics::{Collector, Opts}; 13 | use crate::proto; 14 | 15 | /// The `pid_t` data type represents process IDs. 16 | pub use libc::pid_t; 17 | 18 | /// Seven metrics per ProcessCollector. 19 | const METRICS_NUMBER: usize = 7; 20 | 21 | /// A collector which exports the current state of process metrics including 22 | /// CPU, memory and file descriptor usage, thread count, as well as the process 23 | /// start time for the given process id. 24 | #[derive(Debug)] 25 | pub struct ProcessCollector { 26 | pid: pid_t, 27 | descs: Vec, 28 | cpu_total: IntCounter, 29 | open_fds: IntGauge, 30 | max_fds: IntGauge, 31 | vsize: IntGauge, 32 | rss: IntGauge, 33 | start_time: IntGauge, 34 | threads: IntGauge, 35 | } 36 | 37 | impl ProcessCollector { 38 | /// Create a `ProcessCollector` with the given process id and namespace. 39 | pub fn new>(pid: pid_t, namespace: S) -> ProcessCollector { 40 | let namespace = namespace.into(); 41 | let mut descs = Vec::new(); 42 | 43 | let cpu_total = IntCounter::with_opts( 44 | Opts::new( 45 | "process_cpu_seconds_total", 46 | "Total user and system CPU time spent in \ 47 | seconds.", 48 | ) 49 | .namespace(namespace.clone()), 50 | ) 51 | .unwrap(); 52 | descs.extend(cpu_total.desc().into_iter().cloned()); 53 | 54 | let open_fds = IntGauge::with_opts( 55 | Opts::new("process_open_fds", "Number of open file descriptors.") 56 | .namespace(namespace.clone()), 57 | ) 58 | .unwrap(); 59 | descs.extend(open_fds.desc().into_iter().cloned()); 60 | 61 | let max_fds = IntGauge::with_opts( 62 | Opts::new( 63 | "process_max_fds", 64 | "Maximum number of open file descriptors.", 65 | ) 66 | .namespace(namespace.clone()), 67 | ) 68 | .unwrap(); 69 | descs.extend(max_fds.desc().into_iter().cloned()); 70 | 71 | let vsize = IntGauge::with_opts( 72 | Opts::new( 73 | "process_virtual_memory_bytes", 74 | "Virtual memory size in bytes.", 75 | ) 76 | .namespace(namespace.clone()), 77 | ) 78 | .unwrap(); 79 | descs.extend(vsize.desc().into_iter().cloned()); 80 | 81 | let rss = IntGauge::with_opts( 82 | Opts::new( 83 | "process_resident_memory_bytes", 84 | "Resident memory size in bytes.", 85 | ) 86 | .namespace(namespace.clone()), 87 | ) 88 | .unwrap(); 89 | descs.extend(rss.desc().into_iter().cloned()); 90 | 91 | let start_time = IntGauge::with_opts( 92 | Opts::new( 93 | "process_start_time_seconds", 94 | "Start time of the process since unix epoch \ 95 | in seconds.", 96 | ) 97 | .namespace(namespace.clone()), 98 | ) 99 | .unwrap(); 100 | // proc_start_time init once because it is immutable 101 | if let Ok(boot_time) = procfs::boot_time_secs() { 102 | if let Ok(stat) = procfs::process::Process::myself().and_then(|p| p.stat()) { 103 | start_time.set(stat.starttime as i64 / *CLK_TCK + boot_time as i64); 104 | } 105 | } 106 | descs.extend(start_time.desc().into_iter().cloned()); 107 | 108 | let threads = IntGauge::with_opts( 109 | Opts::new("process_threads", "Number of OS threads in the process.") 110 | .namespace(namespace), 111 | ) 112 | .unwrap(); 113 | descs.extend(threads.desc().into_iter().cloned()); 114 | 115 | ProcessCollector { 116 | pid, 117 | descs, 118 | cpu_total, 119 | open_fds, 120 | max_fds, 121 | vsize, 122 | rss, 123 | start_time, 124 | threads, 125 | } 126 | } 127 | 128 | /// Return a `ProcessCollector` of the calling process. 129 | pub fn for_self() -> ProcessCollector { 130 | let pid = unsafe { libc::getpid() }; 131 | ProcessCollector::new(pid, "") 132 | } 133 | } 134 | 135 | impl Collector for ProcessCollector { 136 | fn desc(&self) -> Vec<&Desc> { 137 | self.descs.iter().collect() 138 | } 139 | 140 | fn collect(&self) -> Vec { 141 | let p = match procfs::process::Process::new(self.pid) { 142 | Ok(p) => p, 143 | Err(..) => { 144 | // we can't construct a Process object, so there's no stats to gather 145 | return Vec::new(); 146 | } 147 | }; 148 | 149 | // file descriptors 150 | if let Ok(fd_count) = p.fd_count() { 151 | self.open_fds.set(fd_count as i64); 152 | } 153 | if let Ok(limits) = p.limits() { 154 | if let procfs::process::LimitValue::Value(max) = limits.max_open_files.soft_limit { 155 | self.max_fds.set(max as i64) 156 | } 157 | } 158 | 159 | let mut cpu_total_mfs = None; 160 | if let Ok(stat) = p.stat() { 161 | // memory 162 | self.vsize.set(stat.vsize as i64); 163 | self.rss.set((stat.rss as i64) * *PAGESIZE); 164 | 165 | // cpu 166 | let total = (stat.utime + stat.stime) / *CLK_TCK as u64; 167 | let past = self.cpu_total.get(); 168 | // If two threads are collecting metrics at the same time, 169 | // the cpu_total counter may have already been updated, 170 | // and the subtraction may underflow. 171 | self.cpu_total.inc_by(total.saturating_sub(past)); 172 | cpu_total_mfs = Some(self.cpu_total.collect()); 173 | 174 | // threads 175 | self.threads.set(stat.num_threads); 176 | } 177 | 178 | // collect MetricFamilys. 179 | let mut mfs = Vec::with_capacity(METRICS_NUMBER); 180 | if let Some(cpu) = cpu_total_mfs { 181 | mfs.extend(cpu); 182 | } 183 | mfs.extend(self.open_fds.collect()); 184 | mfs.extend(self.max_fds.collect()); 185 | mfs.extend(self.vsize.collect()); 186 | mfs.extend(self.rss.collect()); 187 | mfs.extend(self.start_time.collect()); 188 | mfs.extend(self.threads.collect()); 189 | mfs 190 | } 191 | } 192 | 193 | lazy_static! { 194 | // getconf CLK_TCK 195 | static ref CLK_TCK: i64 = { 196 | unsafe { 197 | libc::sysconf(libc::_SC_CLK_TCK) 198 | }.into() 199 | }; 200 | 201 | // getconf PAGESIZE 202 | static ref PAGESIZE: i64 = { 203 | unsafe { 204 | libc::sysconf(libc::_SC_PAGESIZE) 205 | }.into() 206 | }; 207 | } 208 | 209 | #[cfg(test)] 210 | mod tests { 211 | use super::*; 212 | use crate::metrics::Collector; 213 | use crate::registry; 214 | 215 | #[test] 216 | fn test_process_collector() { 217 | let pc = ProcessCollector::for_self(); 218 | { 219 | // Seven metrics per process collector. 220 | let descs = pc.desc(); 221 | assert_eq!(descs.len(), super::METRICS_NUMBER); 222 | let mfs = pc.collect(); 223 | assert_eq!(mfs.len(), super::METRICS_NUMBER); 224 | } 225 | 226 | let r = registry::Registry::new(); 227 | let res = r.register(Box::new(pc)); 228 | assert!(res.is_ok()); 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/proto_ext.rs: -------------------------------------------------------------------------------- 1 | use protobuf::{EnumOrUnknown, MessageField}; 2 | 3 | use crate::proto::{ 4 | Bucket, Counter, Gauge, Histogram, LabelPair, Metric, MetricFamily, MetricType, Quantile, 5 | Summary, 6 | }; 7 | 8 | impl Metric { 9 | /// Creates a new metric with the specified label pairs. 10 | pub fn from_label(label: Vec) -> Self { 11 | Metric { 12 | label, 13 | ..Default::default() 14 | } 15 | } 16 | 17 | /// Creates a new metric with the specified gauge value. 18 | pub fn from_gauge(gauge: Gauge) -> Self { 19 | Metric { 20 | gauge: gauge.into(), 21 | ..Default::default() 22 | } 23 | } 24 | 25 | #[deprecated(since = "0.14.0", note = "Please use `.timestamp_ms()` instead")] 26 | /// Returns the timestamp of this metric. 27 | pub fn get_timestamp_ms(&self) -> i64 { 28 | self.timestamp_ms() 29 | } 30 | 31 | /// Returns the summary of this metric. 32 | pub fn get_summary(&self) -> &MessageField

{ 33 | &self.summary 34 | } 35 | 36 | /// Sets the summary of this metric to the specified summary. 37 | pub fn set_summary(&mut self, summary: Summary) { 38 | self.summary = summary.into(); 39 | } 40 | 41 | /// Returns the value of the counter for this metric. 42 | pub fn get_counter(&self) -> &MessageField { 43 | &self.counter 44 | } 45 | 46 | /// Sets the counter of this metric to the specified counter. 47 | pub fn set_counter(&mut self, counter: Counter) { 48 | self.counter = counter.into(); 49 | } 50 | 51 | /// Returns all label pairs associated with this metric. 52 | pub fn get_label(&self) -> &[LabelPair] { 53 | &self.label 54 | } 55 | 56 | /// Sets the label pairs associated with this metric. 57 | pub fn set_label(&mut self, label: Vec) { 58 | self.label = label; 59 | } 60 | 61 | /// Returns all label pairs associated with ownership. 62 | pub fn take_label(&mut self) -> Vec { 63 | std::mem::take(&mut self.label) 64 | } 65 | 66 | /// Returns the gauge of this metric. 67 | pub fn get_gauge(&self) -> &MessageField { 68 | &self.gauge 69 | } 70 | 71 | /// Sets the gauge of this metric to the specified gauge. 72 | pub fn set_gauge(&mut self, gauge: Gauge) { 73 | self.gauge = gauge.into(); 74 | } 75 | 76 | /// Returns the histogram of this metric. 77 | pub fn get_histogram(&self) -> &MessageField { 78 | &self.histogram 79 | } 80 | 81 | /// Sets the histogram of this metric to the specified histogram. 82 | pub fn set_histogram(&mut self, histogram: Histogram) { 83 | self.histogram = histogram.into(); 84 | } 85 | } 86 | 87 | impl MetricFamily { 88 | #[deprecated(since = "0.14.0", note = "Please use `.name()` instead")] 89 | /// Returns the name of this metric family. 90 | pub fn get_name(&self) -> &str { 91 | self.name() 92 | } 93 | 94 | #[deprecated(since = "0.14.0", note = "Please use `.help()` instead")] 95 | /// Returns the help text of this metric family. 96 | pub fn get_help(&self) -> &str { 97 | self.help() 98 | } 99 | 100 | /// Sets the metric for this metric family (replaces any existing metrics). 101 | pub fn set_metric(&mut self, metric: Vec) { 102 | self.metric = metric; 103 | } 104 | 105 | /// Returns the type of this metric family. 106 | pub fn get_field_type(&self) -> MetricType { 107 | self.type_() 108 | } 109 | 110 | /// Sets the type of this metric family. 111 | pub fn set_field_type(&mut self, t: MetricType) { 112 | self.type_ = t.into(); 113 | } 114 | 115 | /// Returns all metrics in this metric family. 116 | pub fn get_metric(&self) -> &[Metric] { 117 | &self.metric 118 | } 119 | 120 | /// Returns all metrics in this metric family mutably. 121 | pub fn mut_metric(&mut self) -> &mut Vec { 122 | &mut self.metric 123 | } 124 | 125 | /// Returns all metrics in this metric family with taking ownership. 126 | pub fn take_metric(&mut self) -> Vec { 127 | std::mem::take(&mut self.metric) 128 | } 129 | } 130 | 131 | impl Summary { 132 | /// Sets the quantiles for this summary. 133 | pub fn set_quantile(&mut self, quantiles: Vec) { 134 | self.quantile = quantiles; 135 | } 136 | 137 | /// Returns the quantiles of this summary. 138 | pub fn get_quantile(&self) -> &[Quantile] { 139 | &self.quantile 140 | } 141 | 142 | #[deprecated(since = "0.14.0", note = "Please use `.sample_count()` instead")] 143 | /// Returns the sample count of this summary. 144 | pub fn get_sample_count(&self) -> u64 { 145 | self.sample_count() 146 | } 147 | 148 | #[deprecated(since = "0.14.0", note = "Please use `.sample_sum()` instead")] 149 | /// Returns the sample sum of this summary. 150 | pub fn get_sample_sum(&self) -> f64 { 151 | self.sample_sum() 152 | } 153 | } 154 | 155 | impl Quantile { 156 | #[deprecated(since = "0.14.0", note = "Please use `.quantile()` instead")] 157 | /// Returns the quantile of this quantile. 158 | pub fn get_quantile(&self) -> f64 { 159 | self.quantile() 160 | } 161 | 162 | #[deprecated(since = "0.14.0", note = "Please use `.value()` instead")] 163 | /// Returns the value of this quantile. 164 | pub fn get_value(&self) -> f64 { 165 | self.value() 166 | } 167 | } 168 | 169 | pub trait MessageFieldExt { 170 | /// Returns the value of the wrapped gauge. 171 | #[allow(dead_code)] 172 | fn get_value(&self) -> f64; 173 | } 174 | 175 | impl MessageFieldExt for MessageField { 176 | fn get_value(&self) -> f64 { 177 | self.value() 178 | } 179 | } 180 | 181 | impl MessageFieldExt for MessageField { 182 | fn get_value(&self) -> f64 { 183 | self.value() 184 | } 185 | } 186 | 187 | impl Histogram { 188 | /// Returns the sample count of this histogram. 189 | pub fn get_sample_count(&self) -> u64 { 190 | self.sample_count.unwrap_or_default() 191 | } 192 | 193 | /// Returns the sample sum of this histogram. 194 | pub fn get_sample_sum(&self) -> f64 { 195 | self.sample_sum.unwrap_or_default() 196 | } 197 | 198 | /// Returns all buckets in this histogram. 199 | pub fn get_bucket(&self) -> &[Bucket] { 200 | &self.bucket 201 | } 202 | 203 | /// Sets the buckets of this histogram. 204 | pub fn set_bucket(&mut self, bucket: Vec) { 205 | self.bucket = bucket; 206 | } 207 | } 208 | 209 | impl Bucket { 210 | #[deprecated(since = "0.14.0", note = "Please use `.cumulative_count()` instead")] 211 | /// Returns the cumulative count of this bucket. 212 | pub fn get_cumulative_count(&self) -> u64 { 213 | self.cumulative_count() 214 | } 215 | 216 | #[deprecated(since = "0.14.0", note = "Please use `.upper_bound()` instead")] 217 | /// Returns the upper bound of this bucket. 218 | pub fn get_upper_bound(&self) -> f64 { 219 | self.upper_bound() 220 | } 221 | } 222 | 223 | impl LabelPair { 224 | #[deprecated(since = "0.14.0", note = "Please use `.value()` instead")] 225 | /// Returns the value of this label pair. 226 | pub fn get_value(&self) -> &str { 227 | self.value() 228 | } 229 | 230 | #[deprecated(since = "0.14.0", note = "Please use `.name()` instead")] 231 | /// Returns the name of this label pair. 232 | pub fn get_name(&self) -> &str { 233 | self.name() 234 | } 235 | } 236 | 237 | impl From for MessageField { 238 | fn from(value: Counter) -> Self { 239 | MessageField::some(value) 240 | } 241 | } 242 | 243 | impl From for MessageField { 244 | fn from(value: Gauge) -> Self { 245 | MessageField::some(value) 246 | } 247 | } 248 | 249 | impl From for MessageField { 250 | fn from(value: Histogram) -> Self { 251 | MessageField::some(value) 252 | } 253 | } 254 | 255 | impl From for MessageField { 256 | fn from(value: Summary) -> Self { 257 | MessageField::some(value) 258 | } 259 | } 260 | 261 | impl From for Option> { 262 | fn from(value: MetricType) -> Self { 263 | Some(EnumOrUnknown::from(value)) 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/pulling_gauge.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, fmt, sync::Arc}; 2 | 3 | use crate::{ 4 | core::Collector, 5 | proto::{Gauge, Metric, MetricFamily, MetricType}, 6 | }; 7 | 8 | /// A [Gauge] that returns the value from a provided function on every collect run. 9 | /// 10 | /// This metric is the equivalant of Go's 11 | /// 12 | /// 13 | /// # Examples 14 | /// ``` 15 | /// # use prometheus::{Registry, PullingGauge}; 16 | /// # // We are stubbing out std::thread::available_parallelism since it's not available in the 17 | /// # // oldest Rust version that we support. 18 | /// # fn available_parallelism() -> f64 { 0.0 } 19 | /// 20 | /// let registry = Registry::new(); 21 | /// let gauge = PullingGauge::new( 22 | /// "available_parallelism", 23 | /// "The available parallelism, usually the numbers of logical cores.", 24 | /// Box::new(|| available_parallelism()) 25 | /// ).unwrap(); 26 | /// registry.register(Box::new(gauge)); 27 | /// ``` 28 | #[derive(Clone)] 29 | pub struct PullingGauge { 30 | desc: crate::core::Desc, 31 | value: Arc f64 + Send + Sync>>, 32 | } 33 | 34 | impl fmt::Debug for PullingGauge { 35 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 36 | f.debug_struct("PullingGauge") 37 | .field("desc", &self.desc) 38 | .field("value", &"") 39 | .finish() 40 | } 41 | } 42 | 43 | impl PullingGauge { 44 | /// Create a new [`PullingGauge`]. 45 | pub fn new, S2: Into>( 46 | name: S1, 47 | help: S2, 48 | value: Box f64 + Send + Sync>, 49 | ) -> crate::Result { 50 | Ok(PullingGauge { 51 | value: Arc::new(value), 52 | desc: crate::core::Desc::new(name.into(), help.into(), Vec::new(), HashMap::new())?, 53 | }) 54 | } 55 | 56 | fn metric(&self) -> Metric { 57 | let mut gauge = Gauge::default(); 58 | let getter = &self.value; 59 | gauge.set_value(getter()); 60 | 61 | Metric::from_gauge(gauge) 62 | } 63 | } 64 | 65 | impl Collector for PullingGauge { 66 | fn desc(&self) -> Vec<&crate::core::Desc> { 67 | vec![&self.desc] 68 | } 69 | 70 | fn collect(&self) -> Vec { 71 | let mut m = MetricFamily::default(); 72 | m.set_name(self.desc.fq_name.clone()); 73 | m.set_help(self.desc.help.clone()); 74 | m.set_field_type(MetricType::GAUGE); 75 | m.set_metric(vec![self.metric()]); 76 | vec![m] 77 | } 78 | } 79 | 80 | #[cfg(test)] 81 | mod tests { 82 | use super::*; 83 | use crate::metrics::Collector; 84 | #[cfg(feature = "protobuf")] 85 | use crate::proto_ext::MessageFieldExt; 86 | 87 | #[test] 88 | fn test_pulling_gauge() { 89 | const VALUE: f64 = 10.0; 90 | 91 | let gauge = 92 | PullingGauge::new("test_gauge", "Purely for testing", Box::new(|| VALUE)).unwrap(); 93 | 94 | let metrics = gauge.collect(); 95 | assert_eq!(metrics.len(), 1); 96 | 97 | assert_eq!(VALUE, metrics[0].get_metric()[0].get_gauge().get_value()); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/push.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Prometheus Authors 2 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 3 | 4 | use std::collections::HashMap; 5 | use std::hash::BuildHasher; 6 | use std::str::{self, FromStr}; 7 | use std::time::Duration; 8 | 9 | use reqwest::blocking::Client; 10 | use reqwest::header::CONTENT_TYPE; 11 | use reqwest::{Method, StatusCode, Url}; 12 | 13 | use lazy_static::lazy_static; 14 | 15 | use crate::encoder::{Encoder, ProtobufEncoder}; 16 | use crate::errors::{Error, Result}; 17 | use crate::metrics::Collector; 18 | use crate::proto; 19 | use crate::registry::Registry; 20 | 21 | const REQWEST_TIMEOUT_SEC: Duration = Duration::from_secs(10); 22 | 23 | lazy_static! { 24 | static ref HTTP_CLIENT: Client = Client::builder() 25 | .timeout(REQWEST_TIMEOUT_SEC) 26 | .build() 27 | .unwrap(); 28 | } 29 | 30 | /// `BasicAuthentication` holder for supporting `push` to Pushgateway endpoints 31 | /// using Basic access authentication. 32 | /// Can be passed to any `push_metrics` method. 33 | #[derive(Debug)] 34 | pub struct BasicAuthentication { 35 | /// The Basic Authentication username (possibly empty string). 36 | pub username: String, 37 | /// The Basic Authentication password (possibly empty string). 38 | pub password: String, 39 | } 40 | 41 | /// `push_metrics` pushes all gathered metrics to the Pushgateway specified by 42 | /// url, using the provided job name and the (optional) further grouping labels 43 | /// (the grouping map may be nil). See the Pushgateway documentation for 44 | /// detailed implications of the job and other grouping labels. Neither the job 45 | /// name nor any grouping label value may contain a "/". The metrics pushed must 46 | /// not contain a job label of their own nor any of the grouping labels. 47 | /// 48 | /// You can use just host:port or ip:port as url, in which case 'http://' is 49 | /// added automatically. You can also include the schema in the URL. However, do 50 | /// not include the '/metrics/jobs/...' part. 51 | /// 52 | /// Note that all previously pushed metrics with the same job and other grouping 53 | /// labels will be replaced with the metrics pushed by this call. (It uses HTTP 54 | /// method 'PUT' to push to the Pushgateway.) 55 | pub fn push_metrics( 56 | job: &str, 57 | grouping: HashMap, 58 | url: &str, 59 | mfs: Vec, 60 | basic_auth: Option, 61 | ) -> Result<()> { 62 | push(job, grouping, url, mfs, "PUT", basic_auth) 63 | } 64 | 65 | /// `push_add_metrics` works like `push_metrics`, but only previously pushed 66 | /// metrics with the same name (and the same job and other grouping labels) will 67 | /// be replaced. (It uses HTTP method 'POST' to push to the Pushgateway.) 68 | pub fn push_add_metrics( 69 | job: &str, 70 | grouping: HashMap, 71 | url: &str, 72 | mfs: Vec, 73 | basic_auth: Option, 74 | ) -> Result<()> { 75 | push(job, grouping, url, mfs, "POST", basic_auth) 76 | } 77 | 78 | const LABEL_NAME_JOB: &str = "job"; 79 | 80 | fn push( 81 | job: &str, 82 | grouping: HashMap, 83 | url: &str, 84 | mfs: Vec, 85 | method: &str, 86 | basic_auth: Option, 87 | ) -> Result<()> { 88 | // Suppress clippy warning needless_pass_by_value. 89 | let grouping = grouping; 90 | 91 | let mut push_url = if url.contains("://") { 92 | url.to_owned() 93 | } else { 94 | format!("http://{}", url) 95 | }; 96 | 97 | if push_url.ends_with('/') { 98 | push_url.pop(); 99 | } 100 | 101 | let mut url_components = Vec::new(); 102 | if job.contains('/') { 103 | return Err(Error::Msg(format!("job contains '/': {}", job))); 104 | } 105 | 106 | // TODO: escape job 107 | url_components.push(job.to_owned()); 108 | 109 | for (ln, lv) in &grouping { 110 | // TODO: check label name 111 | if lv.contains('/') { 112 | return Err(Error::Msg(format!( 113 | "value of grouping label {} contains '/': {}", 114 | ln, lv 115 | ))); 116 | } 117 | url_components.push(ln.to_owned()); 118 | url_components.push(lv.to_owned()); 119 | } 120 | 121 | push_url = format!("{}/metrics/job/{}", push_url, url_components.join("/")); 122 | 123 | let encoder = ProtobufEncoder::new(); 124 | let mut buf = Vec::new(); 125 | 126 | for mf in mfs { 127 | // Check for pre-existing grouping labels: 128 | for m in mf.get_metric() { 129 | for lp in m.get_label() { 130 | if lp.get_name() == LABEL_NAME_JOB { 131 | return Err(Error::Msg(format!( 132 | "pushed metric {} already contains a \ 133 | job label", 134 | mf.get_name() 135 | ))); 136 | } 137 | if grouping.contains_key(lp.get_name()) { 138 | return Err(Error::Msg(format!( 139 | "pushed metric {} already contains \ 140 | grouping label {}", 141 | mf.get_name(), 142 | lp.get_name() 143 | ))); 144 | } 145 | } 146 | } 147 | // Ignore error, `no metrics` and `no name`. 148 | let _ = encoder.encode(&[mf], &mut buf); 149 | } 150 | 151 | let mut builder = HTTP_CLIENT 152 | .request( 153 | Method::from_str(method).unwrap(), 154 | Url::from_str(&push_url).unwrap(), 155 | ) 156 | .header(CONTENT_TYPE, encoder.format_type()) 157 | .body(buf); 158 | 159 | if let Some(BasicAuthentication { username, password }) = basic_auth { 160 | builder = builder.basic_auth(username, Some(password)); 161 | } 162 | 163 | let response = builder.send().map_err(|e| Error::Msg(format!("{}", e)))?; 164 | 165 | match response.status() { 166 | StatusCode::ACCEPTED => Ok(()), 167 | StatusCode::OK => Ok(()), 168 | _ => Err(Error::Msg(format!( 169 | "unexpected status code {} while pushing to {}", 170 | response.status(), 171 | push_url 172 | ))), 173 | } 174 | } 175 | 176 | fn push_from_collector( 177 | job: &str, 178 | grouping: HashMap, 179 | url: &str, 180 | collectors: Vec>, 181 | method: &str, 182 | basic_auth: Option, 183 | ) -> Result<()> { 184 | let registry = Registry::new(); 185 | for bc in collectors { 186 | registry.register(bc)?; 187 | } 188 | 189 | let mfs = registry.gather(); 190 | push(job, grouping, url, mfs, method, basic_auth) 191 | } 192 | 193 | /// `push_collector` push metrics collected from the provided collectors. It is 194 | /// a convenient way to push only a few metrics. 195 | pub fn push_collector( 196 | job: &str, 197 | grouping: HashMap, 198 | url: &str, 199 | collectors: Vec>, 200 | basic_auth: Option, 201 | ) -> Result<()> { 202 | push_from_collector(job, grouping, url, collectors, "PUT", basic_auth) 203 | } 204 | 205 | /// `push_add_collector` works like `push_add_metrics`, it collects from the 206 | /// provided collectors. It is a convenient way to push only a few metrics. 207 | pub fn push_add_collector( 208 | job: &str, 209 | grouping: HashMap, 210 | url: &str, 211 | collectors: Vec>, 212 | basic_auth: Option, 213 | ) -> Result<()> { 214 | push_from_collector(job, grouping, url, collectors, "POST", basic_auth) 215 | } 216 | 217 | const DEFAULT_GROUP_LABEL_PAIR: (&str, &str) = ("instance", "unknown"); 218 | 219 | /// `hostname_grouping_key` returns a label map with the only entry 220 | /// {instance=""}. This can be conveniently used as the grouping 221 | /// parameter if metrics should be pushed with the hostname as label. The 222 | /// returned map is created upon each call so that the caller is free to add more 223 | /// labels to the map. 224 | /// 225 | /// Note: This function returns `instance = "unknown"` in Windows. 226 | #[cfg(not(target_os = "windows"))] 227 | pub fn hostname_grouping_key() -> HashMap { 228 | // Host names are limited to 255 bytes. 229 | // ref: http://pubs.opengroup.org/onlinepubs/7908799/xns/gethostname.html 230 | let max_len = 256; 231 | let mut name = vec![0u8; max_len]; 232 | match unsafe { 233 | libc::gethostname( 234 | name.as_mut_ptr() as *mut libc::c_char, 235 | max_len as libc::size_t, 236 | ) 237 | } { 238 | 0 => { 239 | let last_char = name.iter().position(|byte| *byte == 0).unwrap_or(max_len); 240 | labels! { 241 | DEFAULT_GROUP_LABEL_PAIR.0.to_owned() => str::from_utf8(&name[..last_char]) 242 | .unwrap_or(DEFAULT_GROUP_LABEL_PAIR.1).to_owned(), 243 | } 244 | } 245 | _ => { 246 | labels! {DEFAULT_GROUP_LABEL_PAIR.0.to_owned() => DEFAULT_GROUP_LABEL_PAIR.1.to_owned(),} 247 | } 248 | } 249 | } 250 | 251 | #[cfg(target_os = "windows")] 252 | pub fn hostname_grouping_key() -> HashMap { 253 | labels! {DEFAULT_GROUP_LABEL_PAIR.0.to_owned() => DEFAULT_GROUP_LABEL_PAIR.1.to_owned(),} 254 | } 255 | 256 | #[cfg(test)] 257 | mod tests { 258 | use super::*; 259 | use crate::proto; 260 | 261 | #[test] 262 | fn test_hostname_grouping_key() { 263 | let map = hostname_grouping_key(); 264 | assert!(!map.is_empty()); 265 | } 266 | 267 | #[test] 268 | fn test_push_bad_label_name() { 269 | let table = vec![ 270 | // Error message: "pushed metric {} already contains a job label" 271 | (LABEL_NAME_JOB, "job label"), 272 | // Error message: "pushed metric {} already contains grouping label {}" 273 | (DEFAULT_GROUP_LABEL_PAIR.0, "grouping label"), 274 | ]; 275 | 276 | for case in table { 277 | let mut l = proto::LabelPair::new(); 278 | l.set_name(case.0.to_owned()); 279 | let mut m = proto::Metric::new(); 280 | m.set_label(vec![l]); 281 | let mut mf = proto::MetricFamily::new(); 282 | mf.set_metric(vec![m]); 283 | let res = push_metrics("test", hostname_grouping_key(), "mockurl", vec![mf], None); 284 | assert!(format!("{}", res.unwrap_err()).contains(case.1)); 285 | } 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/timer.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; 2 | use std::thread; 3 | use std::time::{Duration, Instant}; 4 | 5 | use lazy_static::lazy_static; 6 | 7 | /// Milliseconds since ANCHOR. 8 | static RECENT: AtomicU64 = AtomicU64::new(0); 9 | lazy_static! { 10 | static ref ANCHOR: Instant = Instant::now(); 11 | } 12 | 13 | /// Convert a duration to millisecond. 14 | #[inline] 15 | pub fn duration_to_millis(dur: Duration) -> u64 { 16 | dur.as_secs() * 1000 + dur.subsec_millis() as u64 17 | } 18 | 19 | /// Returns milliseconds since ANCHOR. 20 | /// 21 | /// ANCHOR is some fixed point in history. 22 | pub fn now_millis() -> u64 { 23 | let res = Instant::now(); 24 | let t = duration_to_millis(res.saturating_duration_since(*ANCHOR)); 25 | let mut recent = RECENT.load(Ordering::Relaxed); 26 | loop { 27 | if recent > t { 28 | return recent; 29 | } 30 | match RECENT.compare_exchange_weak(recent, t, Ordering::Relaxed, Ordering::Relaxed) { 31 | Ok(_) => return t, 32 | Err(r) => recent = r, 33 | } 34 | } 35 | } 36 | 37 | /// Returns recent returned value by `now_millis`. 38 | pub fn recent_millis() -> u64 { 39 | RECENT.load(Ordering::Relaxed) 40 | } 41 | 42 | lazy_static! { 43 | static ref UPDATER_IS_RUNNING: AtomicBool = AtomicBool::new(false); 44 | } 45 | 46 | const CHECK_UPDATE_INTERVAL: Duration = Duration::from_millis(200); 47 | 48 | /// Ensures background updater is running, which will call `now_millis` periodically. 49 | pub fn ensure_updater() { 50 | if UPDATER_IS_RUNNING 51 | .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) 52 | .is_ok() 53 | { 54 | std::thread::Builder::new() 55 | .name("time updater".to_owned()) 56 | .spawn(|| loop { 57 | thread::sleep(CHECK_UPDATE_INTERVAL); 58 | now_millis(); 59 | }) 60 | .unwrap(); 61 | } 62 | } 63 | 64 | #[cfg(test)] 65 | mod tests { 66 | use std::thread; 67 | use std::time::Duration; 68 | 69 | #[test] 70 | fn test_duration_to_millis() { 71 | let cases = vec![(1, 1, 1000), (0, 1_000_000, 1), (3, 103_000_000, 3103)]; 72 | for (secs, nanos, exp) in cases { 73 | let dur = Duration::new(secs, nanos); 74 | assert_eq!(super::duration_to_millis(dur), exp); 75 | } 76 | } 77 | 78 | #[test] 79 | fn test_time_update() { 80 | assert_eq!(super::recent_millis(), 0); 81 | let now = super::now_millis(); 82 | assert_eq!(super::recent_millis(), now); 83 | super::ensure_updater(); 84 | thread::sleep(super::CHECK_UPDATE_INTERVAL * 2); 85 | assert!(super::recent_millis() > now); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/value.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Prometheus Authors 2 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 3 | 4 | use crate::atomic64::{Atomic, Number}; 5 | use crate::desc::{Desc, Describer}; 6 | use crate::errors::{Error, Result}; 7 | use crate::proto::{Counter, Gauge, LabelPair, Metric, MetricFamily, MetricType}; 8 | 9 | /// `ValueType` is an enumeration of metric types that represent a simple value 10 | /// for [`Counter`] and [`Gauge`]. 11 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 12 | pub enum ValueType { 13 | Counter, 14 | Gauge, 15 | } 16 | 17 | impl ValueType { 18 | /// `metric_type` returns the corresponding proto metric type. 19 | pub fn metric_type(self) -> MetricType { 20 | match self { 21 | ValueType::Counter => MetricType::COUNTER, 22 | ValueType::Gauge => MetricType::GAUGE, 23 | } 24 | } 25 | } 26 | 27 | /// A generic metric for [`Counter`] and [`Gauge`]. 28 | /// Its effective type is determined by `ValueType`. This is a low-level 29 | /// building block used by the library to back the implementations of 30 | /// [`Counter`] and [`Gauge`]. 31 | #[derive(Debug)] 32 | pub struct Value { 33 | pub desc: Desc, 34 | pub val: P, 35 | pub val_type: ValueType, 36 | pub label_pairs: Vec, 37 | } 38 | 39 | impl Value

{ 40 | pub fn new>( 41 | describer: &D, 42 | val_type: ValueType, 43 | val: P::T, 44 | label_values: &[V], 45 | ) -> Result { 46 | let desc = describer.describe()?; 47 | let label_pairs = make_label_pairs(&desc, label_values)?; 48 | 49 | Ok(Self { 50 | desc, 51 | val: P::new(val), 52 | val_type, 53 | label_pairs, 54 | }) 55 | } 56 | 57 | #[inline] 58 | pub fn get(&self) -> P::T { 59 | self.val.get() 60 | } 61 | 62 | #[inline] 63 | pub fn set(&self, val: P::T) { 64 | self.val.set(val); 65 | } 66 | 67 | #[inline] 68 | pub fn inc_by(&self, val: P::T) { 69 | self.val.inc_by(val); 70 | } 71 | 72 | #[inline] 73 | pub fn inc(&self) { 74 | self.inc_by(P::T::from_i64(1)); 75 | } 76 | 77 | #[inline] 78 | pub fn dec(&self) { 79 | self.dec_by(P::T::from_i64(1)); 80 | } 81 | 82 | #[inline] 83 | pub fn dec_by(&self, val: P::T) { 84 | self.val.dec_by(val) 85 | } 86 | 87 | pub fn metric(&self) -> Metric { 88 | let mut m = Metric::from_label(self.label_pairs.clone()); 89 | 90 | let val = self.get(); 91 | match self.val_type { 92 | ValueType::Counter => { 93 | let mut counter = Counter::default(); 94 | counter.set_value(val.into_f64()); 95 | m.set_counter(counter); 96 | } 97 | ValueType::Gauge => { 98 | let mut gauge = Gauge::default(); 99 | gauge.set_value(val.into_f64()); 100 | m.set_gauge(gauge); 101 | } 102 | } 103 | 104 | m 105 | } 106 | 107 | pub fn collect(&self) -> MetricFamily { 108 | let mut m = MetricFamily::default(); 109 | m.set_name(self.desc.fq_name.clone()); 110 | m.set_help(self.desc.help.clone()); 111 | m.set_field_type(self.val_type.metric_type()); 112 | m.set_metric(vec![self.metric()]); 113 | m 114 | } 115 | } 116 | 117 | pub fn make_label_pairs>(desc: &Desc, label_values: &[V]) -> Result> { 118 | if desc.variable_labels.len() != label_values.len() { 119 | return Err(Error::InconsistentCardinality { 120 | expect: desc.variable_labels.len(), 121 | got: label_values.len(), 122 | }); 123 | } 124 | 125 | let total_len = desc.variable_labels.len() + desc.const_label_pairs.len(); 126 | if total_len == 0 { 127 | return Ok(vec![]); 128 | } 129 | 130 | if desc.variable_labels.is_empty() { 131 | return Ok(desc.const_label_pairs.clone()); 132 | } 133 | 134 | let mut label_pairs = Vec::with_capacity(total_len); 135 | for (i, n) in desc.variable_labels.iter().enumerate() { 136 | let mut label_pair = LabelPair::default(); 137 | label_pair.set_name(n.clone()); 138 | label_pair.set_value(label_values[i].as_ref().to_owned()); 139 | label_pairs.push(label_pair); 140 | } 141 | 142 | for label_pair in &desc.const_label_pairs { 143 | label_pairs.push(label_pair.clone()); 144 | } 145 | label_pairs.sort(); 146 | Ok(label_pairs) 147 | } 148 | -------------------------------------------------------------------------------- /static-metric/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for prometheus-static-metric 2 | 3 | ## 0.5.1 4 | 5 | - Import `TokenStream` via `proc_macro` instead of `syn` to support `syn` 6 | `>= 1.0.58` (https://github.com/tikv/rust-prometheus/issues/378). 7 | 8 | ## 0.5.0 9 | 10 | - Bug fix: Allow not specifying visibility token (e.g. `pub(crate)`) for metric definition. 11 | 12 | ## 0.4.0 13 | 14 | - Misc: Update dependencies (https://github.com/tikv/rust-prometheus/pull/289, https://github.com/tikv/rust-prometheus/pull/311) 15 | - Add: Local metric trait (https://github.com/tikv/rust-prometheus/pull/297) 16 | - Add: Auto-flushable thread local metrics (https://github.com/tikv/rust-prometheus/pull/304) 17 | - Add: Derive `Eq` and `Hash` for label enums (https://github.com/tikv/rust-prometheus/pull/317) 18 | 19 | ## 0.3.0 20 | 21 | - Misc: Update the dependent Syn version (https://github.com/tikv/rust-prometheus/pull/268) 22 | - Misc: Requires rust-prometheus v0.7+ (https://github.com/tikv/rust-prometheus/pull/252) 23 | 24 | ## 0.2.0 25 | 26 | - Add: Local static metric (https://github.com/tikv/rust-prometheus/pull/245) 27 | 28 | ## 0.1.4 29 | 30 | - Add: Static metric enums support `get_str()` (https://github.com/tikv/rust-prometheus/pull/183) 31 | 32 | ## 0.1.3 33 | 34 | - Change: Static metrics are now type safe (https://github.com/tikv/rust-prometheus/pull/182) 35 | 36 | ## 0.1.2 37 | 38 | - Misc: Update the dependent Protobuf version (https://github.com/tikv/rust-prometheus/pull/181) 39 | 40 | ## 0.1.1 41 | 42 | - Add: `register_static_xxx!` macros (https://github.com/tikv/rust-prometheus/pull/180) 43 | -------------------------------------------------------------------------------- /static-metric/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "prometheus-static-metric" 3 | version = "0.5.1" 4 | license = "Apache-2.0" 5 | authors = ["me@breeswish.org"] 6 | description = "Static metric helper utilities for rust-prometheus." 7 | repository = "https://github.com/tikv/rust-prometheus" 8 | homepage = "https://github.com/tikv/rust-prometheus" 9 | documentation = "https://docs.rs/prometheus-static-metric" 10 | edition = "2018" 11 | 12 | [lib] 13 | proc-macro = true 14 | 15 | [dependencies] 16 | syn = { version = "2.0", features = ["full", "extra-traits"] } 17 | proc-macro2 = "1.0" 18 | quote = "1.0" 19 | lazy_static = "1.4" 20 | 21 | [dev-dependencies] 22 | criterion = "0.5" 23 | prometheus = { path = "../" } 24 | 25 | [features] 26 | default = [] 27 | 28 | [[bench]] 29 | name = "benches" 30 | harness = false 31 | -------------------------------------------------------------------------------- /static-metric/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all build test format bench clean examples 2 | 3 | ENABLE_FEATURES ?= default 4 | 5 | all: format build test examples 6 | 7 | build: 8 | cargo build --features="${ENABLE_FEATURES}" 9 | 10 | test: 11 | cargo test --features="${ENABLE_FEATURES}" -- --nocapture 12 | 13 | dev: format test 14 | 15 | format: 16 | @cargo fmt --all -- --check >/dev/null || cargo fmt --all 17 | 18 | bench: format 19 | RUSTFLAGS="--cfg bench" cargo bench --features="${ENABLE_FEATURES}" -- --nocapture 20 | 21 | clean: 22 | cargo clean 23 | 24 | examples: 25 | cargo build --example advanced 26 | cargo build --example local 27 | cargo build --example make_auto_flush_static_counter 28 | cargo build --example make_auto_flush_static_metric_histogram 29 | cargo build --example metric_enum 30 | cargo build --example register_integration 31 | cargo build --example simple 32 | cargo build --example with_lazy_static 33 | -------------------------------------------------------------------------------- /static-metric/README.md: -------------------------------------------------------------------------------- 1 | # prometheus-static-metric 2 | 3 | [![docs.rs](https://docs.rs/prometheus-static-metric/badge.svg)](https://docs.rs/prometheus-static-metric) 4 | [![crates.io](http://meritbadge.herokuapp.com/prometheus-static-metric)](https://crates.io/crates/prometheus-static-metric) 5 | 6 | Utility macro to build static metrics for the [rust-prometheus](https://github.com/tikv/rust-prometheus) library. 7 | 8 | ## Why? 9 | 10 | `MetricVec` (i.e. `CounterVec`, `GaugeVec` or `HistogramVec`) is slow. However if every possible values for labels are 11 | known, each metric in the `MetricVec` can be cached to avoid the runtime cost. 12 | 13 | For example, the following code can be slow when it is invoked multiple times: 14 | 15 | ```rust 16 | some_counter_vec.with_label_values(&["label_1_foo", "label_2_bar"]).inc(); 17 | ``` 18 | 19 | It is because we are retriving a specific `Counter` according to values each time and to ensure thread-safety there is 20 | a lock inside which makes things worse. 21 | 22 | We can optimize it by caching the counter by label values: 23 | 24 | ```rust 25 | // init before hand 26 | let foo_bar_counter = some_counter.with_label_values(&["label_1_foo", "label_2_bar"]); 27 | 28 | foo_bar_counter.inc(); 29 | ``` 30 | 31 | So far everything seems good. We achieve the same performance as `Counter` for `CounterVec`. But what if there are many 32 | labels and each of them has many values? We need to hand-craft a lot of code in this way. 33 | 34 | That's what this crate solves. This crate provides a macro that helps you do the optimization above without really 35 | introducing a lot of templating code. 36 | 37 | ## Getting Started 38 | 39 | - Add to `Cargo.toml`: 40 | 41 | ```toml 42 | [dependencies] 43 | prometheus-static-metric = "0.4" 44 | ``` 45 | 46 | - Add to `lib.rs`: 47 | 48 | ```rust 49 | use prometheus_static_metric; 50 | ``` 51 | 52 | ## Example 53 | 54 | Use the `make_static_metric!` to define all possible values for each label. Your definition will be expanded to a real 55 | `struct` for easy access while keeping high-performance. 56 | 57 | ```rust 58 | use prometheus_static_metric::make_static_metric; 59 | 60 | make_static_metric! { 61 | pub struct MyStaticCounterVec: Counter { 62 | "method" => { 63 | post, 64 | get, 65 | put, 66 | delete, 67 | }, 68 | "product" => { 69 | foo, 70 | bar, 71 | }, 72 | } 73 | } 74 | 75 | fn main() { 76 | let vec = CounterVec::new(Opts::new("foo", "bar"), &["method", "product"]).unwrap(); 77 | let static_counter_vec = MyStaticCounterVec::from(&vec); 78 | 79 | static_counter_vec.post.foo.inc(); 80 | static_counter_vec.delete.bar.inc_by(4.0); 81 | assert_eq!(static_counter_vec.post.bar.get(), 0.0); 82 | assert_eq!(vec.with_label_values(&["post", "foo"]).get(), 1.0); 83 | assert_eq!(vec.with_label_values(&["delete", "bar"]).get(), 4.0); 84 | } 85 | ``` 86 | 87 | ## Auto-flush-able local threaded mertric 88 | 89 | For heavier scenario that a global shared static-metric might not be effecient enough, you can use `make_auto_flush_static_metric!` macro, which will store data in local thread storage, with a custom rate to flush to global `MetricVec`. 90 | 91 | ```rust 92 | use prometheus::*; 93 | 94 | use lazy_static:lazy_static; 95 | use prometheus_static_metric::{auto_flush_from, make_auto_flush_static_metric}; 96 | 97 | make_auto_flush_static_metric! { 98 | 99 | pub label_enum FooBar { 100 | foo, 101 | bar, 102 | } 103 | 104 | pub label_enum Methods { 105 | post, 106 | get, 107 | put, 108 | delete, 109 | } 110 | 111 | pub struct Lhrs: LocalIntCounter { 112 | "product" => FooBar, 113 | "method" => Methods, 114 | "version" => { 115 | http1: "HTTP/1", 116 | http2: "HTTP/2", 117 | }, 118 | } 119 | } 120 | 121 | lazy_static! { 122 | pub static ref HTTP_COUNTER_VEC: IntCounterVec = 123 | register_int_counter_vec ! ( 124 | "http_requests_total", 125 | "Number of HTTP requests.", 126 | & ["product", "method", "version"] // it doesn't matter for the label order 127 | ).unwrap(); 128 | } 129 | 130 | lazy_static! { 131 | // You can also use default flush duration which is 1 second. 132 | // pub static ref TLS_HTTP_COUNTER: Lhrs = auto_flush_from!(HTTP_COUNTER_VEC, Lhrs); 133 | pub static ref TLS_HTTP_COUNTER: Lhrs = auto_flush_from!(HTTP_COUNTER_VEC, Lhrs, std::time::Duration::from_secs(1)); 134 | } 135 | 136 | fn main() { 137 | TLS_HTTP_COUNTER.foo.post.http1.inc(); 138 | TLS_HTTP_COUNTER.foo.post.http1.inc(); 139 | 140 | assert_eq!( 141 | HTTP_COUNTER_VEC 142 | .with_label_values(&["foo", "post", "HTTP/1"]) 143 | .get(), 144 | 0 145 | ); 146 | 147 | ::std::thread::sleep(::std::time::Duration::from_secs(2)); 148 | 149 | TLS_HTTP_COUNTER.foo.post.http1.inc(); 150 | assert_eq!( 151 | HTTP_COUNTER_VEC 152 | .with_label_values(&["foo", "post", "HTTP/1"]) 153 | .get(), 154 | 3 155 | ); 156 | } 157 | ``` 158 | 159 | Please take a look at [examples](./examples) directory for more. 160 | -------------------------------------------------------------------------------- /static-metric/benches/benches.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | use criterion::{criterion_group, criterion_main, Criterion}; 4 | use prometheus::{IntCounter, IntCounterVec, Opts}; 5 | 6 | use prometheus_static_metric::make_static_metric; 7 | 8 | /// Single `IntCounter` performance. 9 | fn bench_single_counter(c: &mut Criterion) { 10 | let counter = IntCounter::new("foo", "bar").unwrap(); 11 | c.bench_function("bench_single_counter", |b| { 12 | b.iter(|| counter.inc()); 13 | }); 14 | } 15 | 16 | /// `IntCounterVec` performance. 17 | fn bench_counter_vec(c: &mut Criterion) { 18 | let counter_vec = IntCounterVec::new(Opts::new("foo", "bar"), &["d1", "d2"]).unwrap(); 19 | c.bench_function("bench_counter_vec", |b| { 20 | b.iter(|| counter_vec.with_label_values(&["foo", "bar"]).inc()); 21 | }); 22 | } 23 | 24 | /// Manually implemented static metrics performance, metrics are placed outside a struct. 25 | fn bench_static_metrics_handwrite_1(c: &mut Criterion) { 26 | let counter_vec = IntCounterVec::new(Opts::new("foo", "bar"), &["d1", "d2"]).unwrap(); 27 | let counter = counter_vec.with_label_values(&["foo", "bar"]); 28 | c.bench_function("bench_static_metrics_handwrite_1", |b| { 29 | b.iter(|| counter.inc()); 30 | }); 31 | } 32 | 33 | /// Manually implemented static metrics performance, metrics are placed nested inside a struct. 34 | fn bench_static_metrics_handwrite_2(c: &mut Criterion) { 35 | let counter_vec = IntCounterVec::new(Opts::new("foo", "bar"), &["d1", "d2"]).unwrap(); 36 | struct StaticCounter1 { 37 | foo: StaticCounter1Field2, 38 | } 39 | struct StaticCounter1Field2 { 40 | bar: IntCounter, 41 | } 42 | let static_counter = StaticCounter1 { 43 | foo: StaticCounter1Field2 { 44 | bar: counter_vec.with_label_values(&["foo", "bar"]), 45 | }, 46 | }; 47 | c.bench_function("bench_static_metrics_handwrite_2", |b| { 48 | b.iter(|| static_counter.foo.bar.inc()); 49 | }); 50 | } 51 | 52 | make_static_metric! { 53 | label_enum D1 { 54 | foo, 55 | } 56 | 57 | label_enum D2 { 58 | bar, 59 | } 60 | 61 | struct StaticCounter2: IntCounter { 62 | "d1" => D1, 63 | "d2" => D2, 64 | } 65 | } 66 | 67 | /// macro implemented static metrics performance. 68 | fn bench_static_metrics_macro(c: &mut Criterion) { 69 | let counter_vec = IntCounterVec::new(Opts::new("foo", "bar"), &["d1", "d2"]).unwrap(); 70 | let static_counter = StaticCounter2::from(&counter_vec); 71 | c.bench_function("bench_static_metrics_macro", |b| { 72 | b.iter(|| static_counter.foo.bar.inc()); 73 | }); 74 | } 75 | 76 | /// macro implemented static metrics performance, with dynamic lookup. 77 | fn bench_static_metrics_macro_with_lookup(c: &mut Criterion) { 78 | let counter_vec = IntCounterVec::new(Opts::new("foo", "bar"), &["d1", "d2"]).unwrap(); 79 | let static_counter = StaticCounter2::from(&counter_vec); 80 | c.bench_function("bench_static_metrics_macro_with_lookup", |b| { 81 | b.iter(|| static_counter.get(D1::foo).get(D2::bar).inc()); 82 | }); 83 | } 84 | 85 | make_static_metric! { 86 | struct StaticCounter3: IntCounter { 87 | "d1" => { val1 }, 88 | "d2" => { val2 }, 89 | "d3" => { val3 }, 90 | "d4" => { val4 }, 91 | "d5" => { val5 }, 92 | "d6" => { val6 }, 93 | "d7" => { val7 }, 94 | "d8" => { val8 }, 95 | "d9" => { val9 }, 96 | "d10" => { val10 }, 97 | } 98 | } 99 | 100 | /// macro implemented static metrics performance, with a deep nesting level. 101 | fn bench_static_metrics_macro_deep(c: &mut Criterion) { 102 | let counter_vec = IntCounterVec::new( 103 | Opts::new("foo", "bar"), 104 | &["d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "d10"], 105 | ) 106 | .unwrap(); 107 | let static_counter = StaticCounter3::from(&counter_vec); 108 | c.bench_function("bench_static_metrics_macro_deep", |b| { 109 | b.iter(|| { 110 | static_counter 111 | .val1 112 | .val2 113 | .val3 114 | .val4 115 | .val5 116 | .val6 117 | .val7 118 | .val8 119 | .val9 120 | .val10 121 | .inc() 122 | }); 123 | }); 124 | } 125 | 126 | criterion_group!( 127 | benches, 128 | bench_counter_vec, 129 | bench_single_counter, 130 | bench_static_metrics_handwrite_1, 131 | bench_static_metrics_handwrite_2, 132 | bench_static_metrics_macro, 133 | bench_static_metrics_macro_deep, 134 | bench_static_metrics_macro_with_lookup, 135 | ); 136 | criterion_main!(benches); 137 | -------------------------------------------------------------------------------- /static-metric/examples/advanced.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | use prometheus::IntCounterVec; 4 | 5 | use lazy_static::lazy_static; 6 | use prometheus::register_int_counter_vec; 7 | use prometheus_static_metric::make_static_metric; 8 | 9 | make_static_metric! { 10 | pub struct HttpRequestStatistics: IntCounter { 11 | "method" => { 12 | post, 13 | get, 14 | put, 15 | delete, 16 | }, 17 | "version" => { 18 | http1: "HTTP/1", 19 | http2: "HTTP/2", 20 | }, 21 | "product" => { 22 | foo, 23 | bar, 24 | }, 25 | } 26 | } 27 | 28 | lazy_static! { 29 | pub static ref HTTP_COUNTER_VEC: IntCounterVec = 30 | register_int_counter_vec!( 31 | "http_requests_total", 32 | "Number of HTTP requests.", 33 | &["product", "method", "version"] // it doesn't matter for the label order 34 | ).unwrap(); 35 | 36 | pub static ref HTTP_COUNTER: HttpRequestStatistics = HttpRequestStatistics 37 | ::from(&HTTP_COUNTER_VEC); 38 | } 39 | 40 | /// This example demonstrates the usage of: 41 | /// 1. using alternative metric types (i.e. IntCounter) 42 | /// 2. specifying different label order compared to the definition 43 | /// 3. using non-identifiers as values 44 | 45 | fn main() { 46 | HTTP_COUNTER.post.http1.foo.inc_by(4); 47 | assert_eq!( 48 | HTTP_COUNTER_VEC 49 | .with_label_values(&["foo", "post", "HTTP/1"]) 50 | .get(), 51 | 4 52 | ); 53 | 54 | // Note: You cannot specify values other than the definition in `get()` because 55 | // it is purely static. 56 | HTTP_COUNTER 57 | .try_get("delete") 58 | .unwrap() 59 | .try_get("HTTP/1") 60 | .unwrap() 61 | .foo 62 | .inc_by(7); 63 | assert_eq!( 64 | HTTP_COUNTER_VEC 65 | .with_label_values(&["foo", "delete", "HTTP/1"]) 66 | .get(), 67 | 7 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /static-metric/examples/local.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | use std::cell::Cell; 4 | 5 | use prometheus::*; 6 | 7 | use lazy_static::lazy_static; 8 | use prometheus_static_metric::make_static_metric; 9 | 10 | make_static_metric! { 11 | pub label_enum Methods { 12 | post, 13 | get, 14 | put, 15 | delete, 16 | } 17 | 18 | pub struct LocalHttpRequestStatistics: LocalIntCounter { 19 | "product" => { 20 | foo, 21 | bar, 22 | }, 23 | "method" => Methods, 24 | "version" => { 25 | http1: "HTTP/1", 26 | http2: "HTTP/2", 27 | }, 28 | } 29 | } 30 | 31 | lazy_static! { 32 | pub static ref HTTP_COUNTER_VEC: IntCounterVec = 33 | register_int_counter_vec!( 34 | "http_requests_total", 35 | "Number of HTTP requests.", 36 | &["product", "method", "version"] // it doesn't matter for the label order 37 | ).unwrap(); 38 | } 39 | 40 | thread_local! { 41 | static THREAD_LAST_TICK_TIME: Cell = Cell::new(timer::now_millis()); 42 | 43 | pub static TLS_HTTP_COUNTER: LocalHttpRequestStatistics = LocalHttpRequestStatistics::from(&HTTP_COUNTER_VEC); 44 | } 45 | 46 | pub fn may_flush_metrics() { 47 | THREAD_LAST_TICK_TIME.with(|tls_last_tick| { 48 | let now = timer::now_millis(); 49 | let last_tick = tls_last_tick.get(); 50 | if now < last_tick + 1000 { 51 | return; 52 | } 53 | tls_last_tick.set(now); 54 | TLS_HTTP_COUNTER.with(|m| m.flush()); 55 | }); 56 | } 57 | 58 | /// This example demonstrates the usage of using static metrics with local metrics. 59 | 60 | fn main() { 61 | TLS_HTTP_COUNTER.with(|m| m.foo.post.http1.inc()); 62 | TLS_HTTP_COUNTER.with(|m| m.foo.post.http1.inc()); 63 | TLS_HTTP_COUNTER.with(|m| m.foo.post.http1.inc()); 64 | 65 | assert_eq!( 66 | HTTP_COUNTER_VEC 67 | .with_label_values(&["foo", "post", "HTTP/1"]) 68 | .get(), 69 | 0 70 | ); 71 | assert_eq!( 72 | HTTP_COUNTER_VEC 73 | .with_label_values(&["foo", "post", "HTTP/2"]) 74 | .get(), 75 | 0 76 | ); 77 | 78 | may_flush_metrics(); 79 | 80 | assert_eq!( 81 | HTTP_COUNTER_VEC 82 | .with_label_values(&["foo", "post", "HTTP/1"]) 83 | .get(), 84 | 0 85 | ); 86 | assert_eq!( 87 | HTTP_COUNTER_VEC 88 | .with_label_values(&["foo", "post", "HTTP/2"]) 89 | .get(), 90 | 0 91 | ); 92 | 93 | ::std::thread::sleep(::std::time::Duration::from_secs(2)); 94 | 95 | may_flush_metrics(); 96 | 97 | assert_eq!( 98 | HTTP_COUNTER_VEC 99 | .with_label_values(&["foo", "post", "HTTP/1"]) 100 | .get(), 101 | 3 102 | ); 103 | assert_eq!( 104 | HTTP_COUNTER_VEC 105 | .with_label_values(&["foo", "post", "HTTP/2"]) 106 | .get(), 107 | 0 108 | ); 109 | } 110 | -------------------------------------------------------------------------------- /static-metric/examples/metric_enum.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | /*! 4 | 5 | Use metric enums to reuse possible values of a label. 6 | 7 | */ 8 | 9 | use prometheus::{CounterVec, IntCounterVec, Opts}; 10 | 11 | use prometheus_static_metric::make_static_metric; 12 | 13 | make_static_metric! { 14 | pub label_enum Methods { 15 | post, 16 | get, 17 | put, 18 | delete, 19 | } 20 | 21 | pub label_enum Products { 22 | foo, 23 | bar, 24 | } 25 | 26 | pub struct MyStaticCounterVec: Counter { 27 | "method" => Methods, 28 | "product" => Products, 29 | } 30 | 31 | pub struct MyAnotherStaticCounterVec: IntCounter { 32 | "error" => { 33 | error_1, 34 | error_2, 35 | }, 36 | "error_method" => Methods, 37 | } 38 | } 39 | 40 | fn main() { 41 | let vec = CounterVec::new(Opts::new("foo", "bar"), &["method", "product"]).unwrap(); 42 | let static_counter_vec = MyStaticCounterVec::from(&vec); 43 | 44 | static_counter_vec.post.foo.inc(); 45 | static_counter_vec.delete.bar.inc_by(4.0); 46 | assert_eq!(static_counter_vec.post.bar.get(), 0.0); 47 | assert_eq!(vec.with_label_values(&["post", "foo"]).get(), 1.0); 48 | assert_eq!(vec.with_label_values(&["delete", "bar"]).get(), 4.0); 49 | 50 | // metric enums will expose an enum for type-safe `get()`. 51 | static_counter_vec.get(Methods::post).foo.inc(); 52 | assert_eq!(static_counter_vec.post.foo.get(), 2.0); 53 | 54 | let vec = IntCounterVec::new(Opts::new("foo", "bar"), &["error", "error_method"]).unwrap(); 55 | let static_counter_vec = MyAnotherStaticCounterVec::from(&vec); 56 | 57 | static_counter_vec.error_1.post.inc(); 58 | static_counter_vec.error_2.delete.inc_by(4); 59 | assert_eq!(static_counter_vec.error_1.delete.get(), 0); 60 | assert_eq!(static_counter_vec.error_1.post.get(), 1); 61 | assert_eq!(vec.with_label_values(&["error_2", "delete"]).get(), 4); 62 | } 63 | -------------------------------------------------------------------------------- /static-metric/examples/register_integration.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | /*! 4 | 5 | You can integrate static metric with the `register_xxx!` macro, 6 | by using the `register_static_xxx!` macro provided by this crate. 7 | 8 | */ 9 | 10 | use prometheus::exponential_buckets; 11 | 12 | use lazy_static::lazy_static; 13 | use prometheus::{register_counter_vec, register_histogram_vec}; 14 | use prometheus_static_metric::{ 15 | make_static_metric, register_static_counter_vec, register_static_histogram_vec, 16 | }; 17 | 18 | make_static_metric! { 19 | pub struct HttpRequestStatistics: Counter { 20 | "method" => { 21 | post, 22 | get, 23 | put, 24 | delete, 25 | }, 26 | "product" => { 27 | foo, 28 | bar, 29 | }, 30 | } 31 | pub struct HttpRequestDuration: Histogram { 32 | "method" => { 33 | post, 34 | get, 35 | put, 36 | delete, 37 | } 38 | } 39 | } 40 | 41 | lazy_static! { 42 | pub static ref HTTP_COUNTER: HttpRequestStatistics = register_static_counter_vec!( 43 | HttpRequestStatistics, 44 | "http_requests_total", 45 | "Number of HTTP requests.", 46 | &["method", "product"] 47 | ) 48 | .unwrap(); 49 | pub static ref HTTP_DURATION: HttpRequestDuration = register_static_histogram_vec!( 50 | HttpRequestDuration, 51 | "http_request_duration", 52 | "Duration of each HTTP request.", 53 | &["method"], 54 | exponential_buckets(0.0005, 2.0, 20).unwrap() 55 | ) 56 | .unwrap(); 57 | } 58 | 59 | fn main() { 60 | HTTP_COUNTER.post.foo.inc(); 61 | HTTP_COUNTER.delete.bar.inc_by(4.0); 62 | assert_eq!(HTTP_COUNTER.post.bar.get(), 0.0); 63 | assert_eq!(HTTP_COUNTER.delete.bar.get(), 4.0); 64 | 65 | HTTP_DURATION.post.observe(0.5); 66 | HTTP_DURATION.post.observe(1.0); 67 | } 68 | -------------------------------------------------------------------------------- /static-metric/examples/simple.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | use prometheus::{CounterVec, Opts}; 4 | 5 | use prometheus_static_metric::make_static_metric; 6 | 7 | make_static_metric! { 8 | pub struct MyStaticCounterVec: Counter { 9 | "method" => { 10 | post, 11 | get, 12 | put, 13 | delete, 14 | }, 15 | "product" => { 16 | foo, 17 | bar, 18 | }, 19 | } 20 | } 21 | 22 | fn main() { 23 | let vec = CounterVec::new(Opts::new("foo", "bar"), &["method", "product"]).unwrap(); 24 | let static_counter_vec = MyStaticCounterVec::from(&vec); 25 | 26 | static_counter_vec.post.foo.inc(); 27 | static_counter_vec.delete.bar.inc_by(4.0); 28 | assert_eq!(static_counter_vec.post.bar.get(), 0.0); 29 | assert_eq!(vec.with_label_values(&["post", "foo"]).get(), 1.0); 30 | assert_eq!(vec.with_label_values(&["delete", "bar"]).get(), 4.0); 31 | } 32 | -------------------------------------------------------------------------------- /static-metric/examples/with_lazy_static.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | use prometheus::CounterVec; 4 | 5 | use lazy_static::lazy_static; 6 | use prometheus::register_counter_vec; 7 | use prometheus_static_metric::make_static_metric; 8 | 9 | make_static_metric! { 10 | pub struct HttpRequestStatistics: Counter { 11 | "method" => { 12 | post, 13 | get, 14 | put, 15 | delete, 16 | }, 17 | "product" => { 18 | foo, 19 | bar, 20 | }, 21 | } 22 | } 23 | 24 | lazy_static! { 25 | pub static ref HTTP_COUNTER_VEC: CounterVec = register_counter_vec!( 26 | "http_requests_total", 27 | "Number of HTTP requests.", 28 | &["method", "product"] 29 | ) 30 | .unwrap(); 31 | pub static ref HTTP_COUNTER: HttpRequestStatistics = 32 | HttpRequestStatistics::from(&HTTP_COUNTER_VEC); 33 | } 34 | 35 | fn main() { 36 | HTTP_COUNTER.post.foo.inc(); 37 | HTTP_COUNTER.delete.bar.inc_by(4.0); 38 | assert_eq!(HTTP_COUNTER.post.bar.get(), 0.0); 39 | assert_eq!( 40 | HTTP_COUNTER_VEC.with_label_values(&["post", "foo"]).get(), 41 | 1.0 42 | ); 43 | assert_eq!( 44 | HTTP_COUNTER_VEC.with_label_values(&["delete", "bar"]).get(), 45 | 4.0 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /static-metric/rustfmt.toml: -------------------------------------------------------------------------------- 1 | reorder_imports = true 2 | use_try_shorthand = true 3 | -------------------------------------------------------------------------------- /static-metric/src/auto_flush_from.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | use proc_macro::TokenStream; 4 | use proc_macro2::Span; 5 | use syn::parse::{Parse, ParseStream}; 6 | use syn::token::*; 7 | use syn::*; 8 | 9 | use quote::quote; 10 | 11 | pub struct AutoFlushFromDef { 12 | class_name: Ident, 13 | inner_class_name: Ident, 14 | source_var_name: Expr, 15 | flush_duration: Option, 16 | } 17 | 18 | impl Parse for AutoFlushFromDef { 19 | fn parse(input: ParseStream) -> Result { 20 | let source_var_name: Expr = input.parse()?; 21 | let _: Comma = input.parse()?; 22 | let class_name: Ident = input.parse()?; 23 | let inner_class_name = Ident::new(&format!("{}Inner", class_name), Span::call_site()); 24 | 25 | let flush_duration = if input.peek(Comma) { 26 | let _: Comma = input.parse()?; 27 | let res: Expr = input.parse()?; 28 | Some(res) 29 | } else { 30 | None 31 | }; 32 | 33 | Ok(AutoFlushFromDef { 34 | class_name, 35 | inner_class_name, 36 | source_var_name, 37 | flush_duration, 38 | }) 39 | } 40 | } 41 | 42 | impl AutoFlushFromDef { 43 | pub fn auto_flush_from(&self) -> TokenStream { 44 | let inner_class_name = self.inner_class_name.clone(); 45 | let class_name = self.class_name.clone(); 46 | let source_var_name = self.source_var_name.clone(); 47 | let update_duration = match &self.flush_duration { 48 | Some(d) => { 49 | quote! { 50 | .with_flush_duration(#d.into()) 51 | } 52 | } 53 | None => quote! {}, 54 | }; 55 | let token_stream_inner = quote! { 56 | { 57 | thread_local! { 58 | static INNER: #inner_class_name = #inner_class_name::from(& #source_var_name)#update_duration; 59 | } 60 | #class_name::from(&INNER) 61 | } 62 | }; 63 | TokenStream::from(token_stream_inner) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /static-metric/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | /*! 4 | This crate provides staticly built metrics to your Prometheus application. 5 | 6 | This is useful since it reduces the amount of branching and processing needed at runtime to collect metrics. 7 | 8 | ```rust 9 | use prometheus::{self, IntCounter, TextEncoder, Encoder}; 10 | 11 | use lazy_static::lazy_static; 12 | use prometheus::register_int_counter; 13 | 14 | lazy_static! { 15 | static ref HIGH_FIVE_COUNTER: IntCounter = 16 | register_int_counter!("highfives", "Number of high fives recieved").unwrap(); 17 | } 18 | 19 | HIGH_FIVE_COUNTER.inc(); 20 | assert_eq!(HIGH_FIVE_COUNTER.get(), 1); 21 | ``` 22 | 23 | Is it reccomended that you consult the [`prometheus` documentation for more information.](https://docs.rs/prometheus/) 24 | */ 25 | mod auto_flush_builder; 26 | mod auto_flush_from; 27 | mod builder; 28 | mod parser; 29 | mod register_macro; 30 | mod util; 31 | 32 | use proc_macro::TokenStream; 33 | 34 | use self::builder::TokensBuilder; 35 | use self::parser::StaticMetricMacroBody; 36 | use self::register_macro::RegisterMethodInvoking; 37 | use crate::auto_flush_from::AutoFlushFromDef; 38 | use auto_flush_builder::AutoFlushTokensBuilder; 39 | 40 | /// Build static metrics. 41 | #[proc_macro] 42 | pub fn make_static_metric(input: TokenStream) -> TokenStream { 43 | let body: StaticMetricMacroBody = syn::parse(input).unwrap(); 44 | TokensBuilder::build(body).into() 45 | } 46 | 47 | /// Build auto flushable static metrics. 48 | /// refer to https://github.com/tikv/rust-prometheus/tree/master/static-metric for more info. 49 | #[proc_macro] 50 | pub fn make_auto_flush_static_metric(input: TokenStream) -> TokenStream { 51 | let body: StaticMetricMacroBody = syn::parse(input).unwrap(); 52 | AutoFlushTokensBuilder::build(body).into() 53 | } 54 | 55 | /// Instantiate an auto flushable static metric struct from a HistogramVec or CounterVec. 56 | #[proc_macro] 57 | pub fn auto_flush_from(input: TokenStream) -> TokenStream { 58 | let def: AutoFlushFromDef = syn::parse(input).unwrap(); 59 | def.auto_flush_from() 60 | } 61 | 62 | /// Register a `CounterVec` and create static metrics from it. 63 | #[proc_macro] 64 | pub fn register_static_counter_vec(input: TokenStream) -> TokenStream { 65 | register_static_vec("counter", input) 66 | } 67 | 68 | /// Register a `IntCounterVec` and create static metrics from it. 69 | #[proc_macro] 70 | pub fn register_static_int_counter_vec(input: TokenStream) -> TokenStream { 71 | register_static_vec("int_counter", input) 72 | } 73 | 74 | /// Register a `GaugeVec` and create static metrics from it. 75 | #[proc_macro] 76 | pub fn register_static_gauge_vec(input: TokenStream) -> TokenStream { 77 | register_static_vec("gauge", input) 78 | } 79 | 80 | /// Register a `IntGaugeVec` and create static metrics from it. 81 | #[proc_macro] 82 | pub fn register_static_int_gauge_vec(input: TokenStream) -> TokenStream { 83 | register_static_vec("int_gauge", input) 84 | } 85 | 86 | /// Register a `HistogramVec` and create static metrics from it. 87 | #[proc_macro] 88 | pub fn register_static_histogram_vec(input: TokenStream) -> TokenStream { 89 | register_static_vec("histogram", input) 90 | } 91 | 92 | /// Procedural macro handler for `register_static_xxx_vec!`. 93 | fn register_static_vec(register_type: &str, input: TokenStream) -> TokenStream { 94 | let invoking: RegisterMethodInvoking = syn::parse(input).unwrap(); 95 | invoking.into_tokens(register_type).into() 96 | } 97 | -------------------------------------------------------------------------------- /static-metric/src/parser.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | use std::collections::HashMap; 4 | 5 | use proc_macro2::Span; 6 | use syn::parse::{Parse, ParseStream}; 7 | use syn::punctuated::Punctuated; 8 | use syn::token::*; 9 | use syn::*; 10 | 11 | /// Matches `label_enum` keyword. 12 | struct LabelEnum { 13 | #[allow(dead_code)] 14 | pub span: Span, 15 | } 16 | 17 | impl Parse for LabelEnum { 18 | fn parse(input: ParseStream) -> Result { 19 | let ident: Ident = input.parse()?; 20 | if &ident == "label_enum" { 21 | return Ok(LabelEnum { span: ident.span() }); 22 | } 23 | Err(input.error("Expected `label_enum`")) 24 | } 25 | } 26 | 27 | /// Matches `... => { ... name: value_string_literal ... }` 28 | #[derive(Debug)] 29 | struct MetricValueDefFull { 30 | name: Ident, 31 | value: LitStr, 32 | } 33 | 34 | impl Parse for MetricValueDefFull { 35 | fn parse(input: ParseStream) -> Result { 36 | let name = input.parse()?; 37 | let _: Colon = input.parse()?; 38 | let value = input.parse()?; 39 | Ok(MetricValueDefFull { name, value }) 40 | } 41 | } 42 | 43 | /// Matches `... => { ... value ... }` 44 | #[derive(Debug)] 45 | struct MetricValueDefShort { 46 | value: Ident, 47 | } 48 | 49 | impl Parse for MetricValueDefShort { 50 | fn parse(input: ParseStream) -> Result { 51 | Ok(MetricValueDefShort { 52 | value: input.parse()?, 53 | }) 54 | } 55 | } 56 | 57 | /// Matches either `... => { ... name: value_string_literal ... }` or `... => { ... value ... }` 58 | #[derive(Debug)] 59 | pub struct MetricValueDef { 60 | pub name: Ident, 61 | pub value: LitStr, 62 | } 63 | 64 | impl Parse for MetricValueDef { 65 | fn parse(input: ParseStream) -> Result { 66 | if input.peek2(Colon) { 67 | let full: MetricValueDefFull = input.parse()?; 68 | Ok(full.into()) 69 | } else { 70 | let short: MetricValueDefShort = input.parse()?; 71 | Ok(short.into()) 72 | } 73 | } 74 | } 75 | 76 | impl From for MetricValueDef { 77 | fn from(e: MetricValueDefFull) -> MetricValueDef { 78 | MetricValueDef { 79 | name: e.name, 80 | value: e.value, 81 | } 82 | } 83 | } 84 | 85 | impl From for MetricValueDef { 86 | fn from(e: MetricValueDefShort) -> MetricValueDef { 87 | MetricValueDef { 88 | value: LitStr::new(&e.value.to_string(), e.value.span()), 89 | name: e.value, 90 | } 91 | } 92 | } 93 | 94 | /// Matches `{ value_def, value_def, ... }` 95 | #[derive(Debug)] 96 | pub struct MetricValueDefList(Vec); 97 | 98 | impl Parse for MetricValueDefList { 99 | fn parse(input: ParseStream) -> Result { 100 | let body; 101 | let _ = braced!(body in input); 102 | let p = Punctuated::::parse_terminated(&body)?; 103 | Ok(MetricValueDefList(p.into_iter().collect())) 104 | } 105 | } 106 | 107 | impl MetricValueDefList { 108 | pub fn get(&self) -> &Vec { 109 | &self.0 110 | } 111 | 112 | pub fn get_names(&self) -> Vec<&Ident> { 113 | self.0.iter().map(|v| &v.name).collect() 114 | } 115 | 116 | pub fn get_values(&self) -> Vec<&LitStr> { 117 | self.0.iter().map(|v| &v.value).collect() 118 | } 119 | } 120 | 121 | /// Matches `(pub) label_enum Foo { value_def, value_def, ... }` 122 | #[derive(Debug)] 123 | pub struct MetricEnumDef { 124 | pub visibility: Visibility, 125 | pub enum_name: Ident, 126 | pub definitions: MetricValueDefList, 127 | } 128 | 129 | impl Parse for MetricEnumDef { 130 | fn parse(input: ParseStream) -> Result { 131 | let visibility = input.parse()?; 132 | let _: LabelEnum = input.parse()?; 133 | let enum_name = input.parse()?; 134 | let definitions = input.parse()?; 135 | Ok(MetricEnumDef { 136 | visibility, 137 | enum_name, 138 | definitions, 139 | }) 140 | } 141 | } 142 | 143 | impl MetricEnumDef { 144 | /// Builds `enum_name::enum_item`. 145 | pub fn build_fields_with_path(&self) -> Vec { 146 | self.definitions 147 | .get() 148 | .iter() 149 | .map(|v| { 150 | let mut segments = Punctuated::new(); 151 | segments.push(PathSegment { 152 | ident: self.enum_name.clone(), 153 | arguments: PathArguments::None, 154 | }); 155 | segments.push(PathSegment { 156 | ident: v.name.clone(), 157 | arguments: PathArguments::None, 158 | }); 159 | Path { 160 | leading_colon: None, 161 | segments, 162 | } 163 | }) 164 | .collect() 165 | } 166 | } 167 | 168 | #[derive(Debug)] 169 | enum MetricLabelArm { 170 | ValueDefinitionList(MetricValueDefList), 171 | EnumReference(Ident), 172 | } 173 | 174 | /// Matches `label_key => { value_def, value_def, ... }` or 175 | /// `label_key => enum_name` 176 | #[derive(Debug)] 177 | pub struct MetricLabelDef { 178 | pub label_key: LitStr, 179 | arm: MetricLabelArm, 180 | } 181 | 182 | impl Parse for MetricLabelDef { 183 | fn parse(input: ParseStream) -> Result { 184 | let label_key = input.parse()?; 185 | let _: FatArrow = input.parse()?; 186 | let arm = if input.peek(Brace) { 187 | MetricLabelArm::ValueDefinitionList(input.parse()?) 188 | } else { 189 | MetricLabelArm::EnumReference(input.parse()?) 190 | }; 191 | Ok(MetricLabelDef { label_key, arm }) 192 | } 193 | } 194 | 195 | impl MetricLabelDef { 196 | /// Get (or lookup if label is defined using enums) the value definition list. 197 | pub fn get_value_def_list<'a>( 198 | &'a self, 199 | enum_definitions: &'a HashMap, 200 | ) -> &'a MetricValueDefList { 201 | match &self.arm { 202 | MetricLabelArm::ValueDefinitionList(ref v) => v, 203 | MetricLabelArm::EnumReference(ref e) => &enum_definitions.get(e).unwrap().definitions, 204 | } 205 | } 206 | 207 | /// Get the enum identifier if label is defined using enums. 208 | pub fn get_enum_ident(&self) -> Option<&Ident> { 209 | match &self.arm { 210 | MetricLabelArm::ValueDefinitionList(_) => None, 211 | MetricLabelArm::EnumReference(ref e) => Some(e), 212 | } 213 | } 214 | } 215 | 216 | /// Matches `(pub) struct Foo: Counter { a => { ... }, b => { ... }, ... }` 217 | #[derive(Debug)] 218 | pub struct MetricDef { 219 | pub visibility: Visibility, 220 | pub struct_name: Ident, 221 | pub metric_type: Ident, 222 | pub labels: Vec, 223 | } 224 | 225 | impl Parse for MetricDef { 226 | fn parse(input: ParseStream) -> Result { 227 | let visibility = input.parse()?; 228 | let _: Struct = input.parse()?; 229 | let struct_name = input.parse()?; 230 | let _: Colon = input.parse()?; 231 | let metric_type = input.parse()?; 232 | 233 | let body; 234 | let _ = braced!(body in input); 235 | let p = Punctuated::::parse_terminated(&body)?; 236 | let labels = p.into_iter().collect(); 237 | 238 | Ok(MetricDef { 239 | visibility, 240 | struct_name, 241 | metric_type, 242 | labels, 243 | }) 244 | } 245 | } 246 | 247 | #[derive(Debug)] 248 | pub enum StaticMetricMacroBodyItem { 249 | Metric(MetricDef), 250 | Enum(MetricEnumDef), 251 | } 252 | 253 | impl Parse for StaticMetricMacroBodyItem { 254 | fn parse(input: ParseStream) -> Result { 255 | if input.peek(Token!(struct)) || input.peek2(Token!(struct)) { 256 | Ok(StaticMetricMacroBodyItem::Metric(input.parse()?)) 257 | } else { 258 | Ok(StaticMetricMacroBodyItem::Enum(input.parse()?)) 259 | } 260 | } 261 | } 262 | 263 | #[derive(Debug)] 264 | pub struct StaticMetricMacroBody { 265 | pub items: Vec, 266 | } 267 | 268 | impl Parse for StaticMetricMacroBody { 269 | fn parse(input: ParseStream) -> Result { 270 | let mut items = Vec::new(); 271 | while !input.is_empty() { 272 | items.push(input.parse()?); 273 | } 274 | Ok(StaticMetricMacroBody { items }) 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /static-metric/src/register_macro.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | use proc_macro2::{Span, TokenStream as Tokens}; 4 | use syn::parse::{Parse, ParseStream}; 5 | use syn::punctuated::Punctuated; 6 | use syn::*; 7 | 8 | use quote::quote; 9 | 10 | /// Matches `register_static_xxx_vec!(static_struct_name, name, desc, labels, ...)`. 11 | pub struct RegisterMethodInvoking { 12 | static_struct_name: Ident, 13 | arguments: Vec, 14 | } 15 | 16 | impl Parse for RegisterMethodInvoking { 17 | fn parse(input: ParseStream) -> Result { 18 | let static_struct_name = input.parse()?; 19 | let _: Token![,] = input.parse()?; 20 | let p = Punctuated::::parse_terminated(input)?; 21 | let arguments = p.into_iter().collect(); 22 | 23 | Ok(RegisterMethodInvoking { 24 | static_struct_name, 25 | arguments, 26 | }) 27 | } 28 | } 29 | 30 | impl RegisterMethodInvoking { 31 | pub fn into_tokens(self, register_type: &str) -> Tokens { 32 | let register_macro_name = Ident::new( 33 | &format!("register_{}_vec", register_type), 34 | Span::call_site(), 35 | ); 36 | let (static_struct_name, arguments) = (self.static_struct_name, self.arguments); 37 | quote! { 38 | { 39 | let metric_result = #register_macro_name!(#(#arguments),*); 40 | metric_result.map(|m| #static_struct_name::from(&m)) 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /static-metric/src/util.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | use proc_macro2::Span; 4 | use syn::Ident; 5 | 6 | pub fn is_local_metric(metric_type: Ident) -> bool { 7 | metric_type.to_string().starts_with("Local") 8 | } 9 | 10 | pub fn to_non_local_metric_type(metric_type: Ident) -> Ident { 11 | let metric_type_str = metric_type.to_string(); 12 | if let Some(stripped) = metric_type_str.strip_prefix("Local") { 13 | Ident::new(stripped, Span::call_site()) 14 | } else { 15 | metric_type 16 | } 17 | } 18 | 19 | pub fn get_metric_vec_type(metric_type: Ident) -> Ident { 20 | Ident::new(&format!("{}Vec", metric_type), Span::call_site()) 21 | } 22 | 23 | pub fn get_label_struct_name(struct_name: Ident, label_index: usize) -> Ident { 24 | let mut struct_name = struct_name.to_string(); 25 | if label_index > 0 { 26 | struct_name.push_str(&(label_index + 1).to_string()); 27 | } 28 | Ident::new(&struct_name, Span::call_site()) 29 | } 30 | 31 | pub fn get_member_type( 32 | struct_name: Ident, 33 | label_index: usize, 34 | metric_type: Ident, 35 | is_last_label: bool, 36 | ) -> Ident { 37 | if is_last_label { 38 | metric_type 39 | } else { 40 | get_label_struct_name(struct_name, label_index + 1) 41 | } 42 | } 43 | 44 | pub fn get_inner_member_type( 45 | struct_name: Ident, 46 | label_index: usize, 47 | metric_type: Ident, 48 | is_last_label: bool, 49 | ) -> Ident { 50 | if is_last_label { 51 | metric_type 52 | } else { 53 | Ident::new( 54 | &format!( 55 | "{}Inner", 56 | get_label_struct_name(struct_name, label_index + 1) 57 | ), 58 | Span::call_site(), 59 | ) 60 | } 61 | } 62 | 63 | pub fn get_delegator_member_type( 64 | struct_name: Ident, 65 | label_index: usize, 66 | is_last_label: bool, 67 | ) -> Ident { 68 | if is_last_label { 69 | Ident::new("usize", Span::call_site()) 70 | } else { 71 | Ident::new( 72 | &format!( 73 | "{}{}", 74 | get_label_struct_name(struct_name, label_index + 1), 75 | "Delegator" 76 | ), 77 | Span::call_site(), 78 | ) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /static-metric/tests/label_enum.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | use prometheus_static_metric::make_static_metric; 4 | 5 | make_static_metric! { 6 | pub label_enum Methods { 7 | post, 8 | get, 9 | put, 10 | delete, 11 | } 12 | 13 | pub label_enum MethodsWithName { 14 | post: "post_name", 15 | } 16 | } 17 | 18 | #[test] 19 | fn test_format() { 20 | assert_eq!("post", Methods::post.get_str()); 21 | assert_eq!("post", format!("{}", Methods::post)); 22 | assert_eq!("post", format!("{:?}", Methods::post)); 23 | assert_eq!("delete", Methods::delete.get_str()); 24 | assert_eq!("delete", format!("{}", Methods::delete)); 25 | assert_eq!("delete", format!("{:?}", Methods::delete)); 26 | assert_eq!("post_name", MethodsWithName::post.get_str()); 27 | assert_eq!("post_name", format!("{}", MethodsWithName::post)); 28 | assert_eq!("post_name", format!("{:?}", MethodsWithName::post)); 29 | } 30 | 31 | #[test] 32 | fn test_equal() { 33 | assert_eq!(Methods::post, Methods::post); 34 | assert_ne!(Methods::post, Methods::get); 35 | } 36 | -------------------------------------------------------------------------------- /static-metric/tests/metric.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 | 3 | use prometheus::core::Collector; 4 | use prometheus::{Counter, CounterVec, Opts}; 5 | use prometheus_static_metric::make_static_metric; 6 | 7 | make_static_metric! { 8 | pub label_enum Methods { 9 | post, 10 | get, 11 | put, 12 | delete, 13 | } 14 | 15 | pub label_enum MethodsWithName { 16 | post: "post_name", 17 | get: "get_name", 18 | put, 19 | delete, 20 | } 21 | 22 | pub struct SimpleCounterVec: Counter { 23 | "method" => Methods, 24 | "product" => { 25 | foo, 26 | bar, 27 | }, 28 | } 29 | 30 | pub struct ComplexCounterVec: Counter { 31 | "method" => MethodsWithName, 32 | "product" => { 33 | foo, 34 | bar: "bar_name", 35 | }, 36 | } 37 | 38 | struct NonPubCounterVec: Counter { 39 | "method" => Methods, 40 | } 41 | } 42 | 43 | /// Helper method to get a label values of a `Counter`. 44 | fn get_labels(counter: &Counter) -> Vec { 45 | counter.collect()[0].get_metric()[0] 46 | .get_label() 47 | .into_iter() 48 | .map(|label| label.get_value().to_string()) 49 | .collect() 50 | } 51 | 52 | #[test] 53 | fn test_fields() { 54 | let vec = CounterVec::new(Opts::new("foo", "bar"), &["method", "product"]).unwrap(); 55 | let metric = SimpleCounterVec::from(&vec); 56 | assert_eq!(get_labels(&metric.post.foo), vec!["post", "foo"]); 57 | assert_eq!(get_labels(&metric.put.bar), vec!["put", "bar"]); 58 | } 59 | 60 | #[test] 61 | fn test_field_value() { 62 | let vec = CounterVec::new(Opts::new("foo", "bar"), &["method", "product"]).unwrap(); 63 | let metric = ComplexCounterVec::from(&vec); 64 | assert_eq!(get_labels(&metric.post.foo), vec!["post_name", "foo"]); 65 | assert_eq!(get_labels(&metric.put.bar), vec!["put", "bar_name"]); 66 | } 67 | 68 | #[test] 69 | fn test_label_order() { 70 | let vec = CounterVec::new(Opts::new("foo", "bar"), &["product", "method"]).unwrap(); 71 | let metric = SimpleCounterVec::from(&vec); 72 | assert_eq!(get_labels(&metric.post.foo), vec!["post", "foo"]); 73 | assert_eq!(get_labels(&metric.put.bar), vec!["put", "bar"]); 74 | } 75 | 76 | #[test] 77 | #[should_panic] 78 | fn test_wrong_label_1() { 79 | let vec = CounterVec::new(Opts::new("foo", "bar"), &["method_foo", "product"]).unwrap(); 80 | SimpleCounterVec::from(&vec); 81 | } 82 | 83 | #[test] 84 | #[should_panic] 85 | fn test_wrong_label_2() { 86 | let vec = CounterVec::new(Opts::new("foo", "bar"), &["method", "product_foo"]).unwrap(); 87 | SimpleCounterVec::from(&vec); 88 | } 89 | 90 | #[test] 91 | fn test_try_get() { 92 | let vec = CounterVec::new(Opts::new("foo", "bar"), &["method", "product"]).unwrap(); 93 | let metric = SimpleCounterVec::from(&vec); 94 | assert_eq!( 95 | get_labels(&metric.try_get("get").unwrap().bar), 96 | vec!["get", "bar"] 97 | ); 98 | assert_eq!( 99 | get_labels(&metric.get.try_get("bar").unwrap()), 100 | vec!["get", "bar"] 101 | ); 102 | assert_eq!( 103 | get_labels(&metric.try_get("get").unwrap().try_get("bar").unwrap()), 104 | vec!["get", "bar"] 105 | ); 106 | assert!(metric.try_get("get_foo").is_none()); 107 | assert!(metric.try_get("get").unwrap().try_get("bar2").is_none()); 108 | } 109 | 110 | #[test] 111 | fn test_try_get_with_field_value() { 112 | let vec = CounterVec::new(Opts::new("foo", "bar"), &["method", "product"]).unwrap(); 113 | let metric = ComplexCounterVec::from(&vec); 114 | assert_eq!( 115 | get_labels(&metric.try_get("get_name").unwrap().bar), 116 | vec!["get_name", "bar_name"] 117 | ); 118 | assert_eq!( 119 | get_labels(&metric.get.try_get("bar_name").unwrap()), 120 | vec!["get_name", "bar_name"] 121 | ); 122 | assert_eq!( 123 | get_labels( 124 | &metric 125 | .try_get("get_name") 126 | .unwrap() 127 | .try_get("bar_name") 128 | .unwrap() 129 | ), 130 | vec!["get_name", "bar_name"] 131 | ); 132 | assert!(metric.try_get("get").is_none()); 133 | } 134 | 135 | #[test] 136 | fn test_get() { 137 | let vec = CounterVec::new(Opts::new("foo", "bar"), &["method", "product"]).unwrap(); 138 | let metric = SimpleCounterVec::from(&vec); 139 | assert_eq!( 140 | get_labels(&metric.get(Methods::get).bar), 141 | vec!["get", "bar"] 142 | ); 143 | } 144 | 145 | #[test] 146 | fn test_get_with_field_value() { 147 | let vec = CounterVec::new(Opts::new("foo", "bar"), &["method", "product"]).unwrap(); 148 | let metric = ComplexCounterVec::from(&vec); 149 | assert_eq!( 150 | get_labels(&metric.get(MethodsWithName::get).bar), 151 | vec!["get_name", "bar_name"] 152 | ); 153 | } 154 | --------------------------------------------------------------------------------