pax_global_header00006660000000000000000000000064143011477100014510gustar00rootroot0000000000000052 comment=0cf38e85047d0ac1d2051007280c6bcdc159530e prom-client-14.1.0/000077500000000000000000000000001430114771000140245ustar00rootroot00000000000000prom-client-14.1.0/.editorconfig000066400000000000000000000002341430114771000165000ustar00rootroot00000000000000; http://editorconfig.org root = true [*] indent_style = tab end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true prom-client-14.1.0/.eslintignore000066400000000000000000000000121430114771000165200ustar00rootroot00000000000000coverage/ prom-client-14.1.0/.eslintrc000066400000000000000000000030621430114771000156510ustar00rootroot00000000000000{ "plugins": ["prettier"], "extends": [ "eslint:recommended", "plugin:node/recommended", "plugin:prettier/recommended" ], "env": { "node": true, "es6": true }, "parserOptions": { "ecmaVersion": 2019 }, "rules": { "no-underscore-dangle": "off", "no-mixed-requires": "off", "new-cap": "off", "no-path-concat": "off", "no-shadow": "warn", "no-array-constructor": "error", "no-caller": "error", "no-eval": "error", "no-extend-native": "error", "no-extra-bind": "error", "no-with": "error", "no-loop-func": "error", "no-multi-str": "error", "no-new-func": "error", "no-new-object": "error", "no-return-assign": "error", "no-sequences": "error", "no-shadow-restricted-names": "error", "no-unused-expressions": "error", "no-use-before-define": "off", "no-new": "off", "dot-notation": ["error", { "allowKeywords": true }], "eqeqeq": "error", "new-parens": "error", "strict": ["error", "global"], "yoda": "error", "valid-jsdoc": "error", "object-shorthand": "error", "no-var": "error", "prefer-const": "error", "prefer-arrow-callback": "error", "arrow-body-style": [ "error", "as-needed", { "requireReturnForObjectLiteral": true } ], "prefer-template": "error" }, "overrides": [ { "files": ["test/**/*.js"], "env": { "jest": true }, "rules": { "no-console": "off", "no-unused-vars": "off", "no-shadow": "off", "no-unused-expressions": "off" } }, { "files": ["example/**/*.js", "benchmarks/**/*.js"], "rules": { "no-console": "off" } } ] } prom-client-14.1.0/.gitattributes000066400000000000000000000000231430114771000167120ustar00rootroot00000000000000* text=auto eol=lf prom-client-14.1.0/.github/000077500000000000000000000000001430114771000153645ustar00rootroot00000000000000prom-client-14.1.0/.github/workflows/000077500000000000000000000000001430114771000174215ustar00rootroot00000000000000prom-client-14.1.0/.github/workflows/changelog.yml000066400000000000000000000010401430114771000220660ustar00rootroot00000000000000on: [pull_request] name: Changelog Reminder jobs: remind: name: Changelog Reminder runs-on: ubuntu-latest steps: - uses: actions/checkout@master - name: Changelog Reminder uses: peterjgrainger/action-changelog-reminder@v1.2.0 with: changelog_regex: 'CHANGELOG\.md' customPrMessage: 'Thanks for opening this pull request! Each pull request require an update in the CHANGELOG. Please update it based on your changes.' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} prom-client-14.1.0/.github/workflows/nodejs.yml000066400000000000000000000021231430114771000214240ustar00rootroot00000000000000name: Node.js CI on: push: branches: - master - main - next pull_request: branches: - '**' jobs: build: name: Test on Node.js v${{ matrix.node-version }} and OS ${{ matrix.os }} strategy: fail-fast: false matrix: node-version: [10.x, 12.x, 14.x, 16.x, 17.x, 18.x] os: [ubuntu-latest, windows-latest, macOS-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} - name: Restore dependencies cache uses: actions/cache@v2 id: cache with: path: node_modules key: ${{ matrix.os }}-{{ matrix.node-version }}-node_modules-${{ hashFiles('**/package.json') }} restore-keys: | ${{ matrix.os }}-{{ matrix.node-version }}-node_modules- - name: Install Dependencies if: steps.cache.outputs.cache-hit != 'true' run: npm i - name: Test run: npm run test prom-client-14.1.0/.gitignore000066400000000000000000000001001430114771000160030ustar00rootroot00000000000000.DS_Store node_modules .idea/ *.log coverage/ package-lock.json prom-client-14.1.0/CHANGELOG.md000066400000000000000000000327341430114771000156460ustar00rootroot00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Breaking ### Changed ### Added ## [14.1.0] - 2022-08-23 ### Changed - types: converted all the generic Metric types to be optional - The `done()` functions returned by `gauge.startTimer()` and `summary.startTimer()` now return the timed duration. Histograms already had this behavior. - types: fixed type for `registry.getMetricsAsArray()` - Improve performance of `gague.inc()` and `gauge.dec()` by calling `hashObject()` once. ### Added - The `processResources` metric was added, which keeps a track of all sorts of active resources. It consists of the following gauges: - `nodejs_active_resources` - Number of active resources that are currently keeping the event loop alive, grouped by async resource type. - `nodejs_active_resources_total` - Total number of active resources. It is supposed to provide the combined result of the `processHandles` and `processRequests` metrics along with information about any other types of async resources that these metrics do not keep a track of (like timers). - Support gzipped pushgateway requests ## [14.0.1] - 2021-11-02 ### Changed - changed: typedef for pushgateway to reflect js implementation. ## [14.0.0] - 2021-09-18 ### Breaking - changed: `linearBuckets` does not propagate rounding errors anymore. Fewer bucket bounds will be affected by rounding errors. Histogram bucket labels may change. [`6f1f3b2`](https://github.com/siimon/prom-client/commit/6f1f3b24c9c21311ff33e7d4b987b40c6b304e04) - changed: The push gateway methods `pushAdd()`, `push()` and `delete()` now return Promises instead of accepting a callback: ```js // Old: gateway.pushAdd({ jobName: 'test' }, (err, resp, body) => {}); // New: gateway .pushAdd({ jobName: 'test' }) .then(({ resp, body }) => {}) .catch(err => {}); // or const { resp, body } = await gateway.pushAdd({ jobName: 'test' }); ``` [`f177b1f`](https://github.com/siimon/prom-client/commit/f177b1fd3d4db5fc48fcb1ec02d94069fffcf144) - changed: The default `nodejs_eventloop_lag_*` metrics are now reset every time they are observed. This prevents these metrics from "stabilizing" over a long period of time and becoming insensitive to small changes. For more info, see [#370](https://github.com/siimon/prom-client/issues/370). [`0f444cd`](https://github.com/siimon/prom-client/commit/0f444cd38e4c7074991270106c270f731bafddb8) ### Changed - Add missing `await`/`then`s to examples. [`074f339`](https://github.com/siimon/prom-client/commit/074f339914e5d71b5829cd4a949affae23dbc409) - Add missing type declaration for `client.contentType`. [`3b66641`](https://github.com/siimon/prom-client/commit/3b6664160bdd1555045b03d8f4c421022f30e1db) - Modernize some label processing code. [`c9bf1d8`](https://github.com/siimon/prom-client/commit/c9bf1d8e3db3b5fb97faf2df9ca9b9af670288f3) ## [13.2.0] - 2021-08-08 ### Changed - Don't add event listener to `process` if cluster module is not used. - fix: set labels for default memory metrics on linux. - fix: fix DEP0152 deprecation warning in Node.js v16+. - fix: Set aggregation mode for newer event loop metrics. (Fixes [#418](https://github.com/siimon/prom-client/issues/418)) - Improve performance of/reduce memory allocations in Gauge. ### Added - feat: added `zero()` to `Histogram` for setting the metrics for a given label combination to zero - fix: allow `Gauge.inc/dec(0)` without defaulting to 1 ## [13.1.0] - 2021-01-24 ### Changed - fix: push client attempting to write Promise (fixes [#390](https://github.com/siimon/prom-client/issues/390)) - types: improve type checking of labels - fix: Summary#observe should throw when adding additional labels to labelset (fixes [#262](https://github.com/siimon/prom-client/issues/262)) ### Added - feat: added the ability to pass labels as an object to `labels()` and `remove()` - Added: More examples with commented output ## [13.0.0] - 2020-12-16 ### Breaking - changed: The following functions are now async (return a promise): `registry.metrics()` `registry.getMetricsAsJSON()` `registry.getMetricsAsArray()` `registry.getSingleMetricAsString()` If your metrics server has a line like `res.send(register.metrics())`, you should change it to `res.send(await register.metrics())`. Additionally, all metric types now accept an optional `collect` function, which is called when the metric's value should be collected and within which you should set the metric's value. You should provide a `collect` function for point-in-time metrics (e.g. current memory usage, as opposed to HTTP request durations that are continuously logged in a histogram). - changed: `register.clusterMetrics()` no longer accepts a callback; it only returns a promise. - removed: v12.0.0 added the undocumented functions `registry.registerCollector` and `registry.collectors()`. These have been removed. If you were using them, you should instead provide a `collect` function as described above. ### Changed - fix: provide nodejs_version_info metric value after calling `registry.resetMetrics()` (#238) - fix: provide process_max_fds metric value after calling `registry.resetMetrics()` - fix: provide process_start_time_seconds metric value after calling `registry.resetMetrics()` - chore: improve performance of `registry.getMetricAsPrometheusString` - chore: refactor metrics to reduce code duplication - chore: replace `utils.getPropertiesFromObj` with `Object.values` - chore: remove unused `catch` bindings - chore: upgrade Prettier to 2.x - fix: startTimer returns `number` in typescript instead of `void` - fix: incorrect typings of `registry.getSingleMetric' (#388) - chore: stop testing node v13 on CI ### Added - feat: exposed `registry.registerCollector()` and `registry.collectors()` methods in TypeScript declaration - Added: complete working example of a pushgateway push in `example/pushgateway.js` - feat: added support for adding labels to default metrics (#374) - Added CHANGELOG reminder ## [12.0.0] - 2020-02-20 ### Breaking - Dropped support for end-of-life Node.js versions 6.x and 8.x - Dropped the previously deprecated support for positional parameters in constructors, only the config object forms remain. - Default metrics are collected on scrape of metrics endpoint, not on an interval. The `timeout` option to `collectDefaultMetrics(conf)` is no longer supported or needed, and the function no longer returns a `Timeout` object. ### Changed - chore: remove ignored package-lock.json - fix: `process_max_fds` is process limit, not OS (#314) - Changed `Metric` labelNames & labelValues in TypeScript declaration to a generic type `T extends string`, instead of `string` - Lazy-load Node.js Cluster module to fix Passenger support (#293) - fix: avoid mutation bug in `registry.getMetricsAsJSON()` - fix: improve performance of `registry.getMetrics*` - End function of histogram `startTimer`, when invoked returns the number of seconds - chore: reindent package.json - chore: correct var name in processStartTime - chore: add test for `process_start_time_seconds` - chore: spelling corrections in README ### Added - feat: implement GC metrics collection without native(C++) modules. - feat: implement advanced event loop monitoring ## [11.5.3] - 2019-06-27 ### Changed - Parameter `compressCount` in Summaries to control compression of data in t-digest. - Compress t-digest in Summaries ## [11.5.2] - 2019-06-20 ### Changed - fix: avoid mutation bug in registry ## [11.5.1] - 2019-06-13 ### Changed - fix: guard against missing constructor ## [11.5.0] - 2019-06-04 ### Added - Added `timestamps` toggle to `collectDefaultMetrics` options - Export `validateMetricName` ## [11.4.0] - 2019-06-04 ### Added - `nodejs_active_handles` metric to the `collectDefaultMetrics()`. Unlike `nodejs_active_handles_total` it split count of active handles by type. - `nodejs_active_requests` metric to the `collectDefaultMetrics()`. Unlike `nodejs_active_requests_total` it split count of active requests by type. ## [11.3.0] - 2019-04-02 ### Changed - Check that cluster worker is still connected before attempting to query it for metrics. (#244) ### Added - Added a `remove()` method on each metric type, based on [Prometheus "Writing Client Libraries" section on labels](https://prometheus.io/docs/instrumenting/writing_clientlibs/#labels) ## [11.2.1] ### Breaking ### Changed ### Added - Updated types for Summary in typescript definition file ## [11.2.0] ### Changed - Updated child dependency `merge` patch version to remove vulnerability. ### Added - Added an initial `benchmark` suite which can be run with `npm run benchmarks`. - Add support for sliding windows in Summaries ## [11.1.3] - 2018-09-22 ### Changed - Fixed performance by avoiding `Object.assign` on hot paths, as well as mutating objects when appropriate. ## [11.1.2] - 2018-09-19 ### Changed - Allow setting Gauge values to NaN, +Inf, and -Inf - Fixed `histogram` scrape performance by using `acc.push` instead of `acc.concat`. Fixes #216 with #219 ## [11.1.1] - 2018-06-29 ### Changed - Fixed `processOpenFileDescriptors` metric when no custom config was set ## [11.1.0] - 2018-06-29 - Added ability to set a name prefix in the default metrics ### Changed - Fixed `startTimer` utility to not mutate objects passed as `startLabels` - Fixed `Counter` to validate labels parameter of `inc()` against initial labelset - Fixed `AggregatorFactory` losing the aggregator method of metrics ## [11.0.0] - 2018-03-10 ### Breaking - Fixed `gauge.setToCurrentTime()` to use seconds instead of milliseconds - This conforms to Prometheus [best practices](https://prometheus.io/docs/practices/naming/#base-units) - Dropped support for node 4 ## [10.2.3] - 2018-02-28 ### Breaking ### Changed - Fixed issue that `registry.getMetricsAsJSON()` ignores registry default labels ### Added ## [10.2.2] - 2017-11-02 ### Changed - Fixed invalid `process_virtual_memory_bytes` reported under linux ## [10.2.1] - 2017-10-27 ### Changed - Only resolve/reject `clusterMetrics` promise if no callback is provided ## [10.2.0] - 2017-10-16 ### Changed - Don't add event listeners if cluster module is not used. - Fixed issue with counters having extra records when using empty labels ### Added - Added `reset` to Counter and Gauge - Added `resetMetrics` to register to calling `reset` of all metric instances ## [10.1.1] - 2017-09-26 ### Changed - Update TypeScript definitions and JSDoc comments to match JavaScript sources - Fix lexical scope of `arguments` in cluster code ## [10.1.0] - 2017-09-04 ### Added - Support aggregating metrics across workers in a Node.js cluster. ## [10.0.4] - 2017-08-22 ### Changed - Include invalid values in the error messages ## [10.0.3] - 2017-08-07 ### Added - Added registerMetric to definitions file ### Changed - Fixed typing of DefaultMetricsCollectorConfiguration in definitions file - Don't pass timestamps through to pushgateway by default ## [10.0.2] - 2017-07-07 ### Changed - Don't poll default metrics every single tick ## [10.0.1] - 2017-07-06 ### Added - Metrics should be initialized to 0 when there are no labels ## [10.0.0] - 2017-07-04 ### Breaking - Print deprecation warning when metrics are constructed using non-objects - Print deprecation warning when `collectDefaultMetrics` is called with a number ### Added - Ability to set default labels by registry - Allow passing in `registry` as second argument to `collectDefaultMetrics` to use that instead of the default registry ### Changed - Convert code base to ES2015 code (node 4) - add engines field to package.json - Use object shorthand - Remove `util-extend` in favor of `Object.assign` - Arrow functions over binding or putting `this` in a variable - Use template strings - `prototype` -> `class` ## [9.1.1] - 2017-06-17 ### Changed - Don't set timestamps for metrics that are never updated ## [9.1.0] - 2017-06-07 ### Added - Ability to merge registries ### Changed - Correct typedefs for object constructor of metrics ## [9.0.0] - 2017-05-06 ### Added - Support for multiple registers - Support for object literals in metric constructors - Timestamp support ### Changed - Collection of default metrics is now disabled by default. Start collection by running `collectDefaultMetrics()`. ### Deprecated - Creating metrics with one argument per parameter - use object literals instead. [unreleased]: https://github.com/siimon/prom-client/compare/v10.2.2...HEAD [10.2.2]: https://github.com/siimon/prom-client/compare/v10.2.1...v10.2.2 [10.2.1]: https://github.com/siimon/prom-client/compare/v10.2.0...v10.2.1 [10.2.0]: https://github.com/siimon/prom-client/compare/v10.1.1...v10.2.0 [10.1.1]: https://github.com/siimon/prom-client/compare/v10.1.0...v10.1.1 [10.1.0]: https://github.com/siimon/prom-client/compare/v10.0.4...v10.1.0 [10.0.4]: https://github.com/siimon/prom-client/compare/v10.0.3...v10.0.4 [10.0.3]: https://github.com/siimon/prom-client/compare/v10.0.2...v10.0.3 [10.0.2]: https://github.com/siimon/prom-client/compare/v10.0.1...v10.0.2 [10.0.1]: https://github.com/siimon/prom-client/compare/v10.0.0...v10.0.1 [10.0.0]: https://github.com/siimon/prom-client/compare/v9.1.1...v10.0.0 [9.1.1]: https://github.com/siimon/prom-client/compare/v9.1.0...v9.1.1 [9.1.0]: https://github.com/siimon/prom-client/compare/v9.0.0...v9.1.0 [9.0.0]: https://github.com/siimon/prom-client/commit/1ef835f908e1a5032f228bbc754479fe7ccf5201 prom-client-14.1.0/LICENSE000066400000000000000000000261151430114771000150360ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2015 Simon Nyberg Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. prom-client-14.1.0/README.md000066400000000000000000000404151430114771000153070ustar00rootroot00000000000000# Prometheus client for node.js [![Actions Status](https://github.com/siimon/prom-client/workflows/Node.js%20CI/badge.svg?branch=master)](https://github.com/siimon/prom-client/actions) A prometheus client for Node.js that supports histogram, summaries, gauges and counters. ## Usage See example folder for a sample usage. The library does not bundle any web framework. To expose the metrics, respond to Prometheus's scrape requests with the result of `await registry.metrics()`. ### Usage with Node.js's `cluster` module Node.js's `cluster` module spawns multiple processes and hands off socket connections to those workers. Returning metrics from a worker's local registry will only reveal that individual worker's metrics, which is generally undesirable. To solve this, you can aggregate all of the workers' metrics in the master process. See `example/cluster.js` for an example. Default metrics use sensible aggregation methods. (Note, however, that the event loop lag mean and percentiles are averaged, which is not perfectly accurate.) Custom metrics are summed across workers by default. To use a different aggregation method, set the `aggregator` property in the metric config to one of 'sum', 'first', 'min', 'max', 'average' or 'omit'. (See `lib/metrics/version.js` for an example.) If you need to expose metrics about an individual worker, you can include a value that is unique to the worker (such as the worker ID or process ID) in a label. (See `example/server.js` for an example using `worker_${cluster.worker.id}` as a label value.) Metrics are aggregated from the global registry by default. To use a different registry, call `client.AggregatorRegistry.setRegistries(registryOrArrayOfRegistries)` from the worker processes. ## API ### Default metrics There are some default metrics recommended by Prometheus [itself](https://prometheus.io/docs/instrumenting/writing_clientlibs/#standard-and-runtime-collectors). To collect these, call `collectDefaultMetrics`. In addition, some Node.js-specific metrics are included, such as event loop lag, active handles, GC and Node.js version. See [lib/metrics](lib/metrics) for a list of all metrics. NOTE: Some of the metrics, concerning File Descriptors and Memory, are only available on Linux. `collectDefaultMetrics` optionally accepts a config object with following entries: - `prefix` an optional prefix for metric names. Default: no prefix. - `register` to which metrics should be registered. Default: the global default registry. - `gcDurationBuckets` with custom buckets for GC duration histogram. Default buckets of GC duration histogram are `[0.001, 0.01, 0.1, 1, 2, 5]` (in seconds). - `eventLoopMonitoringPrecision` with sampling rate in milliseconds. Must be greater than zero. Default: 10. To register metrics to another registry, pass it in as `register`: ```js const client = require('prom-client'); const collectDefaultMetrics = client.collectDefaultMetrics; const Registry = client.Registry; const register = new Registry(); collectDefaultMetrics({ register }); ``` To use custom buckets for GC duration histogram, pass it in as `gcDurationBuckets`: ```js const client = require('prom-client'); const collectDefaultMetrics = client.collectDefaultMetrics; collectDefaultMetrics({ gcDurationBuckets: [0.1, 0.2, 0.3] }); ``` To prefix metric names with your own arbitrary string, pass in a `prefix`: ```js const client = require('prom-client'); const collectDefaultMetrics = client.collectDefaultMetrics; const prefix = 'my_application_'; collectDefaultMetrics({ prefix }); ``` To apply generic labels to all default metrics, pass an object to the `labels` property (useful if you're working in a clustered environment): ```js const client = require('prom-client'); const collectDefaultMetrics = client.collectDefaultMetrics; collectDefaultMetrics({ labels: { NODE_APP_INSTANCE: process.env.NODE_APP_INSTANCE }, }); ``` You can get the full list of metrics by inspecting `client.collectDefaultMetrics.metricsList`. Default metrics are collected on scrape of metrics endpoint, not on an interval. ```js const client = require('prom-client'); const collectDefaultMetrics = client.collectDefaultMetrics; collectDefaultMetrics(); ``` ### Custom Metrics All metric types have two mandatory parameters: `name` and `help`. Refer to for guidance on naming metrics. For metrics based on point-in-time observations (e.g. current memory usage, as opposed to HTTP request durations observed continuously in a histogram), you should provide a `collect()` function, which will be invoked when Prometheus scrapes your metrics endpoint. `collect()` can either be synchronous or return a promise. See **Gauge** below for an example. (Note that you should not update metric values in a `setInterval` callback; do so in this `collect` function instead.) See [**Labels**](#labels) for information on how to configure labels for all metric types. #### Counter Counters go up, and reset when the process restarts. ```js const client = require('prom-client'); const counter = new client.Counter({ name: 'metric_name', help: 'metric_help', }); counter.inc(); // Increment by 1 counter.inc(10); // Increment by 10 ``` #### Gauge Gauges are similar to Counters but a Gauge's value can be decreased. ```js const client = require('prom-client'); const gauge = new client.Gauge({ name: 'metric_name', help: 'metric_help' }); gauge.set(10); // Set to 10 gauge.inc(); // Increment 1 gauge.inc(10); // Increment 10 gauge.dec(); // Decrement by 1 gauge.dec(10); // Decrement by 10 ``` ##### Configuration If the gauge is used for a point-in-time observation, you should provide a `collect` function: ```js const client = require('prom-client'); new client.Gauge({ name: 'metric_name', help: 'metric_help', collect() { // Invoked when the registry collects its metrics' values. // This can be synchronous or it can return a promise/be an async function. this.set(/* the current value */); }, }); ``` ```js // Async version: const client = require('prom-client'); new client.Gauge({ name: 'metric_name', help: 'metric_help', async collect() { // Invoked when the registry collects its metrics' values. const currentValue = await somethingAsync(); this.set(currentValue); }, }); ``` Note that you should not use arrow functions for `collect` because arrow functions will not have the correct value for `this`. ##### Utility Functions ```js // Set value to current time: gauge.setToCurrentTime(); // Record durations: const end = gauge.startTimer(); http.get('url', res => { end(); }); ``` #### Histogram Histograms track sizes and frequency of events. ##### Configuration The defaults buckets are intended to cover usual web/RPC requests, but they can be overridden. (See also [**Bucket Generators**](#bucket-generators).) ```js const client = require('prom-client'); new client.Histogram({ name: 'metric_name', help: 'metric_help', buckets: [0.1, 5, 15, 50, 100, 500], }); ``` ##### Examples ```js const client = require('prom-client'); const histogram = new client.Histogram({ name: 'metric_name', help: 'metric_help', }); histogram.observe(10); // Observe value in histogram ``` ##### Utility Methods ```js const end = histogram.startTimer(); xhrRequest(function (err, res) { const seconds = end(); // Observes and returns the value to xhrRequests duration in seconds }); ``` #### Summary Summaries calculate percentiles of observed values. ##### Configuration The default percentiles are: 0.01, 0.05, 0.5, 0.9, 0.95, 0.99, 0.999. But they can be overridden by specifying a `percentiles` array. (See also [**Bucket Generators**](#bucket-generators).) ```js const client = require('prom-client'); new client.Summary({ name: 'metric_name', help: 'metric_help', percentiles: [0.01, 0.1, 0.9, 0.99], }); ``` To enable the sliding window functionality for summaries you need to add `maxAgeSeconds` and `ageBuckets` to the config like this: ```js const client = require('prom-client'); new client.Summary({ name: 'metric_name', help: 'metric_help', maxAgeSeconds: 600, ageBuckets: 5, }); ``` The `maxAgeSeconds` will tell how old a bucket can be before it is reset and `ageBuckets` configures how many buckets we will have in our sliding window for the summary. ##### Examples ```js const client = require('prom-client'); const summary = new client.Summary({ name: 'metric_name', help: 'metric_help', }); summary.observe(10); ``` ##### Utility Methods ```js const end = summary.startTimer(); xhrRequest(function (err, res) { end(); // Observes the value to xhrRequests duration in seconds }); ``` ### Labels All metrics can take a `labelNames` property in the configuration object. All label names that the metric support needs to be declared here. There are two ways to add values to the labels: ```js const client = require('prom-client'); const gauge = new client.Gauge({ name: 'metric_name', help: 'metric_help', labelNames: ['method', 'statusCode'], }); // 1st version: Set value to 100 with "method" set to "GET" and "statusCode" to "200" gauge.set({ method: 'GET', statusCode: '200' }, 100); // 2nd version: Same effect as above gauge.labels({ method: 'GET', statusCode: '200' }).set(100); // 3rd version: And again the same effect as above gauge.labels('GET', '200').set(100); ``` It is also possible to use timers with labels, both before and after the timer is created: ```js const end = startTimer({ method: 'GET' }); // Set method to GET, we don't know statusCode yet xhrRequest(function (err, res) { if (err) { end({ statusCode: '500' }); // Sets value to xhrRequest duration in seconds with statusCode 500 } else { end({ statusCode: '200' }); // Sets value to xhrRequest duration in seconds with statusCode 200 } }); ``` #### Zeroing metrics with Labels Metrics with labels can not be exported before they have been observed at least once since the possible label values are not known before they're observed. For histograms, this can be solved by explicitly zeroing all expected label values: ```js const histogram = new client.Histogram({ name: 'metric_name', help: 'metric_help', buckets: [0.1, 5, 15, 50, 100, 500], labels: ['method'], }); histogram.zero({ method: 'GET' }); histogram.zero({ method: 'POST' }); ``` #### Strongly typed Labels Typescript can also enforce label names using `as const` ```typescript import * as client from 'prom-client'; const gauge = new client.Counter({ name: 'metric_name', help: 'metric_help', // add `as const` here to enforce label names labelNames: ['method'] as const, }); // Ok gauge.inc({ method: 1 }); // this is an error since `'methods'` is not a valid `labelName` // @ts-expect-error gauge.inc({ methods: 1 }); ``` #### Default Labels (segmented by registry) Static labels may be applied to every metric emitted by a registry: ```js const client = require('prom-client'); const defaultLabels = { serviceName: 'api-v1' }; client.register.setDefaultLabels(defaultLabels); ``` This will output metrics in the following way: ``` # HELP process_resident_memory_bytes Resident memory size in bytes. # TYPE process_resident_memory_bytes gauge process_resident_memory_bytes{serviceName="api-v1"} 33853440 1498510040309 ``` Default labels will be overridden if there is a name conflict. `register.clear()` will clear default labels. ### Multiple registries By default, metrics are automatically registered to the global registry (located at `require('prom-client').register`). You can prevent this by specifying `registers: []` in the metric constructor configuration. Using non-global registries requires creating a Registry instance and passing it inside `registers` in the metric configuration object. Alternatively you can pass an empty `registers` array and register it manually. Registry has a `merge` function that enables you to expose multiple registries on the same endpoint. If the same metric name exists in both registries, an error will be thrown. ```js const client = require('prom-client'); const registry = new client.Registry(); const counter = new client.Counter({ name: 'metric_name', help: 'metric_help', registers: [registry], // specify a non-default registry }); const histogram = new client.Histogram({ name: 'metric_name', help: 'metric_help', registers: [], // don't automatically register this metric }); registry.registerMetric(histogram); // register metric manually counter.inc(); const mergedRegistries = client.Registry.merge([registry, client.register]); ``` If you want to use multiple or non-default registries with the Node.js `cluster` module, you will need to set the registry/registries to aggregate from: ```js const AggregatorRegistry = client.AggregatorRegistry; AggregatorRegistry.setRegistries(registry); // or for multiple registries: AggregatorRegistry.setRegistries([registry1, registry2]); ``` ### Register You can get all metrics by running `await register.metrics()`, which will return a string in the Prometheus exposition format. #### Getting a single metric value in Prometheus exposition format If you need to output a single metric in the Prometheus exposition format, you can use `await register.getSingleMetricAsString(*name of metric*)`, which will return a string for Prometheus to consume. #### Getting a single metric If you need to get a reference to a previously registered metric, you can use `await register.getSingleMetric(*name of metric*)`. #### Removing metrics You can remove all metrics by calling `register.clear()`. You can also remove a single metric by calling `register.removeSingleMetric(*name of metric*)`. #### Resetting metrics If you need to reset all metrics, you can use `register.resetMetrics()`. The metrics will remain present in the register and can be used without the need to instantiate them again, like you would need to do after `register.clear()`. #### Cluster metrics You can get aggregated metrics for all workers in a Node.js cluster with `await register.clusterMetrics()`. This method returns a promise that resolves with a metrics string suitable for Prometheus to consume. ```js const metrics = await register.clusterMetrics(); // - or - register .clusterMetrics() .then(metrics => { /* ... */ }) .catch(err => { /* ... */ }); ``` ### Pushgateway It is possible to push metrics via a [Pushgateway](https://github.com/prometheus/pushgateway). ```js const client = require('prom-client'); let gateway = new client.Pushgateway('http://127.0.0.1:9091'); gateway.pushAdd({ jobName: 'test' }) .then({resp, body} => { /* ... */ }) .catch(err => { /* ... */ })); //Add metric and overwrite old ones gateway.push({ jobName: 'test' }) .then({resp, body} => { /* ... */ }) .catch(err => { /* ... */ })); //Overwrite all metrics (use PUT) gateway.delete({ jobName: 'test' }) .then({resp, body} => { /* ... */ }) .catch(err => { /* ... */ })); //Delete all metrics for jobName //All gateway requests can have groupings on it gateway.pushAdd({ jobName: 'test', groupings: { key: 'value' } }) .then({resp, body} => { /* ... */ }) .catch(err => { /* ... */ })); // It's possible to extend the Pushgateway with request options from nodes core // http/https library. In particular, you might want to provide an agent so that // TCP connections are reused. gateway = new client.Pushgateway('http://127.0.0.1:9091', { timeout: 5000, //Set the request timeout to 5000ms agent: new http.Agent({ keepAlive: true, keepAliveMsec: 10000, maxSockets: 5, }), }); ``` ### Bucket Generators For convenience, there are two bucket generator functions - linear and exponential. ```js const client = require('prom-client'); new client.Histogram({ name: 'metric_name', help: 'metric_help', buckets: client.linearBuckets(0, 10, 20), //Create 20 buckets, starting on 0 and a width of 10 }); new client.Histogram({ name: 'metric_name', help: 'metric_help', buckets: client.exponentialBuckets(1, 2, 5), //Create 5 buckets, starting on 1 and with a factor of 2 }); ``` The content-type prometheus expects is also exported as a constant, both on the `register` and from the main file of this project, called `contentType`. ### Garbage Collection Metrics To avoid native dependencies in this module, GC statistics for bytes reclaimed in each GC sweep are kept in a separate module: https://github.com/SimenB/node-prometheus-gc-stats. (Note that that metric may no longer be accurate now that v8 uses parallel garbage collection.) prom-client-14.1.0/benchmarks/000077500000000000000000000000001430114771000161415ustar00rootroot00000000000000prom-client-14.1.0/benchmarks/gauge.js000066400000000000000000000014631430114771000175730ustar00rootroot00000000000000'use strict'; const { getLabelNames, labelCombinationFactory } = require('./utils/labels'); module.exports = setupGaugeSuite; function setupGaugeSuite(suite) { suite.add( 'inc', labelCombinationFactory([], (client, { Gauge }, labels) => Gauge.inc(labels, 1), ), { teardown, setup: setup(0) }, ); suite.add( 'inc with labels', labelCombinationFactory([8, 8], (client, { Gauge }, labels) => Gauge.inc(labels, 1), ), { teardown, setup: setup(2) }, ); } function setup(labelCount) { return client => { const registry = new client.Registry(); const Gauge = new client.Gauge({ name: 'Gauge', help: 'Gauge', labelNames: getLabelNames(labelCount), registers: [registry], }); return { registry, Gauge }; }; } function teardown(client, { registry }) { registry.clear(); } prom-client-14.1.0/benchmarks/histogram.js000066400000000000000000000027071430114771000205020ustar00rootroot00000000000000'use strict'; const { getLabelNames, labelCombinationFactory } = require('./utils/labels'); module.exports = setupHistogramSuite; function setupHistogramSuite(suite) { suite.add( 'observe#1 with 64', labelCombinationFactory([64], (client, { histogram }, labels) => histogram.observe(labels, 1), ), { teardown, setup: setup(1) }, ); suite.add( 'observe#2 with 8', labelCombinationFactory([8, 8], (client, { histogram }, labels) => histogram.observe(labels, 1), ), { teardown, setup: setup(2) }, ); suite.add( 'observe#2 with 4 and 2 with 2', labelCombinationFactory([4, 4, 2, 2], (client, { histogram }, labels) => histogram.observe(labels, 1), ), { teardown, setup: setup(4) }, ); suite.add( 'observe#2 with 2 and 2 with 4', labelCombinationFactory([2, 2, 4, 4], (client, { histogram }, labels) => histogram.observe(labels, 1), ), { teardown, setup: setup(4) }, ); suite.add( 'observe#6 with 2', labelCombinationFactory( [2, 2, 2, 2, 2, 2], (client, { histogram }, labels) => histogram.observe(labels, 1), ), { teardown, setup: setup(6) }, ); } function setup(labelCount) { return client => { const registry = new client.Registry(); const histogram = new client.Histogram({ name: 'histogram', help: 'histogram', labelNames: getLabelNames(labelCount), registers: [registry], }); return { registry, histogram }; }; } function teardown(client, { registry }) { registry.clear(); } prom-client-14.1.0/benchmarks/index.js000066400000000000000000000010541430114771000176060ustar00rootroot00000000000000'use strict'; const createRegressionBenchmark = require('@clevernature/benchmark-regression'); const currentClient = require('..'); const benchmarks = createRegressionBenchmark(currentClient, [ 'prom-client@latest', ]); benchmarks.suite('registry', require('./registry')); benchmarks.suite('histogram', require('./histogram')); benchmarks.suite('gauge', require('./gauge')); benchmarks.suite('summary', require('./summary')); benchmarks.run().catch(err => { console.error(err.stack); // eslint-disable-next-line no-process-exit process.exit(1); }); prom-client-14.1.0/benchmarks/registry.js000066400000000000000000000022671430114771000203560ustar00rootroot00000000000000'use strict'; const { getLabelNames, getLabelCombinations } = require('./utils/labels'); module.exports = setupRegistrySuite; function setupRegistrySuite(suite) { const labelSetups = [ { name: '1 with 64', counts: [64] }, { name: '2 with 8', counts: [8, 8] }, { name: '2 with 4 and 2 with 2', counts: [4, 4, 2, 2] }, { name: '2 with 2 and 2 with 4', counts: [2, 2, 4, 4] }, { name: '6 with 2', counts: [2, 2, 2, 2, 2, 2] }, ]; labelSetups.forEach(({ name, counts }) => { suite.add( `getMetricsAsJSON#${name}`, (client, registry) => registry.getMetricsAsJSON(), { setup: setup(counts) }, ); }); labelSetups.forEach(({ name, counts }) => { suite.add(`metrics#${name}`, (client, registry) => registry.metrics(), { setup: setup(counts), }); }); } function setup(labelCounts) { return client => { const registry = new client.Registry(); const histogram = new client.Histogram({ name: 'histogram', help: 'histogram', labelNames: getLabelNames(labelCounts.length), registers: [registry], }); const labelCombinations = getLabelCombinations(labelCounts); labelCombinations.forEach(labels => histogram.observe(labels, 1)); return registry; }; } prom-client-14.1.0/benchmarks/summary.js000066400000000000000000000026411430114771000201770ustar00rootroot00000000000000'use strict'; const { getLabelNames, labelCombinationFactory } = require('./utils/labels'); module.exports = setupSummarySuite; function setupSummarySuite(suite) { suite.add( 'observe#1 with 64', labelCombinationFactory([64], (client, { summary }, labels) => summary.observe(labels, 1), ), { teardown, setup: setup(1) }, ); suite.add( 'observe#2 with 8', labelCombinationFactory([8, 8], (client, { summary }, labels) => summary.observe(labels, 1), ), { teardown, setup: setup(2) }, ); suite.add( 'observe#2 with 4 and 2 with 2', labelCombinationFactory([4, 4, 2, 2], (client, { summary }, labels) => summary.observe(labels, 1), ), { teardown, setup: setup(4) }, ); suite.add( 'observe#2 with 2 and 2 with 4', labelCombinationFactory([2, 2, 4, 4], (client, { summary }, labels) => summary.observe(labels, 1), ), { teardown, setup: setup(4) }, ); suite.add( 'observe#6 with 2', labelCombinationFactory([2, 2, 2, 2, 2, 2], (client, { summary }, labels) => summary.observe(labels, 1), ), { teardown, setup: setup(6) }, ); } function setup(labelCount) { return client => { const registry = new client.Registry(); const summary = new client.Summary({ name: 'summary', help: 'summary', labelNames: getLabelNames(labelCount), registers: [registry], }); return { registry, summary }; }; } function teardown(client, { registry }) { registry.clear(); } prom-client-14.1.0/benchmarks/utils/000077500000000000000000000000001430114771000173015ustar00rootroot00000000000000prom-client-14.1.0/benchmarks/utils/labels.js000066400000000000000000000021221430114771000210760ustar00rootroot00000000000000'use strict'; const letters = 'abcdefghijklmnopqrstuvwxyz'.split(''); module.exports = { getLabelNames, getLabelCombinations, labelCombinationFactory, }; function getLabelNames(count) { return letters.slice(0, count); } const flatten = (a, b) => [].concat(...a.map(c => b.map(d => [].concat(c, d)))); const cartesianProduct = (a, b, ...c) => b ? cartesianProduct(flatten(a, b), ...c) : a; const times = a => Array.from(Array(a)).map((_, x) => x); function getLabelCombinations(labelValues) { const labelNames = getLabelNames(labelValues.length); labelValues = labelValues.length > 1 ? labelValues : labelValues.concat(1); const labelValuesArray = labelValues.map(times); const labelValueCombinations = cartesianProduct(...labelValuesArray); return labelValueCombinations.map(values => labelNames.reduce((acc, label, i) => { acc[label] = values[i]; return acc; }, {}), ); } function labelCombinationFactory(labelValues, fn) { const labelCombinations = getLabelCombinations(labelValues); return (client, ctx) => labelCombinations.forEach(labels => fn(client, ctx, labels)); } prom-client-14.1.0/example/000077500000000000000000000000001430114771000154575ustar00rootroot00000000000000prom-client-14.1.0/example/cluster.js000066400000000000000000000014011430114771000174720ustar00rootroot00000000000000'use strict'; const cluster = require('cluster'); const express = require('express'); const metricsServer = express(); const AggregatorRegistry = require('../').AggregatorRegistry; const aggregatorRegistry = new AggregatorRegistry(); if (cluster.isMaster) { for (let i = 0; i < 4; i++) { cluster.fork(); } metricsServer.get('/cluster_metrics', async (req, res) => { try { const metrics = await aggregatorRegistry.clusterMetrics(); res.set('Content-Type', aggregatorRegistry.contentType); res.send(metrics); } catch (ex) { res.statusCode = 500; res.send(ex.message); } }); metricsServer.listen(3001); console.log( 'Cluster metrics server listening to 3001, metrics exposed on /cluster_metrics', ); } else { require('./server.js'); } prom-client-14.1.0/example/counter.js000066400000000000000000000026331430114771000175000ustar00rootroot00000000000000'use strict'; // Counter // Single Label // Multiple Values const { Counter, register } = require('..'); async function main() { const c = new Counter({ name: 'test_counter', help: 'Example of a counter', labelNames: ['code'], }); c.inc({ code: 200 }); console.log(await register.metrics()); /* # HELP test_counter Example of a counter # TYPE test_counter counter test_counter{code="200"} 1 */ c.inc({ code: 200 }); console.log(await register.metrics()); /* # HELP test_counter Example of a counter # TYPE test_counter counter test_counter{code="200"} 2 */ c.inc(); console.log(await register.metrics()); /* # HELP test_counter Example of a counter # TYPE test_counter counter test_counter{code="200"} 2 test_counter 1 */ c.reset(); console.log(await register.metrics()); /* # HELP test_counter Example of a counter # TYPE test_counter counter */ c.inc(15); console.log(await register.metrics()); /* # HELP test_counter Example of a counter # TYPE test_counter counter test_counter 15 */ c.inc({ code: 200 }, 12); console.log(await register.metrics()); /* # HELP test_counter Example of a counter # TYPE test_counter counter test_counter 15 test_counter{code="200"} 12 */ c.labels('200').inc(12); console.log(await register.metrics()); /* # HELP test_counter Example of a counter # TYPE test_counter counter test_counter 15 test_counter{code="200"} 24 */ } main(); prom-client-14.1.0/example/default-metrics.js000066400000000000000000000141161430114771000211100ustar00rootroot00000000000000'use strict'; const { collectDefaultMetrics, register } = require('..'); collectDefaultMetrics({ timeout: 10000, gcDurationBuckets: [0.001, 0.01, 0.1, 1, 2, 5], // These are the default buckets. }); register.metrics().then(str => console.log(str)); /* Output from metrics(): # HELP process_cpu_user_seconds_total Total user CPU time spent in seconds. # TYPE process_cpu_user_seconds_total counter process_cpu_user_seconds_total 0.004261 # HELP process_cpu_system_seconds_total Total system CPU time spent in seconds. # TYPE process_cpu_system_seconds_total counter process_cpu_system_seconds_total 0.000547 # HELP process_cpu_seconds_total Total user and system CPU time spent in seconds. # TYPE process_cpu_seconds_total counter process_cpu_seconds_total 0.004808 # HELP process_start_time_seconds Start time of the process since unix epoch in seconds. # TYPE process_start_time_seconds gauge process_start_time_seconds 1585092233 # HELP process_resident_memory_bytes Resident memory size in bytes. # TYPE process_resident_memory_bytes gauge process_resident_memory_bytes 37072896 # HELP process_virtual_memory_bytes Virtual memory size in bytes. # TYPE process_virtual_memory_bytes gauge process_virtual_memory_bytes 576438272 # HELP process_heap_bytes Process heap size in bytes. # TYPE process_heap_bytes gauge process_heap_bytes 50720768 # HELP process_open_fds Number of open file descriptors. # TYPE process_open_fds gauge process_open_fds 98 # HELP process_max_fds Maximum number of open file descriptors. # TYPE process_max_fds gauge process_max_fds 1048576 # HELP nodejs_eventloop_lag_seconds Lag of event loop in seconds. # TYPE nodejs_eventloop_lag_seconds gauge nodejs_eventloop_lag_seconds 0 # HELP nodejs_eventloop_lag_min_seconds The minimum recorded event loop delay. # TYPE nodejs_eventloop_lag_min_seconds gauge nodejs_eventloop_lag_min_seconds 9223372036.854776 # HELP nodejs_eventloop_lag_max_seconds The maximum recorded event loop delay. # TYPE nodejs_eventloop_lag_max_seconds gauge nodejs_eventloop_lag_max_seconds 0 # HELP nodejs_eventloop_lag_mean_seconds The mean of the recorded event loop delays. # TYPE nodejs_eventloop_lag_mean_seconds gauge nodejs_eventloop_lag_mean_seconds Nan # HELP nodejs_eventloop_lag_stddev_seconds The standard deviation of the recorded event loop delays. # TYPE nodejs_eventloop_lag_stddev_seconds gauge nodejs_eventloop_lag_stddev_seconds Nan # HELP nodejs_eventloop_lag_p50_seconds The 50th percentile of the recorded event loop delays. # TYPE nodejs_eventloop_lag_p50_seconds gauge nodejs_eventloop_lag_p50_seconds 0 # HELP nodejs_eventloop_lag_p90_seconds The 90th percentile of the recorded event loop delays. # TYPE nodejs_eventloop_lag_p90_seconds gauge nodejs_eventloop_lag_p90_seconds 0 # HELP nodejs_eventloop_lag_p99_seconds The 99th percentile of the recorded event loop delays. # TYPE nodejs_eventloop_lag_p99_seconds gauge nodejs_eventloop_lag_p99_seconds 0 # HELP nodejs_active_handles Number of active libuv handles grouped by handle type. Every handle type is C++ class name. # TYPE nodejs_active_handles gauge # HELP nodejs_active_handles_total Total number of active handles. # TYPE nodejs_active_handles_total gauge nodejs_active_handles_total 0 # HELP nodejs_active_requests Number of active libuv requests grouped by request type. Every request type is C++ class name. # TYPE nodejs_active_requests gauge # HELP nodejs_active_requests_total Total number of active requests. # TYPE nodejs_active_requests_total gauge nodejs_active_requests_total 0 # HELP nodejs_heap_size_total_bytes Process heap size from Node.js in bytes. # TYPE nodejs_heap_size_total_bytes gauge nodejs_heap_size_total_bytes 5746688 # HELP nodejs_heap_size_used_bytes Process heap size used from Node.js in bytes. # TYPE nodejs_heap_size_used_bytes gauge nodejs_heap_size_used_bytes 3870560 # HELP nodejs_external_memory_bytes Node.js external memory size in bytes. # TYPE nodejs_external_memory_bytes gauge nodejs_external_memory_bytes 1221803 # HELP nodejs_heap_space_size_total_bytes Process heap space size total from Node.js in bytes. # TYPE nodejs_heap_space_size_total_bytes gauge nodejs_heap_space_size_total_bytes{space="read_only"} 262144 nodejs_heap_space_size_total_bytes{space="new"} 2097152 nodejs_heap_space_size_total_bytes{space="old"} 2244608 nodejs_heap_space_size_total_bytes{space="code"} 430080 nodejs_heap_space_size_total_bytes{space="map"} 528384 nodejs_heap_space_size_total_bytes{space="large_object"} 135168 nodejs_heap_space_size_total_bytes{space="code_large_object"} 49152 nodejs_heap_space_size_total_bytes{space="new_large_object"} 0 # HELP nodejs_heap_space_size_used_bytes Process heap space size used from Node.js in bytes. # TYPE nodejs_heap_space_size_used_bytes gauge nodejs_heap_space_size_used_bytes{space="read_only"} 32808 nodejs_heap_space_size_used_bytes{space="new"} 955440 nodejs_heap_space_size_used_bytes{space="old"} 2231120 nodejs_heap_space_size_used_bytes{space="code"} 165472 nodejs_heap_space_size_used_bytes{space="map"} 353760 nodejs_heap_space_size_used_bytes{space="large_object"} 131112 nodejs_heap_space_size_used_bytes{space="code_large_object"} 2784 nodejs_heap_space_size_used_bytes{space="new_large_object"} 0 # HELP nodejs_heap_space_size_available_bytes Process heap space size available from Node.js in bytes. # TYPE nodejs_heap_space_size_available_bytes gauge nodejs_heap_space_size_available_bytes{space="read_only"} 0 nodejs_heap_space_size_available_bytes{space="new"} 92016 nodejs_heap_space_size_available_bytes{space="old"} 3608 nodejs_heap_space_size_available_bytes{space="code"} 0 nodejs_heap_space_size_available_bytes{space="map"} 0 nodejs_heap_space_size_available_bytes{space="large_object"} 0 nodejs_heap_space_size_available_bytes{space="code_large_object"} 0 nodejs_heap_space_size_available_bytes{space="new_large_object"} 1047456 # HELP nodejs_version_info Node.js version info. # TYPE nodejs_version_info gauge nodejs_version_info{version="v12.16.1",major="12",minor="16",patch="1"} 1 # HELP nodejs_gc_duration_seconds Garbage collection duration by kind, one of major, minor, incremental or weakcb. # TYPE nodejs_gc_duration_seconds histogram */ prom-client-14.1.0/example/gauge.js000066400000000000000000000020651430114771000171100ustar00rootroot00000000000000'use strict'; // Gauge // Single Label // Multiple Values const { Gauge, register } = require('..'); async function main() { const g = new Gauge({ name: 'test_gauge', help: 'Example of a gauge', labelNames: ['code'], }); g.set({ code: 200 }, 5); console.log(await register.metrics()); /* # HELP test_gauge Example of a gauge # TYPE test_gauge gauge test_gauge{code="200"} 5 */ g.set(15); console.log(await register.metrics()); /* # HELP test_gauge Example of a gauge # TYPE test_gauge gauge test_gauge{code="200"} 5 test_gauge 15 */ g.labels('200').inc(); console.log(await register.metrics()); /* # HELP test_gauge Example of a gauge # TYPE test_gauge gauge test_gauge{code="200"} 6 test_gauge 15 */ g.inc(); console.log(await register.metrics()); /* # HELP test_gauge Example of a gauge # TYPE test_gauge gauge test_gauge{code="200"} 6 test_gauge 16 */ g.set(22); console.log(await register.metrics()); /* # HELP test_gauge Example of a gauge # TYPE test_gauge gauge test_gauge{code="200"} 6 test_gauge 22 */ } main(); prom-client-14.1.0/example/histogram-1.js000066400000000000000000000021001430114771000201410ustar00rootroot00000000000000'use strict'; // Histogram // Single Label // Single Value const { register, Histogram } = require('..'); const h = new Histogram({ name: 'test_histogram', help: 'Example of a histogram', labelNames: ['code'], }); h.labels('200').observe(0.4); h.labels('200').observe(0.6); h.observe({ code: '200' }, 0.4); register.metrics().then(str => console.log(str)); /* Output from metrics(): # HELP test_histogram Example of a histogram # TYPE test_histogram histogram test_histogram_bucket{le="0.005",code="200"} 0 test_histogram_bucket{le="0.01",code="200"} 0 test_histogram_bucket{le="0.025",code="200"} 0 test_histogram_bucket{le="0.05",code="200"} 0 test_histogram_bucket{le="0.1",code="200"} 0 test_histogram_bucket{le="0.25",code="200"} 0 test_histogram_bucket{le="0.5",code="200"} 2 test_histogram_bucket{le="1",code="200"} 3 test_histogram_bucket{le="2.5",code="200"} 3 test_histogram_bucket{le="5",code="200"} 3 test_histogram_bucket{le="10",code="200"} 3 test_histogram_bucket{le="+Inf",code="200"} 3 test_histogram_sum{code="200"} 1.4 test_histogram_count{code="200"} 3 */ prom-client-14.1.0/example/histogram-2.js000066400000000000000000000032501430114771000201510ustar00rootroot00000000000000'use strict'; // Histogram // Single Label // Multiple Values const { register, Histogram } = require('..'); const h = new Histogram({ name: 'test_histogram', help: 'Example of a histogram', labelNames: ['code'], }); h.labels('200').observe(0.4); h.labels('300').observe(0.6); h.observe({ code: '200' }, 0.4); register.metrics().then(str => console.log(str)); /* Output from metrics(): # HELP test_histogram Example of a histogram # TYPE test_histogram histogram test_histogram_bucket{le="0.005",code="200"} 0 test_histogram_bucket{le="0.01",code="200"} 0 test_histogram_bucket{le="0.025",code="200"} 0 test_histogram_bucket{le="0.05",code="200"} 0 test_histogram_bucket{le="0.1",code="200"} 0 test_histogram_bucket{le="0.25",code="200"} 0 test_histogram_bucket{le="0.5",code="200"} 2 test_histogram_bucket{le="1",code="200"} 2 test_histogram_bucket{le="2.5",code="200"} 2 test_histogram_bucket{le="5",code="200"} 2 test_histogram_bucket{le="10",code="200"} 2 test_histogram_bucket{le="+Inf",code="200"} 2 test_histogram_sum{code="200"} 0.8 test_histogram_count{code="200"} 2 test_histogram_bucket{le="0.005",code="300"} 0 test_histogram_bucket{le="0.01",code="300"} 0 test_histogram_bucket{le="0.025",code="300"} 0 test_histogram_bucket{le="0.05",code="300"} 0 test_histogram_bucket{le="0.1",code="300"} 0 test_histogram_bucket{le="0.25",code="300"} 0 test_histogram_bucket{le="0.5",code="300"} 0 test_histogram_bucket{le="1",code="300"} 1 test_histogram_bucket{le="2.5",code="300"} 1 test_histogram_bucket{le="5",code="300"} 1 test_histogram_bucket{le="10",code="300"} 1 test_histogram_bucket{le="+Inf",code="300"} 1 test_histogram_sum{code="300"} 0.6 test_histogram_count{code="300"} 1 */ prom-client-14.1.0/example/histogram-3.js000066400000000000000000000024531430114771000201560ustar00rootroot00000000000000'use strict'; // Histogram // Multiple Labels // Single Value per Label const { register, Histogram } = require('..'); const h = new Histogram({ name: 'test_histogram', help: 'Example of a histogram', labelNames: ['code', 'color'], }); h.labels('200', 'blue').observe(0.4); h.labels('200', 'blue').observe(0.6); h.observe({ code: '200', color: 'blue' }, 0.4); register.metrics().then(str => console.log(str)); /* Output from metrics(): # HELP test_histogram Example of a histogram # TYPE test_histogram histogram test_histogram_bucket{le="0.005",code="200",color="blue"} 0 test_histogram_bucket{le="0.01",code="200",color="blue"} 0 test_histogram_bucket{le="0.025",code="200",color="blue"} 0 test_histogram_bucket{le="0.05",code="200",color="blue"} 0 test_histogram_bucket{le="0.1",code="200",color="blue"} 0 test_histogram_bucket{le="0.25",code="200",color="blue"} 0 test_histogram_bucket{le="0.5",code="200",color="blue"} 2 test_histogram_bucket{le="1",code="200",color="blue"} 3 test_histogram_bucket{le="2.5",code="200",color="blue"} 3 test_histogram_bucket{le="5",code="200",color="blue"} 3 test_histogram_bucket{le="10",code="200",color="blue"} 3 test_histogram_bucket{le="+Inf",code="200",color="blue"} 3 test_histogram_sum{code="200",color="blue"} 1.4 test_histogram_count{code="200",color="blue"} 3 */ prom-client-14.1.0/example/histogram-4.js000066400000000000000000000071741430114771000201640ustar00rootroot00000000000000'use strict'; // Histogram // Multiple Labels // Multiple Values per Label const { register, Histogram } = require('..'); const h = new Histogram({ name: 'test_histogram', help: 'Example of a histogram', labelNames: ['code', 'color'], }); h.labels('200', 'blue').observe(0.4); h.labels('300', 'red').observe(0.6); h.labels('300', 'blue').observe(0.4); h.labels('200', 'red').observe(0.6); register.metrics().then(str => console.log(str)); /* Output from metrics(): # HELP test_histogram Example of a histogram # TYPE test_histogram histogram test_histogram_bucket{le="0.005",code="200",color="blue"} 0 test_histogram_bucket{le="0.01",code="200",color="blue"} 0 test_histogram_bucket{le="0.025",code="200",color="blue"} 0 test_histogram_bucket{le="0.05",code="200",color="blue"} 0 test_histogram_bucket{le="0.1",code="200",color="blue"} 0 test_histogram_bucket{le="0.25",code="200",color="blue"} 0 test_histogram_bucket{le="0.5",code="200",color="blue"} 1 test_histogram_bucket{le="1",code="200",color="blue"} 1 test_histogram_bucket{le="2.5",code="200",color="blue"} 1 test_histogram_bucket{le="5",code="200",color="blue"} 1 test_histogram_bucket{le="10",code="200",color="blue"} 1 test_histogram_bucket{le="+Inf",code="200",color="blue"} 1 test_histogram_sum{code="200",color="blue"} 0.4 test_histogram_count{code="200",color="blue"} 1 test_histogram_bucket{le="0.005",code="300",color="red"} 0 test_histogram_bucket{le="0.01",code="300",color="red"} 0 test_histogram_bucket{le="0.025",code="300",color="red"} 0 test_histogram_bucket{le="0.05",code="300",color="red"} 0 test_histogram_bucket{le="0.1",code="300",color="red"} 0 test_histogram_bucket{le="0.25",code="300",color="red"} 0 test_histogram_bucket{le="0.5",code="300",color="red"} 0 test_histogram_bucket{le="1",code="300",color="red"} 1 test_histogram_bucket{le="2.5",code="300",color="red"} 1 test_histogram_bucket{le="5",code="300",color="red"} 1 test_histogram_bucket{le="10",code="300",color="red"} 1 test_histogram_bucket{le="+Inf",code="300",color="red"} 1 test_histogram_sum{code="300",color="red"} 0.6 test_histogram_count{code="300",color="red"} 1 test_histogram_bucket{le="0.005",code="300",color="blue"} 0 test_histogram_bucket{le="0.01",code="300",color="blue"} 0 test_histogram_bucket{le="0.025",code="300",color="blue"} 0 test_histogram_bucket{le="0.05",code="300",color="blue"} 0 test_histogram_bucket{le="0.1",code="300",color="blue"} 0 test_histogram_bucket{le="0.25",code="300",color="blue"} 0 test_histogram_bucket{le="0.5",code="300",color="blue"} 1 test_histogram_bucket{le="1",code="300",color="blue"} 1 test_histogram_bucket{le="2.5",code="300",color="blue"} 1 test_histogram_bucket{le="5",code="300",color="blue"} 1 test_histogram_bucket{le="10",code="300",color="blue"} 1 test_histogram_bucket{le="+Inf",code="300",color="blue"} 1 test_histogram_sum{code="300",color="blue"} 0.4 test_histogram_count{code="300",color="blue"} 1 test_histogram_bucket{le="0.005",code="200",color="red"} 0 test_histogram_bucket{le="0.01",code="200",color="red"} 0 test_histogram_bucket{le="0.025",code="200",color="red"} 0 test_histogram_bucket{le="0.05",code="200",color="red"} 0 test_histogram_bucket{le="0.1",code="200",color="red"} 0 test_histogram_bucket{le="0.25",code="200",color="red"} 0 test_histogram_bucket{le="0.5",code="200",color="red"} 0 test_histogram_bucket{le="1",code="200",color="red"} 1 test_histogram_bucket{le="2.5",code="200",color="red"} 1 test_histogram_bucket{le="5",code="200",color="red"} 1 test_histogram_bucket{le="10",code="200",color="red"} 1 test_histogram_bucket{le="+Inf",code="200",color="red"} 1 test_histogram_sum{code="200",color="red"} 0.6 test_histogram_count{code="200",color="red"} 1 */ prom-client-14.1.0/example/pushgateway.js000066400000000000000000000012201430114771000203510ustar00rootroot00000000000000'use strict'; const client = require('../index'); function run() { const Registry = client.Registry; const register = new Registry(); const gateway = new client.Pushgateway('http://127.0.0.1:9091', [], register); const prefix = 'dummy_prefix_name'; const test = new client.Counter({ name: `${prefix}_test`, help: `${prefix}_test`, registers: [register], }); register.registerMetric(test); test.inc(10); return gateway .push({ jobName: prefix }) .then(({ resp, body }) => { console.log(`Body: ${body}`); console.log(`Response status: ${resp.statusCode}`); }) .catch(err => { console.log(`Error: ${err}`); }); } run(); prom-client-14.1.0/example/server.js000066400000000000000000000045321430114771000173270ustar00rootroot00000000000000'use strict'; const express = require('express'); const cluster = require('cluster'); const server = express(); const register = require('../').register; // Enable collection of default metrics require('../').collectDefaultMetrics({ gcDurationBuckets: [0.001, 0.01, 0.1, 1, 2, 5], // These are the default buckets. }); // Create custom metrics const Histogram = require('../').Histogram; const h = new Histogram({ name: 'test_histogram', help: 'Example of a histogram', labelNames: ['code'], }); const Counter = require('../').Counter; const c = new Counter({ name: 'test_counter', help: 'Example of a counter', labelNames: ['code'], }); new Counter({ name: 'scrape_counter', help: 'Number of scrapes (example of a counter with a collect fn)', collect() { // collect is invoked each time `register.metrics()` is called. this.inc(); }, }); const Gauge = require('../').Gauge; const g = new Gauge({ name: 'test_gauge', help: 'Example of a gauge', labelNames: ['method', 'code'], }); // Set metric values to some random values for demonstration setTimeout(() => { h.labels('200').observe(Math.random()); h.labels('300').observe(Math.random()); }, 10); setInterval(() => { c.inc({ code: 200 }); }, 5000); setInterval(() => { c.inc({ code: 400 }); }, 2000); setInterval(() => { c.inc(); }, 2000); setInterval(() => { g.set({ method: 'get', code: 200 }, Math.random()); g.set(Math.random()); g.labels('post', '300').inc(); }, 100); if (cluster.isWorker) { // Expose some worker-specific metric as an example setInterval(() => { c.inc({ code: `worker_${cluster.worker.id}` }); }, 2000); } const t = []; setInterval(() => { for (let i = 0; i < 100; i++) { t.push(new Date()); } }, 10); setInterval(() => { while (t.length > 0) { t.pop(); } }); // Setup server to Prometheus scrapes: server.get('/metrics', async (req, res) => { try { res.set('Content-Type', register.contentType); res.end(await register.metrics()); } catch (ex) { res.status(500).end(ex); } }); server.get('/metrics/counter', async (req, res) => { try { res.set('Content-Type', register.contentType); res.end(await register.getSingleMetricAsString('test_counter')); } catch (ex) { res.status(500).end(ex); } }); const port = process.env.PORT || 3000; console.log( `Server listening to ${port}, metrics exposed on /metrics endpoint`, ); server.listen(port); prom-client-14.1.0/index.d.ts000066400000000000000000000374661430114771000157450ustar00rootroot00000000000000// Type definitions for prom-client // Definitions by: Simon Nyberg http://twitter.com/siimon_nyberg /** * Container for all registered metrics */ export class Registry { /** * Get string representation for all metrics */ metrics(): Promise; /** * Remove all metrics from the registry */ clear(): void; /** * Reset all metrics in the registry */ resetMetrics(): void; /** * Register metric to register * @param metric Metric to add to register */ registerMetric(metric: Metric): void; /** * Get all metrics as objects */ getMetricsAsJSON(): Promise; /** * Get all metrics as objects */ getMetricsAsArray(): metric[]; /** * Remove a single metric * @param name The name of the metric to remove */ removeSingleMetric(name: string): void; /** * Get a single metric * @param name The name of the metric */ getSingleMetric(name: string): Metric | undefined; /** * Set static labels to every metric emitted by this registry * @param labels of name/value pairs: * { defaultLabel: "value", anotherLabel: "value 2" } */ setDefaultLabels(labels: Object): void; /** * Get a string representation of a single metric by name * @param name The name of the metric */ getSingleMetricAsString(name: string): Promise; /** * Gets the Content-Type of the metrics for use in the response headers. */ contentType: string; /** * Merge registers * @param registers The registers you want to merge together */ static merge(registers: Registry[]): Registry; } export type Collector = () => void; /** * The register that contains all metrics */ export const register: Registry; /** * The Content-Type of the metrics for use in the response headers. */ export const contentType: string; export class AggregatorRegistry extends Registry { /** * Gets aggregated metrics for all workers. * @return {Promise} Promise that resolves with the aggregated * metrics. */ clusterMetrics(): Promise; /** * Creates a new Registry instance from an array of metrics that were * created by `registry.getMetricsAsJSON()`. Metrics are aggregated using * the method specified by their `aggregator` property, or by summation if * `aggregator` is undefined. * @param {Array} metricsArr Array of metrics, each of which created by * `registry.getMetricsAsJSON()`. * @return {Registry} aggregated registry. */ static aggregate(metricsArr: Array): Registry; // TODO Promise? /** * Sets the registry or registries to be aggregated. Call from workers to * use a registry/registries other than the default global registry. * @param {Array|Registry} regs Registry or registries to be * aggregated. * @return {void} */ static setRegistries(regs: Array | Registry): void; } /** * General metric type */ export type Metric = | Counter | Gauge | Summary | Histogram; /** * Aggregation methods, used for aggregating metrics in a Node.js cluster. */ export type Aggregator = 'omit' | 'sum' | 'first' | 'min' | 'max' | 'average'; export enum MetricType { Counter, Gauge, Histogram, Summary, } type CollectFunction = (this: T) => void | Promise; interface metric { name: string; help: string; type: MetricType; aggregator: Aggregator; collect: CollectFunction; } type LabelValues = Partial>; interface MetricConfiguration { name: string; help: string; labelNames?: T[] | readonly T[]; registers?: Registry[]; aggregator?: Aggregator; collect?: CollectFunction; } export interface CounterConfiguration extends MetricConfiguration { collect?: CollectFunction>; } /** * A counter is a cumulative metric that represents a single numerical value that only ever goes up */ export class Counter { /** * @param configuration Configuration when creating a Counter metric. Name and Help is required. */ constructor(configuration: CounterConfiguration); /** * Increment for given labels * @param labels Object with label keys and values * @param value The number to increment with */ inc(labels: LabelValues, value?: number): void; /** * Increment with value * @param value The value to increment with */ inc(value?: number): void; /** * Return the child for given labels * @param values Label values * @return Configured counter with given labels */ labels(...values: string[]): Counter.Internal; /** * Return the child for given labels * @param labels Object with label keys and values * @return Configured counter with given labels */ labels(labels: LabelValues): Counter.Internal; /** * Reset counter values */ reset(): void; /** * Remove metrics for the given label values * @param values Label values */ remove(...values: string[]): void; /** * Remove metrics for the given label values * @param labels Object with label keys and values */ remove(labels: LabelValues): void; } export namespace Counter { interface Internal { /** * Increment with value * @param value The value to increment with */ inc(value?: number): void; } } export interface GaugeConfiguration extends MetricConfiguration { collect?: CollectFunction>; } /** * A gauge is a metric that represents a single numerical value that can arbitrarily go up and down. */ export class Gauge { /** * @param configuration Configuration when creating a Gauge metric. Name and Help is mandatory */ constructor(configuration: GaugeConfiguration); /** * Increment gauge for given labels * @param labels Object with label keys and values * @param value The value to increment with */ inc(labels: LabelValues, value?: number): void; /** * Increment gauge * @param value The value to increment with */ inc(value?: number): void; /** * Decrement gauge * @param labels Object with label keys and values * @param value Value to decrement with */ dec(labels: LabelValues, value?: number): void; /** * Decrement gauge * @param value The value to decrement with */ dec(value?: number): void; /** * Set gauge value for labels * @param labels Object with label keys and values * @param value The value to set */ set(labels: LabelValues, value: number): void; /** * Set gauge value * @param value The value to set */ set(value: number): void; /** * Set gauge value to current epoch time in ms * @param labels Object with label keys and values */ setToCurrentTime(labels?: LabelValues): void; /** * Start a timer. Calling the returned function will set the gauge's value * to the observed duration in seconds. * @param labels Object with label keys and values * @return Function to invoke when timer should be stopped. The value it * returns is the timed duration. */ startTimer(labels?: LabelValues): (labels?: LabelValues) => number; /** * Return the child for given labels * @param values Label values * @return Configured gauge with given labels */ labels(...values: string[]): Gauge.Internal; /** * Return the child for given labels * @param labels Object with label keys and values * @return Configured counter with given labels */ labels(labels: LabelValues): Gauge.Internal; /** * Reset gauge values */ reset(): void; /** * Remove metrics for the given label values * @param values Label values */ remove(...values: string[]): void; /** * Remove metrics for the given label values * @param labels Object with label keys and values */ remove(labels: LabelValues): void; } export namespace Gauge { interface Internal { /** * Increment gauge with value * @param value The value to increment with */ inc(value?: number): void; /** * Decrement with value * @param value The value to decrement with */ dec(value?: number): void; /** * Set gauges value * @param value The value to set */ set(value: number): void; /** * Set gauge value to current epoch time in ms */ setToCurrentTime(): void; /** * Start a timer. Calling the returned function will set the gauge's value * to the observed duration in seconds. * @return Function to invoke when timer should be stopped. The value it * returns is the timed duration. */ startTimer(): (labels?: LabelValues) => number; } } export interface HistogramConfiguration extends MetricConfiguration { buckets?: number[]; collect?: CollectFunction>; } /** * A histogram samples observations (usually things like request durations or response sizes) and counts them in configurable buckets */ export class Histogram { /** * @param configuration Configuration when creating the Histogram. Name and Help is mandatory */ constructor(configuration: HistogramConfiguration); /** * Observe value * @param value The value to observe */ observe(value: number): void; /** * Observe value for given labels * @param labels Object with label keys and values * @param value The value to observe */ observe(labels: LabelValues, value: number): void; /** * Start a timer. Calling the returned function will observe the duration in * seconds in the histogram. * @param labels Object with label keys and values * @return Function to invoke when timer should be stopped. The value it * returns is the timed duration. */ startTimer(labels?: LabelValues): (labels?: LabelValues) => number; /** * Reset histogram values */ reset(): void; /** * Initialize the metrics for the given combination of labels to zero */ zero(labels: LabelValues): void; /** * Return the child for given labels * @param values Label values * @return Configured histogram with given labels */ labels(...values: string[]): Histogram.Internal; /** * Return the child for given labels * @param labels Object with label keys and values * @return Configured counter with given labels */ labels(labels: LabelValues): Histogram.Internal; /** * Remove metrics for the given label values * @param values Label values */ remove(...values: string[]): void; /** * Remove metrics for the given label values * @param labels Object with label keys and values */ remove(labels: LabelValues): void; } export namespace Histogram { interface Internal { /** * Observe value * @param value The value to observe */ observe(value: number): void; /** * Start a timer. Calling the returned function will observe the * duration in seconds in the histogram. * @param labels Object with label keys and values * @return Function to invoke when timer should be stopped. The value it * returns is the timed duration. */ startTimer(): (labels?: LabelValues) => void; } interface Config { /** * Buckets used in the histogram */ buckets?: number[]; } } export interface SummaryConfiguration extends MetricConfiguration { percentiles?: number[]; maxAgeSeconds?: number; ageBuckets?: number; compressCount?: number; collect?: CollectFunction>; } /** * A summary samples observations */ export class Summary { /** * @param configuration Configuration when creating Summary metric. Name and Help is mandatory */ constructor(configuration: SummaryConfiguration); /** * Observe value in summary * @param value The value to observe */ observe(value: number): void; /** * Observe value for given labels * @param labels Object with label keys and values * @param value Value to observe */ observe(labels: LabelValues, value: number): void; /** * Start a timer. Calling the returned function will observe the duration in * seconds in the summary. * @param labels Object with label keys and values * @return Function to invoke when timer should be stopped */ startTimer(labels?: LabelValues): (labels?: LabelValues) => number; /** * Reset all values in the summary */ reset(): void; /** * Return the child for given labels * @param values Label values * @return Configured summary with given labels */ labels(...values: string[]): Summary.Internal; /** * Return the child for given labels * @param labels Object with label keys and values * @return Configured counter with given labels */ labels(labels: LabelValues): Summary.Internal; /** * Remove metrics for the given label values * @param values Label values */ remove(...values: string[]): void; /** * Remove metrics for the given label values * @param labels Object with label keys and values */ remove(labels: LabelValues): void; } export namespace Summary { interface Internal { /** * Observe value in summary * @param value The value to observe */ observe(value: number): void; /** * Start a timer. Calling the returned function will observe the * duration in seconds in the summary. * @param labels Object with label keys and values * @return Function to invoke when timer should be stopped. The value it * returns is the timed duration. */ startTimer(): (labels?: LabelValues) => number; } interface Config { /** * Configurable percentiles, values should never be greater than 1 */ percentiles?: number[]; } } /** * Push metrics to a Pushgateway */ export class Pushgateway { /** * @param url Complete url to the Pushgateway. If port is needed append url with :port * @param options Options * @param registry Registry */ constructor(url: string, options?: any, registry?: Registry); /** * Add metric and overwrite old ones * @param params Push parameters */ pushAdd( params: Pushgateway.Parameters, ): Promise<{ resp?: unknown; body?: unknown }>; /** * Overwrite all metric (using PUT to Pushgateway) * @param params Push parameters */ push( params: Pushgateway.Parameters, ): Promise<{ resp?: unknown; body?: unknown }>; /** * Delete all metrics for jobName * @param params Push parameters */ delete( params: Pushgateway.Parameters, ): Promise<{ resp?: unknown; body?: unknown }>; } export namespace Pushgateway { interface Parameters { /** * Jobname that is pushing the metric */ jobName: string; /** * Label sets used in the url when making a request to the Pushgateway, */ groupings?: { [key: string]: string; }; } } /** * Create an array with equal spacing between the elements * @param start The first value in the array * @param width The spacing between the elements * @param count The number of items in array * @return An array with the requested number of elements */ export function linearBuckets( start: number, width: number, count: number, ): number[]; /** * Create an array that grows exponentially * @param start The first value in the array * @param factor The exponential factor * @param count The number of items in array * @return An array with the requested number of elements */ export function exponentialBuckets( start: number, factor: number, count: number, ): number[]; export interface DefaultMetricsCollectorConfiguration { register?: Registry; prefix?: string; gcDurationBuckets?: number[]; eventLoopMonitoringPrecision?: number; labels?: Object; } /** * Configure default metrics * @param config Configuration object for default metrics collector */ export function collectDefaultMetrics( config?: DefaultMetricsCollectorConfiguration, ): void; export interface defaultMetrics { /** * All available default metrics */ metricsList: string[]; } /** * Validate a metric name * @param name The name to validate * @return True if the metric name is valid, false if not */ export function validateMetricName(name: string): boolean; prom-client-14.1.0/index.js000066400000000000000000000016201430114771000154700ustar00rootroot00000000000000/** * Prometheus client * @module Prometheus client */ 'use strict'; exports.register = require('./lib/registry').globalRegistry; exports.Registry = require('./lib/registry'); exports.contentType = require('./lib/registry').globalRegistry.contentType; exports.validateMetricName = require('./lib/validation').validateMetricName; exports.Counter = require('./lib/counter'); exports.Gauge = require('./lib/gauge'); exports.Histogram = require('./lib/histogram'); exports.Summary = require('./lib/summary'); exports.Pushgateway = require('./lib/pushgateway'); exports.linearBuckets = require('./lib/bucketGenerators').linearBuckets; exports.exponentialBuckets = require('./lib/bucketGenerators').exponentialBuckets; exports.collectDefaultMetrics = require('./lib/defaultMetrics'); exports.aggregators = require('./lib/metricAggregators').aggregators; exports.AggregatorRegistry = require('./lib/cluster'); prom-client-14.1.0/lib/000077500000000000000000000000001430114771000145725ustar00rootroot00000000000000prom-client-14.1.0/lib/bucketGenerators.js000066400000000000000000000013371430114771000204430ustar00rootroot00000000000000'use strict'; exports.linearBuckets = (start, width, count) => { if (count < 1) { throw new Error('Linear buckets needs a positive count'); } const buckets = new Array(count); for (let i = 0; i < count; i++) { buckets[i] = start + i * width; } return buckets; }; exports.exponentialBuckets = (start, factor, count) => { if (start <= 0) { throw new Error('Exponential buckets needs a positive start'); } if (count < 1) { throw new Error('Exponential buckets needs a positive count'); } if (factor <= 1) { throw new Error('Exponential buckets needs a factor greater than 1'); } const buckets = new Array(count); for (let i = 0; i < count; i++) { buckets[i] = start; start *= factor; } return buckets; }; prom-client-14.1.0/lib/cluster.js000066400000000000000000000127161430114771000166200ustar00rootroot00000000000000'use strict'; /** * Extends the Registry class with a `clusterMetrics` method that returns * aggregated metrics for all workers. * * In cluster workers, listens for and responds to requests for metrics by the * cluster master. */ const Registry = require('./registry'); const { Grouper } = require('./util'); const { aggregators } = require('./metricAggregators'); // We need to lazy-load the 'cluster' module as some application servers - // namely Passenger - crash when it is imported. let cluster = () => { const data = require('cluster'); cluster = () => data; return data; }; const GET_METRICS_REQ = 'prom-client:getMetricsReq'; const GET_METRICS_RES = 'prom-client:getMetricsRes'; let registries = [Registry.globalRegistry]; let requestCtr = 0; // Concurrency control let listenersAdded = false; const requests = new Map(); // Pending requests for workers' local metrics. class AggregatorRegistry extends Registry { constructor() { super(); addListeners(); } /** * Gets aggregated metrics for all workers. The optional callback and * returned Promise resolve with the same value; either may be used. * @return {Promise} Promise that resolves with the aggregated * metrics. */ clusterMetrics() { const requestId = requestCtr++; return new Promise((resolve, reject) => { let settled = false; function done(err, result) { if (settled) return; settled = true; if (err) reject(err); else resolve(result); } const request = { responses: [], pending: 0, done, errorTimeout: setTimeout(() => { const err = new Error('Operation timed out.'); request.done(err); }, 5000), }; requests.set(requestId, request); const message = { type: GET_METRICS_REQ, requestId, }; for (const id in cluster().workers) { // If the worker exits abruptly, it may still be in the workers // list but not able to communicate. if (cluster().workers[id].isConnected()) { cluster().workers[id].send(message); request.pending++; } } if (request.pending === 0) { // No workers were up clearTimeout(request.errorTimeout); process.nextTick(() => done(null, '')); } }); } /** * Creates a new Registry instance from an array of metrics that were * created by `registry.getMetricsAsJSON()`. Metrics are aggregated using * the method specified by their `aggregator` property, or by summation if * `aggregator` is undefined. * @param {Array} metricsArr Array of metrics, each of which created by * `registry.getMetricsAsJSON()`. * @return {Registry} aggregated registry. */ static aggregate(metricsArr) { const aggregatedRegistry = new Registry(); const metricsByName = new Grouper(); // Gather by name metricsArr.forEach(metrics => { metrics.forEach(metric => { metricsByName.add(metric.name, metric); }); }); // Aggregate gathered metrics. metricsByName.forEach(metrics => { const aggregatorName = metrics[0].aggregator; const aggregatorFn = aggregators[aggregatorName]; if (typeof aggregatorFn !== 'function') { throw new Error(`'${aggregatorName}' is not a defined aggregator.`); } const aggregatedMetric = aggregatorFn(metrics); // NB: The 'omit' aggregator returns undefined. if (aggregatedMetric) { const aggregatedMetricWrapper = Object.assign( { get: () => aggregatedMetric, }, aggregatedMetric, ); aggregatedRegistry.registerMetric(aggregatedMetricWrapper); } }); return aggregatedRegistry; } /** * Sets the registry or registries to be aggregated. Call from workers to * use a registry/registries other than the default global registry. * @param {Array|Registry} regs Registry or registries to be * aggregated. * @return {void} */ static setRegistries(regs) { if (!Array.isArray(regs)) regs = [regs]; regs.forEach(reg => { if (!(reg instanceof Registry)) { throw new TypeError(`Expected Registry, got ${typeof reg}`); } }); registries = regs; } } /** * Adds event listeners for cluster aggregation. Idempotent (safe to call more * than once). * @return {void} */ function addListeners() { if (listenersAdded) return; listenersAdded = true; if (cluster().isMaster) { // Listen for worker responses to requests for local metrics cluster().on('message', (worker, message) => { if (message.type === GET_METRICS_RES) { const request = requests.get(message.requestId); if (message.error) { request.done(new Error(message.error)); return; } message.metrics.forEach(registry => request.responses.push(registry)); request.pending--; if (request.pending === 0) { // finalize requests.delete(message.requestId); clearTimeout(request.errorTimeout); const registry = AggregatorRegistry.aggregate(request.responses); const promString = registry.metrics(); request.done(null, promString); } } }); } if (cluster().isWorker) { // Respond to master's requests for worker's local metrics. process.on('message', message => { if (message.type === GET_METRICS_REQ) { Promise.all(registries.map(r => r.getMetricsAsJSON())) .then(metrics => { process.send({ type: GET_METRICS_RES, requestId: message.requestId, metrics, }); }) .catch(error => { process.send({ type: GET_METRICS_RES, requestId: message.requestId, error: error.message, }); }); } }); } } module.exports = AggregatorRegistry; prom-client-14.1.0/lib/counter.js000066400000000000000000000036021430114771000166100ustar00rootroot00000000000000/** * Counter metric */ 'use strict'; const util = require('util'); const type = 'counter'; const { hashObject, isObject, getLabels, removeLabels } = require('./util'); const { validateLabel } = require('./validation'); const { Metric } = require('./metric'); class Counter extends Metric { /** * Increment counter * @param {object} labels - What label you want to be incremented * @param {Number} value - Value to increment, if omitted increment with 1 * @returns {void} */ inc(labels, value) { let hash; if (isObject(labels)) { hash = hashObject(labels); validateLabel(this.labelNames, labels); } else { value = labels; labels = {}; } if (value && !Number.isFinite(value)) { throw new TypeError(`Value is not a valid number: ${util.format(value)}`); } if (value < 0) { throw new Error('It is not possible to decrease a counter'); } if (value === null || value === undefined) value = 1; setValue(this.hashMap, value, labels, hash); } /** * Reset counter * @returns {void} */ reset() { this.hashMap = {}; if (this.labelNames.length === 0) { setValue(this.hashMap, 0); } } async get() { if (this.collect) { const v = this.collect(); if (v instanceof Promise) await v; } return { help: this.help, name: this.name, type, values: Object.values(this.hashMap), aggregator: this.aggregator, }; } labels(...args) { const labels = getLabels(this.labelNames, args) || {}; return { inc: this.inc.bind(this, labels), }; } remove(...args) { const labels = getLabels(this.labelNames, args) || {}; validateLabel(this.labelNames, labels); return removeLabels.call(this, this.hashMap, labels); } } function setValue(hashMap, value, labels = {}, hash = '') { if (hashMap[hash]) { hashMap[hash].value += value; } else { hashMap[hash] = { value, labels }; } return hashMap; } module.exports = Counter; prom-client-14.1.0/lib/defaultMetrics.js000066400000000000000000000031631430114771000201060ustar00rootroot00000000000000'use strict'; const { isObject } = require('./util'); // Default metrics. const processCpuTotal = require('./metrics/processCpuTotal'); const processStartTime = require('./metrics/processStartTime'); const osMemoryHeap = require('./metrics/osMemoryHeap'); const processOpenFileDescriptors = require('./metrics/processOpenFileDescriptors'); const processMaxFileDescriptors = require('./metrics/processMaxFileDescriptors'); const eventLoopLag = require('./metrics/eventLoopLag'); const processHandles = require('./metrics/processHandles'); const processRequests = require('./metrics/processRequests'); const processResources = require('./metrics/processResources'); const heapSizeAndUsed = require('./metrics/heapSizeAndUsed'); const heapSpacesSizeAndUsed = require('./metrics/heapSpacesSizeAndUsed'); const version = require('./metrics/version'); const gc = require('./metrics/gc'); const metrics = { processCpuTotal, processStartTime, osMemoryHeap, processOpenFileDescriptors, processMaxFileDescriptors, eventLoopLag, ...(typeof process.getActiveResourcesInfo === 'function' ? { processResources } : {}), processHandles, processRequests, heapSizeAndUsed, heapSpacesSizeAndUsed, version, gc, }; const metricsList = Object.keys(metrics); module.exports = function collectDefaultMetrics(config) { if (config !== null && config !== undefined && !isObject(config)) { throw new TypeError('config must be null, undefined, or an object'); } config = { eventLoopMonitoringPrecision: 10, ...config }; for (const metric of Object.values(metrics)) { metric(config.register, config); } }; module.exports.metricsList = metricsList; prom-client-14.1.0/lib/gauge.js000066400000000000000000000102741430114771000162240ustar00rootroot00000000000000/** * Gauge metric */ 'use strict'; const util = require('util'); const type = 'gauge'; const { setValue, setValueDelta, getLabels, hashObject, isObject, removeLabels, } = require('./util'); const { validateLabel } = require('./validation'); const { Metric } = require('./metric'); class Gauge extends Metric { /** * Set a gauge to a value * @param {object} labels - Object with labels and their values * @param {Number} value - Value to set the gauge to, must be positive * @returns {void} */ set(labels, value) { value = getValueArg(labels, value); labels = getLabelArg(labels); set(this, labels, value); } /** * Reset gauge * @returns {void} */ reset() { this.hashMap = {}; if (this.labelNames.length === 0) { setValue(this.hashMap, 0, {}); } } /** * Increment a gauge value * @param {object} labels - Object with labels where key is the label key and value is label value. Can only be one level deep * @param {Number} value - Value to increment - if omitted, increment with 1 * @returns {void} */ inc(labels, value) { value = getValueArg(labels, value); labels = getLabelArg(labels); if (value === undefined) value = 1; setDelta(this, labels, value); } /** * Decrement a gauge value * @param {object} labels - Object with labels where key is the label key and value is label value. Can only be one level deep * @param {Number} value - Value to decrement - if omitted, decrement with 1 * @returns {void} */ dec(labels, value) { value = getValueArg(labels, value); labels = getLabelArg(labels); if (value === undefined) value = 1; setDelta(this, labels, -value); } /** * Set the gauge to current unix epoch * @param {object} labels - Object with labels where key is the label key and value is label value. Can only be one level deep * @returns {void} */ setToCurrentTime(labels) { const now = Date.now() / 1000; if (labels === undefined) { this.set(now); } else { this.set(labels, now); } } /** * Start a timer * @param {object} labels - Object with labels where key is the label key and value is label value. Can only be one level deep * @returns {function} - Invoke this function to set the duration in seconds since you started the timer. * @example * var done = gauge.startTimer(); * makeXHRRequest(function(err, response) { * done(); //Duration of the request will be saved * }); */ startTimer(labels) { const start = process.hrtime(); return endLabels => { const delta = process.hrtime(start); const value = delta[0] + delta[1] / 1e9; this.set(Object.assign({}, labels, endLabels), value); return value; }; } async get() { if (this.collect) { const v = this.collect(); if (v instanceof Promise) await v; } return { help: this.help, name: this.name, type, values: Object.values(this.hashMap), aggregator: this.aggregator, }; } _getValue(labels) { const hash = hashObject(labels || {}); return this.hashMap[hash] ? this.hashMap[hash].value : 0; } labels(...args) { const labels = getLabels(this.labelNames, args); validateLabel(this.labelNames, labels); return { inc: this.inc.bind(this, labels), dec: this.dec.bind(this, labels), set: this.set.bind(this, labels), setToCurrentTime: this.setToCurrentTime.bind(this, labels), startTimer: this.startTimer.bind(this, labels), }; } remove(...args) { const labels = getLabels(this.labelNames, args); validateLabel(this.labelNames, labels); removeLabels.call(this, this.hashMap, labels); } } function set(gauge, labels, value) { if (typeof value !== 'number') { throw new TypeError(`Value is not a valid number: ${util.format(value)}`); } validateLabel(gauge.labelNames, labels); setValue(gauge.hashMap, value, labels); } function setDelta(gauge, labels, delta) { if (typeof delta !== 'number') { throw new TypeError(`Delta is not a valid number: ${util.format(delta)}`); } validateLabel(gauge.labelNames, labels); const hash = hashObject(labels); setValueDelta(gauge.hashMap, delta, labels, hash); } function getLabelArg(labels) { return isObject(labels) ? labels : {}; } function getValueArg(labels, value) { return isObject(labels) ? value : labels; } module.exports = Gauge; prom-client-14.1.0/lib/histogram.js000066400000000000000000000133211430114771000171250ustar00rootroot00000000000000/** * Histogram */ 'use strict'; const util = require('util'); const type = 'histogram'; const { getLabels, hashObject, isObject, removeLabels } = require('./util'); const { validateLabel } = require('./validation'); const { Metric } = require('./metric'); class Histogram extends Metric { constructor(config) { super(config, { buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], }); for (const label of this.labelNames) { if (label === 'le') { throw new Error('le is a reserved label keyword'); } } this.upperBounds = this.buckets; this.bucketValues = this.upperBounds.reduce((acc, upperBound) => { acc[upperBound] = 0; return acc; }, {}); Object.freeze(this.bucketValues); Object.freeze(this.upperBounds); if (this.labelNames.length === 0) { this.hashMap = { [hashObject({})]: createBaseValues( {}, Object.assign({}, this.bucketValues), ), }; } } /** * Observe a value in histogram * @param {object} labels - Object with labels where key is the label key and value is label value. Can only be one level deep * @param {Number} value - Value to observe in the histogram * @returns {void} */ observe(labels, value) { observe.call(this, labels === 0 ? 0 : labels || {})(value); } async get() { if (this.collect) { const v = this.collect(); if (v instanceof Promise) await v; } const data = Object.values(this.hashMap); const values = data .map(extractBucketValuesForExport(this)) .reduce(addSumAndCountForExport(this), []); return { name: this.name, help: this.help, type, values, aggregator: this.aggregator, }; } reset() { this.hashMap = {}; } /** * Initialize the metrics for the given combination of labels to zero * @param {object} labels - Object with labels where key is the label key and value is label value. Can only be one level deep * @returns {void} */ zero(labels) { const hash = hashObject(labels); this.hashMap[hash] = createBaseValues( labels, Object.assign({}, this.bucketValues), ); } /** * Start a timer that could be used to logging durations * @param {object} labels - Object with labels where key is the label key and value is label value. Can only be one level deep * @returns {function} - Function to invoke when you want to stop the timer and observe the duration in seconds * @example * var end = histogram.startTimer(); * makeExpensiveXHRRequest(function(err, res) { * const duration = end(); //Observe the duration of expensiveXHRRequest and returns duration in seconds * console.log('Duration', duration); * }); */ startTimer(labels) { return startTimer.call(this, labels)(); } labels(...args) { const labels = getLabels(this.labelNames, args); validateLabel(this.labelNames, labels); return { observe: observe.call(this, labels), startTimer: startTimer.call(this, labels), }; } remove(...args) { const labels = getLabels(this.labelNames, args); validateLabel(this.labelNames, labels); removeLabels.call(this, this.hashMap, labels); } } function startTimer(startLabels) { return () => { const start = process.hrtime(); return endLabels => { const delta = process.hrtime(start); const value = delta[0] + delta[1] / 1e9; this.observe(Object.assign({}, startLabels, endLabels), value); return value; }; }; } function setValuePair(labels, value, metricName) { return { labels, value, metricName, }; } function findBound(upperBounds, value) { for (let i = 0; i < upperBounds.length; i++) { const bound = upperBounds[i]; if (value <= bound) { return bound; } } return -1; } function observe(labels) { return value => { const labelValuePair = convertLabelsAndValues(labels, value); validateLabel(this.labelNames, labelValuePair.labels); if (!Number.isFinite(labelValuePair.value)) { throw new TypeError( `Value is not a valid number: ${util.format(labelValuePair.value)}`, ); } const hash = hashObject(labelValuePair.labels); let valueFromMap = this.hashMap[hash]; if (!valueFromMap) { valueFromMap = createBaseValues( labelValuePair.labels, Object.assign({}, this.bucketValues), ); } const b = findBound(this.upperBounds, labelValuePair.value); valueFromMap.sum += labelValuePair.value; valueFromMap.count += 1; if (Object.prototype.hasOwnProperty.call(valueFromMap.bucketValues, b)) { valueFromMap.bucketValues[b] += 1; } this.hashMap[hash] = valueFromMap; }; } function createBaseValues(labels, bucketValues) { return { labels, bucketValues, sum: 0, count: 0, }; } function convertLabelsAndValues(labels, value) { if (!isObject(labels)) { return { value: labels, labels: {}, }; } return { labels, value, }; } function extractBucketValuesForExport(histogram) { return bucketData => { const buckets = []; const bucketLabelNames = Object.keys(bucketData.labels); let acc = 0; for (const upperBound of histogram.upperBounds) { acc += bucketData.bucketValues[upperBound]; const lbls = { le: upperBound }; for (const labelName of bucketLabelNames) { lbls[labelName] = bucketData.labels[labelName]; } buckets.push(setValuePair(lbls, acc, `${histogram.name}_bucket`)); } return { buckets, data: bucketData }; }; } function addSumAndCountForExport(histogram) { return (acc, d) => { acc.push(...d.buckets); const infLabel = { le: '+Inf' }; for (const label of Object.keys(d.data.labels)) { infLabel[label] = d.data.labels[label]; } acc.push( setValuePair(infLabel, d.data.count, `${histogram.name}_bucket`), setValuePair(d.data.labels, d.data.sum, `${histogram.name}_sum`), setValuePair(d.data.labels, d.data.count, `${histogram.name}_count`), ); return acc; }; } module.exports = Histogram; prom-client-14.1.0/lib/metric.js000066400000000000000000000023341430114771000164150ustar00rootroot00000000000000'use strict'; const { globalRegistry } = require('./registry'); const { isObject } = require('./util'); const { validateMetricName, validateLabelName } = require('./validation'); /** * @abstract */ class Metric { constructor(config, defaults = {}) { if (!isObject(config)) { throw new TypeError('constructor expected a config object'); } Object.assign( this, { labelNames: [], registers: [globalRegistry], aggregator: 'sum', }, defaults, config, ); if (!this.registers) { // in case config.registers is `undefined` this.registers = [globalRegistry]; } if (!this.help) { throw new Error('Missing mandatory help parameter'); } if (!this.name) { throw new Error('Missing mandatory name parameter'); } if (!validateMetricName(this.name)) { throw new Error('Invalid metric name'); } if (!validateLabelName(this.labelNames)) { throw new Error('Invalid label name'); } if (this.collect && typeof this.collect !== 'function') { throw new Error('Optional "collect" parameter must be a function'); } this.reset(); for (const register of this.registers) { register.registerMetric(this); } } reset() { /* abstract */ } } module.exports = { Metric }; prom-client-14.1.0/lib/metricAggregators.js000066400000000000000000000040101430114771000205740ustar00rootroot00000000000000'use strict'; const { Grouper, hashObject } = require('./util'); /** * Returns a new function that applies the `aggregatorFn` to the values. * @param {Function} aggregatorFn function to apply to values. * @return {Function} aggregator function */ function AggregatorFactory(aggregatorFn) { return metrics => { if (metrics.length === 0) return; const result = { help: metrics[0].help, name: metrics[0].name, type: metrics[0].type, values: [], aggregator: metrics[0].aggregator, }; // Gather metrics by metricName and labels. const byLabels = new Grouper(); metrics.forEach(metric => { metric.values.forEach(value => { const key = hashObject(value.labels); byLabels.add(`${value.metricName}_${key}`, value); }); }); // Apply aggregator function to gathered metrics. byLabels.forEach(values => { if (values.length === 0) return; const valObj = { value: aggregatorFn(values), labels: values[0].labels, }; if (values[0].metricName) { valObj.metricName = values[0].metricName; } // NB: Timestamps are omitted. result.values.push(valObj); }); return result; }; } // Export for users to define their own aggregation methods. exports.AggregatorFactory = AggregatorFactory; /** * Functions that can be used to aggregate metrics from multiple registries. */ exports.aggregators = { /** * @return The sum of values. */ sum: AggregatorFactory(v => v.reduce((p, c) => p + c.value, 0)), /** * @return The first value. */ first: AggregatorFactory(v => v[0].value), /** * @return {undefined} Undefined; omits the metric. */ omit: () => {}, /** * @return The arithmetic mean of the values. */ average: AggregatorFactory( v => v.reduce((p, c) => p + c.value, 0) / v.length, ), /** * @return The minimum of the values. */ min: AggregatorFactory(v => v.reduce((p, c) => Math.min(p, c.value), Infinity), ), /** * @return The maximum of the values. */ max: AggregatorFactory(v => v.reduce((p, c) => Math.max(p, c.value), -Infinity), ), }; prom-client-14.1.0/lib/metrics/000077500000000000000000000000001430114771000162405ustar00rootroot00000000000000prom-client-14.1.0/lib/metrics/eventLoopLag.js000066400000000000000000000076371430114771000212120ustar00rootroot00000000000000'use strict'; const Gauge = require('../gauge'); // Check if perf_hooks module is available let perf_hooks; try { /* eslint-disable node/no-unsupported-features/node-builtins */ perf_hooks = require('perf_hooks'); } catch { // node version is too old } // Reported always. const NODEJS_EVENTLOOP_LAG = 'nodejs_eventloop_lag_seconds'; // Reported only when perf_hooks is available. const NODEJS_EVENTLOOP_LAG_MIN = 'nodejs_eventloop_lag_min_seconds'; const NODEJS_EVENTLOOP_LAG_MAX = 'nodejs_eventloop_lag_max_seconds'; const NODEJS_EVENTLOOP_LAG_MEAN = 'nodejs_eventloop_lag_mean_seconds'; const NODEJS_EVENTLOOP_LAG_STDDEV = 'nodejs_eventloop_lag_stddev_seconds'; const NODEJS_EVENTLOOP_LAG_P50 = 'nodejs_eventloop_lag_p50_seconds'; const NODEJS_EVENTLOOP_LAG_P90 = 'nodejs_eventloop_lag_p90_seconds'; const NODEJS_EVENTLOOP_LAG_P99 = 'nodejs_eventloop_lag_p99_seconds'; function reportEventloopLag(start, gauge, labels) { const delta = process.hrtime(start); const nanosec = delta[0] * 1e9 + delta[1]; const seconds = nanosec / 1e9; gauge.set(labels, seconds); } module.exports = (registry, config = {}) => { const namePrefix = config.prefix ? config.prefix : ''; const labels = config.labels ? config.labels : {}; const labelNames = Object.keys(labels); const registers = registry ? [registry] : undefined; let collect; if (!perf_hooks || !perf_hooks.monitorEventLoopDelay) { collect = () => { const start = process.hrtime(); setImmediate(reportEventloopLag, start, lag, labels); }; } else { const histogram = perf_hooks.monitorEventLoopDelay({ resolution: config.eventLoopMonitoringPrecision, }); histogram.enable(); collect = () => { const start = process.hrtime(); setImmediate(reportEventloopLag, start, lag, labels); lagMin.set(labels, histogram.min / 1e9); lagMax.set(labels, histogram.max / 1e9); lagMean.set(labels, histogram.mean / 1e9); lagStddev.set(labels, histogram.stddev / 1e9); lagP50.set(labels, histogram.percentile(50) / 1e9); lagP90.set(labels, histogram.percentile(90) / 1e9); lagP99.set(labels, histogram.percentile(99) / 1e9); histogram.reset(); }; } const lag = new Gauge({ name: namePrefix + NODEJS_EVENTLOOP_LAG, help: 'Lag of event loop in seconds.', registers, labelNames, aggregator: 'average', // Use this one metric's `collect` to set all metrics' values. collect, }); const lagMin = new Gauge({ name: namePrefix + NODEJS_EVENTLOOP_LAG_MIN, help: 'The minimum recorded event loop delay.', registers, labelNames, aggregator: 'min', }); const lagMax = new Gauge({ name: namePrefix + NODEJS_EVENTLOOP_LAG_MAX, help: 'The maximum recorded event loop delay.', registers, labelNames, aggregator: 'max', }); const lagMean = new Gauge({ name: namePrefix + NODEJS_EVENTLOOP_LAG_MEAN, help: 'The mean of the recorded event loop delays.', registers, labelNames, aggregator: 'average', }); const lagStddev = new Gauge({ name: namePrefix + NODEJS_EVENTLOOP_LAG_STDDEV, help: 'The standard deviation of the recorded event loop delays.', registers, labelNames, aggregator: 'average', }); const lagP50 = new Gauge({ name: namePrefix + NODEJS_EVENTLOOP_LAG_P50, help: 'The 50th percentile of the recorded event loop delays.', registers, labelNames, aggregator: 'average', }); const lagP90 = new Gauge({ name: namePrefix + NODEJS_EVENTLOOP_LAG_P90, help: 'The 90th percentile of the recorded event loop delays.', registers, labelNames, aggregator: 'average', }); const lagP99 = new Gauge({ name: namePrefix + NODEJS_EVENTLOOP_LAG_P99, help: 'The 99th percentile of the recorded event loop delays.', registers, labelNames, aggregator: 'average', }); }; module.exports.metricNames = [ NODEJS_EVENTLOOP_LAG, NODEJS_EVENTLOOP_LAG_MIN, NODEJS_EVENTLOOP_LAG_MAX, NODEJS_EVENTLOOP_LAG_MEAN, NODEJS_EVENTLOOP_LAG_STDDEV, NODEJS_EVENTLOOP_LAG_P50, NODEJS_EVENTLOOP_LAG_P90, NODEJS_EVENTLOOP_LAG_P99, ]; prom-client-14.1.0/lib/metrics/gc.js000066400000000000000000000034561430114771000171770ustar00rootroot00000000000000'use strict'; const Histogram = require('../histogram'); let perf_hooks; try { // eslint-disable-next-line perf_hooks = require('perf_hooks'); } catch { // node version is too old } const NODEJS_GC_DURATION_SECONDS = 'nodejs_gc_duration_seconds'; const DEFAULT_GC_DURATION_BUCKETS = [0.001, 0.01, 0.1, 1, 2, 5]; const kinds = []; kinds[perf_hooks.constants.NODE_PERFORMANCE_GC_MAJOR] = 'major'; kinds[perf_hooks.constants.NODE_PERFORMANCE_GC_MINOR] = 'minor'; kinds[perf_hooks.constants.NODE_PERFORMANCE_GC_INCREMENTAL] = 'incremental'; kinds[perf_hooks.constants.NODE_PERFORMANCE_GC_WEAKCB] = 'weakcb'; module.exports = (registry, config = {}) => { if (!perf_hooks) { return; } const namePrefix = config.prefix ? config.prefix : ''; const labels = config.labels ? config.labels : {}; const labelNames = Object.keys(labels); const buckets = config.gcDurationBuckets ? config.gcDurationBuckets : DEFAULT_GC_DURATION_BUCKETS; const gcHistogram = new Histogram({ name: namePrefix + NODEJS_GC_DURATION_SECONDS, help: 'Garbage collection duration by kind, one of major, minor, incremental or weakcb.', labelNames: ['kind', ...labelNames], buckets, registers: registry ? [registry] : undefined, }); const obs = new perf_hooks.PerformanceObserver(list => { const entry = list.getEntries()[0]; // Node < 16 uses entry.kind // Node >= 16 uses entry.detail.kind // See: https://nodejs.org/docs/latest-v16.x/api/deprecations.html#deprecations_dep0152_extension_performanceentry_properties const kind = entry.detail ? kinds[entry.detail.kind] : kinds[entry.kind]; // Convert duration from milliseconds to seconds gcHistogram.observe(Object.assign({ kind }, labels), entry.duration / 1000); }); obs.observe({ entryTypes: ['gc'] }); }; module.exports.metricNames = [NODEJS_GC_DURATION_SECONDS]; prom-client-14.1.0/lib/metrics/heapSizeAndUsed.js000066400000000000000000000030601430114771000216110ustar00rootroot00000000000000'use strict'; const Gauge = require('../gauge'); const safeMemoryUsage = require('./helpers/safeMemoryUsage'); const NODEJS_HEAP_SIZE_TOTAL = 'nodejs_heap_size_total_bytes'; const NODEJS_HEAP_SIZE_USED = 'nodejs_heap_size_used_bytes'; const NODEJS_EXTERNAL_MEMORY = 'nodejs_external_memory_bytes'; module.exports = (registry, config = {}) => { if (typeof process.memoryUsage !== 'function') { return; } const labels = config.labels ? config.labels : {}; const labelNames = Object.keys(labels); const registers = registry ? [registry] : undefined; const namePrefix = config.prefix ? config.prefix : ''; const collect = () => { const memUsage = safeMemoryUsage(); if (memUsage) { heapSizeTotal.set(labels, memUsage.heapTotal); heapSizeUsed.set(labels, memUsage.heapUsed); if (memUsage.external !== undefined) { externalMemUsed.set(labels, memUsage.external); } } }; const heapSizeTotal = new Gauge({ name: namePrefix + NODEJS_HEAP_SIZE_TOTAL, help: 'Process heap size from Node.js in bytes.', registers, labelNames, // Use this one metric's `collect` to set all metrics' values. collect, }); const heapSizeUsed = new Gauge({ name: namePrefix + NODEJS_HEAP_SIZE_USED, help: 'Process heap size used from Node.js in bytes.', registers, labelNames, }); const externalMemUsed = new Gauge({ name: namePrefix + NODEJS_EXTERNAL_MEMORY, help: 'Node.js external memory size in bytes.', registers, labelNames, }); }; module.exports.metricNames = [ NODEJS_HEAP_SIZE_TOTAL, NODEJS_HEAP_SIZE_USED, NODEJS_EXTERNAL_MEMORY, ]; prom-client-14.1.0/lib/metrics/heapSpacesSizeAndUsed.js000066400000000000000000000025211430114771000227510ustar00rootroot00000000000000'use strict'; const Gauge = require('../gauge'); const v8 = require('v8'); const METRICS = ['total', 'used', 'available']; const NODEJS_HEAP_SIZE = {}; METRICS.forEach(metricType => { NODEJS_HEAP_SIZE[metricType] = `nodejs_heap_space_size_${metricType}_bytes`; }); module.exports = (registry, config = {}) => { const registers = registry ? [registry] : undefined; const namePrefix = config.prefix ? config.prefix : ''; const labels = config.labels ? config.labels : {}; const labelNames = ['space', ...Object.keys(labels)]; const gauges = {}; METRICS.forEach(metricType => { gauges[metricType] = new Gauge({ name: namePrefix + NODEJS_HEAP_SIZE[metricType], help: `Process heap space size ${metricType} from Node.js in bytes.`, labelNames, registers, }); }); // Use this one metric's `collect` to set all metrics' values. gauges.total.collect = () => { for (const space of v8.getHeapSpaceStatistics()) { const spaceName = space.space_name.substr( 0, space.space_name.indexOf('_space'), ); gauges.total.set({ space: spaceName, ...labels }, space.space_size); gauges.used.set({ space: spaceName, ...labels }, space.space_used_size); gauges.available.set( { space: spaceName, ...labels }, space.space_available_size, ); } }; }; module.exports.metricNames = Object.values(NODEJS_HEAP_SIZE); prom-client-14.1.0/lib/metrics/helpers/000077500000000000000000000000001430114771000177025ustar00rootroot00000000000000prom-client-14.1.0/lib/metrics/helpers/processMetricsHelpers.js000066400000000000000000000012171430114771000245710ustar00rootroot00000000000000'use strict'; function aggregateByObjectName(list) { const data = {}; for (let i = 0; i < list.length; i++) { const listElement = list[i]; if (!listElement || typeof listElement.constructor === 'undefined') { continue; } if (Object.hasOwnProperty.call(data, listElement.constructor.name)) { data[listElement.constructor.name] += 1; } else { data[listElement.constructor.name] = 1; } } return data; } function updateMetrics(gauge, data, labels) { gauge.reset(); for (const key in data) { gauge.set(Object.assign({ type: key }, labels || {}), data[key]); } } module.exports = { aggregateByObjectName, updateMetrics, }; prom-client-14.1.0/lib/metrics/helpers/safeMemoryUsage.js000066400000000000000000000003161430114771000233340ustar00rootroot00000000000000'use strict'; // process.memoryUsage() can throw on some platforms, see #67 function safeMemoryUsage() { try { return process.memoryUsage(); } catch { return; } } module.exports = safeMemoryUsage; prom-client-14.1.0/lib/metrics/osMemoryHeap.js000066400000000000000000000021051430114771000212040ustar00rootroot00000000000000'use strict'; const Gauge = require('../gauge'); const linuxVariant = require('./osMemoryHeapLinux'); const safeMemoryUsage = require('./helpers/safeMemoryUsage'); const PROCESS_RESIDENT_MEMORY = 'process_resident_memory_bytes'; function notLinuxVariant(registry, config = {}) { const namePrefix = config.prefix ? config.prefix : ''; const labels = config.labels ? config.labels : {}; const labelNames = Object.keys(labels); new Gauge({ name: namePrefix + PROCESS_RESIDENT_MEMORY, help: 'Resident memory size in bytes.', registers: registry ? [registry] : undefined, labelNames, collect() { const memUsage = safeMemoryUsage(); // I don't think the other things returned from `process.memoryUsage()` is relevant to a standard export if (memUsage) { this.set(labels, memUsage.rss); } }, }); } module.exports = (registry, config) => process.platform === 'linux' ? linuxVariant(registry, config) : notLinuxVariant(registry, config); module.exports.metricNames = process.platform === 'linux' ? linuxVariant.metricNames : [PROCESS_RESIDENT_MEMORY]; prom-client-14.1.0/lib/metrics/osMemoryHeapLinux.js000066400000000000000000000044641430114771000222360ustar00rootroot00000000000000'use strict'; const Gauge = require('../gauge'); const fs = require('fs'); const values = ['VmSize', 'VmRSS', 'VmData']; const PROCESS_RESIDENT_MEMORY = 'process_resident_memory_bytes'; const PROCESS_VIRTUAL_MEMORY = 'process_virtual_memory_bytes'; const PROCESS_HEAP = 'process_heap_bytes'; function structureOutput(input) { const returnValue = {}; input .split('\n') .filter(s => values.some(value => s.indexOf(value) === 0)) .forEach(string => { const split = string.split(':'); // Get the value let value = split[1].trim(); // Remove trailing ` kb` value = value.substr(0, value.length - 3); // Make it into a number in bytes bytes value = Number(value) * 1024; returnValue[split[0]] = value; }); return returnValue; } module.exports = (registry, config = {}) => { const registers = registry ? [registry] : undefined; const namePrefix = config.prefix ? config.prefix : ''; const labels = config.labels ? config.labels : {}; const labelNames = Object.keys(labels); const residentMemGauge = new Gauge({ name: namePrefix + PROCESS_RESIDENT_MEMORY, help: 'Resident memory size in bytes.', registers, labelNames, // Use this one metric's `collect` to set all metrics' values. collect() { try { // Sync I/O is often problematic, but /proc isn't really I/O, it // a virtual filesystem that maps directly to in-kernel data // structures and never blocks. // // Node.js/libuv do this already for process.memoryUsage(), see: // - https://github.com/libuv/libuv/blob/a629688008694ed8022269e66826d4d6ec688b83/src/unix/linux-core.c#L506-L523 const stat = fs.readFileSync('/proc/self/status', 'utf8'); const structuredOutput = structureOutput(stat); residentMemGauge.set(labels, structuredOutput.VmRSS); virtualMemGauge.set(labels, structuredOutput.VmSize); heapSizeMemGauge.set(labels, structuredOutput.VmData); } catch { // noop } }, }); const virtualMemGauge = new Gauge({ name: namePrefix + PROCESS_VIRTUAL_MEMORY, help: 'Virtual memory size in bytes.', registers, labelNames, }); const heapSizeMemGauge = new Gauge({ name: namePrefix + PROCESS_HEAP, help: 'Process heap size in bytes.', registers, labelNames, }); }; module.exports.metricNames = [ PROCESS_RESIDENT_MEMORY, PROCESS_VIRTUAL_MEMORY, PROCESS_HEAP, ]; prom-client-14.1.0/lib/metrics/processCpuTotal.js000066400000000000000000000031761430114771000217370ustar00rootroot00000000000000'use strict'; const Counter = require('../counter'); const PROCESS_CPU_USER_SECONDS = 'process_cpu_user_seconds_total'; const PROCESS_CPU_SYSTEM_SECONDS = 'process_cpu_system_seconds_total'; const PROCESS_CPU_SECONDS = 'process_cpu_seconds_total'; module.exports = (registry, config = {}) => { const registers = registry ? [registry] : undefined; const namePrefix = config.prefix ? config.prefix : ''; const labels = config.labels ? config.labels : {}; const labelNames = Object.keys(labels); let lastCpuUsage = process.cpuUsage(); const cpuUserUsageCounter = new Counter({ name: namePrefix + PROCESS_CPU_USER_SECONDS, help: 'Total user CPU time spent in seconds.', registers, labelNames, // Use this one metric's `collect` to set all metrics' values. collect() { const cpuUsage = process.cpuUsage(); const userUsageMicros = cpuUsage.user - lastCpuUsage.user; const systemUsageMicros = cpuUsage.system - lastCpuUsage.system; lastCpuUsage = cpuUsage; cpuUserUsageCounter.inc(labels, userUsageMicros / 1e6); cpuSystemUsageCounter.inc(labels, systemUsageMicros / 1e6); cpuUsageCounter.inc(labels, (userUsageMicros + systemUsageMicros) / 1e6); }, }); const cpuSystemUsageCounter = new Counter({ name: namePrefix + PROCESS_CPU_SYSTEM_SECONDS, help: 'Total system CPU time spent in seconds.', registers, labelNames, }); const cpuUsageCounter = new Counter({ name: namePrefix + PROCESS_CPU_SECONDS, help: 'Total user and system CPU time spent in seconds.', registers, labelNames, }); }; module.exports.metricNames = [ PROCESS_CPU_USER_SECONDS, PROCESS_CPU_SYSTEM_SECONDS, PROCESS_CPU_SECONDS, ]; prom-client-14.1.0/lib/metrics/processHandles.js000066400000000000000000000026101430114771000215520ustar00rootroot00000000000000'use strict'; const { aggregateByObjectName } = require('./helpers/processMetricsHelpers'); const { updateMetrics } = require('./helpers/processMetricsHelpers'); const Gauge = require('../gauge'); const NODEJS_ACTIVE_HANDLES = 'nodejs_active_handles'; const NODEJS_ACTIVE_HANDLES_TOTAL = 'nodejs_active_handles_total'; module.exports = (registry, config = {}) => { // Don't do anything if the function is removed in later nodes (exists in node@6-12...) if (typeof process._getActiveHandles !== 'function') { return; } const registers = registry ? [registry] : undefined; const namePrefix = config.prefix ? config.prefix : ''; const labels = config.labels ? config.labels : {}; const labelNames = Object.keys(labels); new Gauge({ name: namePrefix + NODEJS_ACTIVE_HANDLES, help: 'Number of active libuv handles grouped by handle type. Every handle type is C++ class name.', labelNames: ['type', ...labelNames], registers, collect() { const handles = process._getActiveHandles(); updateMetrics(this, aggregateByObjectName(handles), labels); }, }); new Gauge({ name: namePrefix + NODEJS_ACTIVE_HANDLES_TOTAL, help: 'Total number of active handles.', registers, labelNames, collect() { const handles = process._getActiveHandles(); this.set(labels, handles.length); }, }); }; module.exports.metricNames = [ NODEJS_ACTIVE_HANDLES, NODEJS_ACTIVE_HANDLES_TOTAL, ]; prom-client-14.1.0/lib/metrics/processMaxFileDescriptors.js000066400000000000000000000020451430114771000237450ustar00rootroot00000000000000'use strict'; const Gauge = require('../gauge'); const fs = require('fs'); const PROCESS_MAX_FDS = 'process_max_fds'; let maxFds; module.exports = (registry, config = {}) => { if (maxFds === undefined) { // This will fail if a linux-like procfs is not available. try { const limits = fs.readFileSync('/proc/self/limits', 'utf8'); const lines = limits.split('\n'); for (const line of lines) { if (line.startsWith('Max open files')) { const parts = line.split(/ +/); maxFds = Number(parts[1]); break; } } } catch { return; } } if (maxFds === undefined) return; const namePrefix = config.prefix ? config.prefix : ''; const labels = config.labels ? config.labels : {}; const labelNames = Object.keys(labels); new Gauge({ name: namePrefix + PROCESS_MAX_FDS, help: 'Maximum number of open file descriptors.', registers: registry ? [registry] : undefined, labelNames, collect() { if (maxFds !== undefined) this.set(labels, maxFds); }, }); }; module.exports.metricNames = [PROCESS_MAX_FDS]; prom-client-14.1.0/lib/metrics/processOpenFileDescriptors.js000066400000000000000000000015321430114771000241210ustar00rootroot00000000000000'use strict'; const Gauge = require('../gauge'); const fs = require('fs'); const process = require('process'); const PROCESS_OPEN_FDS = 'process_open_fds'; module.exports = (registry, config = {}) => { if (process.platform !== 'linux') { return; } const namePrefix = config.prefix ? config.prefix : ''; const labels = config.labels ? config.labels : {}; const labelNames = Object.keys(labels); new Gauge({ name: namePrefix + PROCESS_OPEN_FDS, help: 'Number of open file descriptors.', registers: registry ? [registry] : undefined, labelNames, collect() { try { const fds = fs.readdirSync('/proc/self/fd'); // Minus 1 to not count the fd that was used by readdirSync(), // it's now closed. this.set(labels, fds.length - 1); } catch { // noop } }, }); }; module.exports.metricNames = [PROCESS_OPEN_FDS]; prom-client-14.1.0/lib/metrics/processRequests.js000066400000000000000000000026451430114771000220170ustar00rootroot00000000000000'use strict'; const Gauge = require('../gauge'); const { aggregateByObjectName } = require('./helpers/processMetricsHelpers'); const { updateMetrics } = require('./helpers/processMetricsHelpers'); const NODEJS_ACTIVE_REQUESTS = 'nodejs_active_requests'; const NODEJS_ACTIVE_REQUESTS_TOTAL = 'nodejs_active_requests_total'; module.exports = (registry, config = {}) => { // Don't do anything if the function is removed in later nodes (exists in node@6) if (typeof process._getActiveRequests !== 'function') { return; } const namePrefix = config.prefix ? config.prefix : ''; const labels = config.labels ? config.labels : {}; const labelNames = Object.keys(labels); new Gauge({ name: namePrefix + NODEJS_ACTIVE_REQUESTS, help: 'Number of active libuv requests grouped by request type. Every request type is C++ class name.', labelNames: ['type', ...labelNames], registers: registry ? [registry] : undefined, collect() { const requests = process._getActiveRequests(); updateMetrics(this, aggregateByObjectName(requests), labels); }, }); new Gauge({ name: namePrefix + NODEJS_ACTIVE_REQUESTS_TOTAL, help: 'Total number of active requests.', registers: registry ? [registry] : undefined, labelNames, collect() { const requests = process._getActiveRequests(); this.set(labels, requests.length); }, }); }; module.exports.metricNames = [ NODEJS_ACTIVE_REQUESTS, NODEJS_ACTIVE_REQUESTS_TOTAL, ]; prom-client-14.1.0/lib/metrics/processResources.js000066400000000000000000000031141430114771000221460ustar00rootroot00000000000000'use strict'; const Gauge = require('../gauge'); const { updateMetrics } = require('./helpers/processMetricsHelpers'); const NODEJS_ACTIVE_RESOURCES = 'nodejs_active_resources'; const NODEJS_ACTIVE_RESOURCES_TOTAL = 'nodejs_active_resources_total'; module.exports = (registry, config = {}) => { // Don't do anything if the function does not exist in previous nodes (exists in node@17.3.0) if (typeof process.getActiveResourcesInfo !== 'function') { return; } const namePrefix = config.prefix ? config.prefix : ''; const labels = config.labels ? config.labels : {}; const labelNames = Object.keys(labels); new Gauge({ name: namePrefix + NODEJS_ACTIVE_RESOURCES, help: 'Number of active resources that are currently keeping the event loop alive, grouped by async resource type.', labelNames: ['type', ...labelNames], registers: registry ? [registry] : undefined, collect() { const resources = process.getActiveResourcesInfo(); const data = {}; for (let i = 0; i < resources.length; i++) { const resource = resources[i]; if (Object.hasOwn(data, resource)) { data[resource] += 1; } else { data[resource] = 1; } } updateMetrics(this, data, labels); }, }); new Gauge({ name: namePrefix + NODEJS_ACTIVE_RESOURCES_TOTAL, help: 'Total number of active resources.', registers: registry ? [registry] : undefined, labelNames, collect() { const resources = process.getActiveResourcesInfo(); this.set(labels, resources.length); }, }); }; module.exports.metricNames = [ NODEJS_ACTIVE_RESOURCES, NODEJS_ACTIVE_RESOURCES_TOTAL, ]; prom-client-14.1.0/lib/metrics/processStartTime.js000066400000000000000000000012721430114771000221130ustar00rootroot00000000000000'use strict'; const Gauge = require('../gauge'); const startInSeconds = Math.round(Date.now() / 1000 - process.uptime()); const PROCESS_START_TIME = 'process_start_time_seconds'; module.exports = (registry, config = {}) => { const namePrefix = config.prefix ? config.prefix : ''; const labels = config.labels ? config.labels : {}; const labelNames = Object.keys(labels); new Gauge({ name: namePrefix + PROCESS_START_TIME, help: 'Start time of the process since unix epoch in seconds.', registers: registry ? [registry] : undefined, labelNames, aggregator: 'omit', collect() { this.set(labels, startInSeconds); }, }); }; module.exports.metricNames = [PROCESS_START_TIME]; prom-client-14.1.0/lib/metrics/version.js000066400000000000000000000016231430114771000202650ustar00rootroot00000000000000'use strict'; const Gauge = require('../gauge'); const version = process.version; const versionSegments = version.slice(1).split('.').map(Number); const NODE_VERSION_INFO = 'nodejs_version_info'; module.exports = (registry, config = {}) => { const namePrefix = config.prefix ? config.prefix : ''; const labels = config.labels ? config.labels : {}; const labelNames = Object.keys(labels); new Gauge({ name: namePrefix + NODE_VERSION_INFO, help: 'Node.js version info.', labelNames: ['version', 'major', 'minor', 'patch', ...labelNames], registers: registry ? [registry] : undefined, aggregator: 'first', collect() { // Needs to be in collect() so value is present even if reg is reset this.labels( version, versionSegments[0], versionSegments[1], versionSegments[2], ...Object.values(labels), ).set(1); }, }); }; module.exports.metricNames = [NODE_VERSION_INFO]; prom-client-14.1.0/lib/pushgateway.js000066400000000000000000000052761430114771000175030ustar00rootroot00000000000000'use strict'; const url = require('url'); const http = require('http'); const https = require('https'); const { gzipSync } = require('zlib'); const { globalRegistry } = require('./registry'); class Pushgateway { constructor(gatewayUrl, options, registry) { if (!registry) { registry = globalRegistry; } this.registry = registry; this.gatewayUrl = gatewayUrl; this.requestOptions = Object.assign({}, options); } pushAdd(params) { if (!params || !params.jobName) { throw new Error('Missing jobName parameter'); } return useGateway.call(this, 'POST', params.jobName, params.groupings); } push(params) { if (!params || !params.jobName) { throw new Error('Missing jobName parameter'); } return useGateway.call(this, 'PUT', params.jobName, params.groupings); } delete(params) { if (!params || !params.jobName) { throw new Error('Missing jobName parameter'); } return useGateway.call(this, 'DELETE', params.jobName, params.groupings); } } async function useGateway(method, job, groupings) { // `URL` first added in v6.13.0 // eslint-disable-next-line node/no-deprecated-api const gatewayUrlParsed = url.parse(this.gatewayUrl); const gatewayUrlPath = gatewayUrlParsed.pathname && gatewayUrlParsed.pathname !== '/' ? gatewayUrlParsed.pathname : ''; const path = `${gatewayUrlPath}/metrics/job/${encodeURIComponent( job, )}${generateGroupings(groupings)}`; // eslint-disable-next-line node/no-deprecated-api const target = url.resolve(this.gatewayUrl, path); // eslint-disable-next-line node/no-deprecated-api const requestParams = url.parse(target); const httpModule = isHttps(requestParams.href) ? https : http; const options = Object.assign(requestParams, this.requestOptions, { method, }); return new Promise((resolve, reject) => { const req = httpModule.request(options, resp => { let body = ''; resp.setEncoding('utf8'); resp.on('data', chunk => { body += chunk; }); resp.on('end', () => { resolve({ resp, body }); }); }); req.on('error', err => { reject(err); }); if (method !== 'DELETE') { this.registry .metrics() .then(metrics => { if ( options.headers && options.headers['Content-Encoding'] === 'gzip' ) { metrics = gzipSync(metrics); } req.write(metrics); req.end(); }) .catch(err => { reject(err); }); } else { req.end(); } }); } function generateGroupings(groupings) { if (!groupings) { return ''; } return Object.keys(groupings) .map( key => `/${encodeURIComponent(key)}/${encodeURIComponent(groupings[key])}`, ) .join(''); } function isHttps(href) { return href.search(/^https/) !== -1; } module.exports = Pushgateway; prom-client-14.1.0/lib/registry.js000066400000000000000000000072221430114771000170030ustar00rootroot00000000000000'use strict'; const { getValueAsString } = require('./util'); function escapeString(str) { return str.replace(/\n/g, '\\n').replace(/\\(?!n)/g, '\\\\'); } function escapeLabelValue(str) { if (typeof str !== 'string') { return str; } return escapeString(str).replace(/"/g, '\\"'); } class Registry { constructor() { this._metrics = {}; this._collectors = []; this._defaultLabels = {}; } getMetricsAsArray() { return Object.values(this._metrics); } async getMetricAsPrometheusString(metric) { const item = await metric.get(); const name = escapeString(item.name); const help = `# HELP ${name} ${escapeString(item.help)}`; const type = `# TYPE ${name} ${item.type}`; const defaultLabelNames = Object.keys(this._defaultLabels); let values = ''; for (const val of item.values || []) { val.labels = val.labels || {}; if (defaultLabelNames.length > 0) { // Make a copy before mutating val.labels = Object.assign({}, val.labels); for (const labelName of defaultLabelNames) { val.labels[labelName] = val.labels[labelName] || this._defaultLabels[labelName]; } } let metricName = val.metricName || item.name; const keys = Object.keys(val.labels); const size = keys.length; if (size > 0) { let labels = ''; let i = 0; for (; i < size - 1; i++) { labels += `${keys[i]}="${escapeLabelValue(val.labels[keys[i]])}",`; } labels += `${keys[i]}="${escapeLabelValue(val.labels[keys[i]])}"`; metricName += `{${labels}}`; } values += `${metricName} ${getValueAsString(val.value)}\n`; } return `${help}\n${type}\n${values}`.trim(); } async metrics() { const promises = []; for (const metric of this.getMetricsAsArray()) { promises.push(this.getMetricAsPrometheusString(metric)); } const resolves = await Promise.all(promises); return `${resolves.join('\n\n')}\n`; } registerMetric(metric) { if (this._metrics[metric.name] && this._metrics[metric.name] !== metric) { throw new Error( `A metric with the name ${metric.name} has already been registered.`, ); } this._metrics[metric.name] = metric; } clear() { this._metrics = {}; this._defaultLabels = {}; } async getMetricsAsJSON() { const metrics = []; const defaultLabelNames = Object.keys(this._defaultLabels); const promises = []; for (const metric of this.getMetricsAsArray()) { promises.push(metric.get()); } const resolves = await Promise.all(promises); for (const item of resolves) { if (item.values && defaultLabelNames.length > 0) { for (const val of item.values) { // Make a copy before mutating val.labels = Object.assign({}, val.labels); for (const labelName of defaultLabelNames) { val.labels[labelName] = val.labels[labelName] || this._defaultLabels[labelName]; } } } metrics.push(item); } return metrics; } removeSingleMetric(name) { delete this._metrics[name]; } getSingleMetricAsString(name) { return this.getMetricAsPrometheusString(this._metrics[name]); } getSingleMetric(name) { return this._metrics[name]; } setDefaultLabels(labels) { this._defaultLabels = labels; } resetMetrics() { for (const metric in this._metrics) { this._metrics[metric].reset(); } } get contentType() { return 'text/plain; version=0.0.4; charset=utf-8'; } static merge(registers) { const mergedRegistry = new Registry(); const metricsToMerge = registers.reduce( (acc, reg) => acc.concat(reg.getMetricsAsArray()), [], ); metricsToMerge.forEach(mergedRegistry.registerMetric, mergedRegistry); return mergedRegistry; } } module.exports = Registry; module.exports.globalRegistry = new Registry(); prom-client-14.1.0/lib/summary.js000066400000000000000000000112411430114771000166240ustar00rootroot00000000000000/** * Summary */ 'use strict'; const util = require('util'); const type = 'summary'; const { getLabels, hashObject, removeLabels } = require('./util'); const { validateLabel } = require('./validation'); const { Metric } = require('./metric'); const timeWindowQuantiles = require('./timeWindowQuantiles'); const DEFAULT_COMPRESS_COUNT = 1000; // every 1000 measurements class Summary extends Metric { constructor(config) { super(config, { percentiles: [0.01, 0.05, 0.5, 0.9, 0.95, 0.99, 0.999], compressCount: DEFAULT_COMPRESS_COUNT, hashMap: {}, }); for (const label of this.labelNames) { if (label === 'quantile') throw new Error('quantile is a reserved label keyword'); } if (this.labelNames.length === 0) { this.hashMap = { [hashObject({})]: { labels: {}, td: new timeWindowQuantiles(this.maxAgeSeconds, this.ageBuckets), count: 0, sum: 0, }, }; } } /** * Observe a value * @param {object} labels - Object with labels where key is the label key and value is label value. Can only be one level deep * @param {Number} value - Value to observe * @returns {void} */ observe(labels, value) { observe.call(this, labels === 0 ? 0 : labels || {})(value); } async get() { if (this.collect) { const v = this.collect(); if (v instanceof Promise) await v; } const data = Object.values(this.hashMap); const values = []; data.forEach(s => { extractSummariesForExport(s, this.percentiles).forEach(v => { values.push(v); }); values.push(getSumForExport(s, this)); values.push(getCountForExport(s, this)); }); return { name: this.name, help: this.help, type, values, aggregator: this.aggregator, }; } reset() { const data = Object.values(this.hashMap); data.forEach(s => { s.td.reset(); s.count = 0; s.sum = 0; }); } /** * Start a timer that could be used to logging durations * @param {object} labels - Object with labels where key is the label key and value is label value. Can only be one level deep * @returns {function} - Function to invoke when you want to stop the timer and observe the duration in seconds * @example * var end = summary.startTimer(); * makeExpensiveXHRRequest(function(err, res) { * end(); //Observe the duration of expensiveXHRRequest * }); */ startTimer(labels) { return startTimer.call(this, labels)(); } labels(...args) { const labels = getLabels(this.labelNames, args); validateLabel(this.labelNames, labels); return { observe: observe.call(this, labels), startTimer: startTimer.call(this, labels), }; } remove(...args) { const labels = getLabels(this.labelNames, args); validateLabel(this.labelNames, labels); removeLabels.call(this, this.hashMap, labels); } } function extractSummariesForExport(summaryOfLabels, percentiles) { summaryOfLabels.td.compress(); return percentiles.map(percentile => { const percentileValue = summaryOfLabels.td.percentile(percentile); return { labels: Object.assign({ quantile: percentile }, summaryOfLabels.labels), value: percentileValue ? percentileValue : 0, }; }); } function getCountForExport(value, summary) { return { metricName: `${summary.name}_count`, labels: value.labels, value: value.count, }; } function getSumForExport(value, summary) { return { metricName: `${summary.name}_sum`, labels: value.labels, value: value.sum, }; } function startTimer(startLabels) { return () => { const start = process.hrtime(); return endLabels => { const delta = process.hrtime(start); const value = delta[0] + delta[1] / 1e9; this.observe(Object.assign({}, startLabels, endLabels), value); return value; }; }; } function observe(labels) { return value => { const labelValuePair = convertLabelsAndValues(labels, value); validateLabel(this.labelNames, labels); if (!Number.isFinite(labelValuePair.value)) { throw new TypeError( `Value is not a valid number: ${util.format(labelValuePair.value)}`, ); } const hash = hashObject(labelValuePair.labels); let summaryOfLabel = this.hashMap[hash]; if (!summaryOfLabel) { summaryOfLabel = { labels: labelValuePair.labels, td: new timeWindowQuantiles(this.maxAgeSeconds, this.ageBuckets), count: 0, sum: 0, }; } summaryOfLabel.td.push(labelValuePair.value); summaryOfLabel.count++; if (summaryOfLabel.count % this.compressCount === 0) { summaryOfLabel.td.compress(); } summaryOfLabel.sum += labelValuePair.value; this.hashMap[hash] = summaryOfLabel; }; } function convertLabelsAndValues(labels, value) { if (value === undefined) { return { value: labels, labels: {}, }; } return { labels, value, }; } module.exports = Summary; prom-client-14.1.0/lib/timeWindowQuantiles.js000066400000000000000000000026231430114771000211470ustar00rootroot00000000000000'use strict'; const { TDigest } = require('tdigest'); class TimeWindowQuantiles { constructor(maxAgeSeconds, ageBuckets) { this.maxAgeSeconds = maxAgeSeconds || 0; this.ageBuckets = ageBuckets || 0; this.shouldRotate = maxAgeSeconds && ageBuckets; this.ringBuffer = Array(ageBuckets).fill(new TDigest()); this.currentBuffer = 0; this.lastRotateTimestampMillis = Date.now(); this.durationBetweenRotatesMillis = (maxAgeSeconds * 1000) / ageBuckets || Infinity; } percentile(quantile) { const bucket = rotate.call(this); return bucket.percentile(quantile); } push(value) { rotate.call(this); this.ringBuffer.forEach(bucket => { bucket.push(value); }); } reset() { this.ringBuffer.forEach(bucket => { bucket.reset(); }); } compress() { this.ringBuffer.forEach(bucket => { bucket.compress(); }); } } function rotate() { let timeSinceLastRotateMillis = Date.now() - this.lastRotateTimestampMillis; while ( timeSinceLastRotateMillis > this.durationBetweenRotatesMillis && this.shouldRotate ) { this.ringBuffer[this.currentBuffer] = new TDigest(); if (++this.currentBuffer >= this.ringBuffer.length) { this.currentBuffer = 0; } timeSinceLastRotateMillis -= this.durationBetweenRotatesMillis; this.lastRotateTimestampMillis += this.durationBetweenRotatesMillis; } return this.ringBuffer[this.currentBuffer]; } module.exports = TimeWindowQuantiles; prom-client-14.1.0/lib/util.js000066400000000000000000000043001430114771000161020ustar00rootroot00000000000000'use strict'; exports.getValueAsString = function getValueString(value) { if (Number.isNaN(value)) { return 'Nan'; } else if (!Number.isFinite(value)) { if (value < 0) { return '-Inf'; } else { return '+Inf'; } } else { return `${value}`; } }; exports.removeLabels = function removeLabels(hashMap, labels) { const hash = hashObject(labels); delete hashMap[hash]; }; exports.setValue = function setValue(hashMap, value, labels) { const hash = hashObject(labels); hashMap[hash] = { value: typeof value === 'number' ? value : 0, labels: labels || {}, }; return hashMap; }; exports.setValueDelta = function setValueDelta( hashMap, deltaValue, labels, hash = '', ) { const value = typeof deltaValue === 'number' ? deltaValue : 0; if (hashMap[hash]) { hashMap[hash].value += value; } else { hashMap[hash] = { value, labels }; } return hashMap; }; exports.getLabels = function (labelNames, args) { if (typeof args[0] === 'object') { return args[0]; } if (labelNames.length !== args.length) { throw new Error('Invalid number of arguments'); } const acc = {}; for (let i = 0; i < labelNames.length; i++) { acc[labelNames[i]] = args[i]; } return acc; }; function hashObject(labels) { // We don't actually need a hash here. We just need a string that // is unique for each possible labels object and consistent across // calls with equivalent labels objects. let keys = Object.keys(labels); if (keys.length === 0) { return ''; } // else if (keys.length > 1) { keys = keys.sort(); // need consistency across calls } let hash = ''; let i = 0; const size = keys.length; for (; i < size - 1; i++) { hash += `${keys[i]}:${labels[keys[i]]},`; } hash += `${keys[i]}:${labels[keys[i]]}`; return hash; } exports.hashObject = hashObject; exports.isObject = function isObject(obj) { return obj === Object(obj); }; class Grouper extends Map { /** * Adds the `value` to the `key`'s array of values. * @param {*} key Key to set. * @param {*} value Value to add to `key`'s array. * @returns {undefined} undefined. */ add(key, value) { if (this.has(key)) { this.get(key).push(value); } else { this.set(key, [value]); } } } exports.Grouper = Grouper; prom-client-14.1.0/lib/validation.js000066400000000000000000000013101430114771000172550ustar00rootroot00000000000000'use strict'; const util = require('util'); // These are from https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels const metricRegexp = /^[a-zA-Z_:][a-zA-Z0-9_:]*$/; const labelRegexp = /^[a-zA-Z_][a-zA-Z0-9_]*$/; exports.validateMetricName = function (name) { return metricRegexp.test(name); }; exports.validateLabelName = function (names = []) { return names.every(name => labelRegexp.test(name)); }; exports.validateLabel = function validateLabel(savedLabels, labels) { for (const label in labels) { if (!savedLabels.includes(label)) { throw new Error( `Added label "${label}" is not included in initial labelset: ${util.inspect( savedLabels, )}`, ); } } }; prom-client-14.1.0/package.json000066400000000000000000000032371430114771000163170ustar00rootroot00000000000000{ "name": "prom-client", "version": "14.1.0", "description": "Client for prometheus", "main": "index.js", "files": [ "lib/", "index.js", "index.d.ts" ], "engines": { "node": ">=10" }, "scripts": { "benchmarks": "node ./benchmarks/index.js", "test": "npm run lint && npm run compile-typescript && npm run test-unit", "lint": "eslint .", "test-unit": "jest", "compile-typescript": "tsc --project ." }, "repository": { "type": "git", "url": "git@github.com:siimon/prom-client.git" }, "keywords": [ "Prometheus", "Metrics", "Client" ], "author": "Simon Nyberg", "license": "Apache-2.0", "homepage": "https://github.com/siimon/prom-client", "devDependencies": { "@clevernature/benchmark-regression": "^1.0.0", "eslint": "^7.7.0", "eslint-config-prettier": "^6.10.0", "eslint-plugin-node": "^11.0.0", "eslint-plugin-prettier": "^3.0.1", "express": "^4.13.3", "husky": "^4.2.1", "jest": "^26.0.1", "lint-staged": "^10.0.4", "nock": "^13.0.5", "prettier": "2.0.5", "typescript": "^4.0.2" }, "dependencies": { "tdigest": "^0.1.1" }, "types": "./index.d.ts", "jest": { "testEnvironment": "node", "testRegex": ".*Test\\.js$" }, "lint-staged": { "*.js": "eslint --fix", "*.{ts,md,json,yml}": "prettier --write", ".{eslintrc,travis.yml}": "prettier --write" }, "prettier": { "singleQuote": true, "useTabs": true, "arrowParens": "avoid", "trailingComma": "all", "overrides": [ { "files": "*.md", "options": { "useTabs": false } }, { "files": ".eslintrc", "options": { "parser": "json" } } ] }, "husky": { "hooks": { "pre-commit": "lint-staged" } } } prom-client-14.1.0/test/000077500000000000000000000000001430114771000150035ustar00rootroot00000000000000prom-client-14.1.0/test/__snapshots__/000077500000000000000000000000001430114771000176215ustar00rootroot00000000000000prom-client-14.1.0/test/__snapshots__/counterTest.js.snap000066400000000000000000000010711430114771000234350ustar00rootroot00000000000000// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`counter remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; exports[`counter with params as object labels should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; exports[`counter with params as object should not be possible to decrease a counter 1`] = `"It is not possible to decrease a counter"`; exports[`counter with params as object should throw an error when the value is not a number 1`] = `"Value is not a valid number: 3ms"`; prom-client-14.1.0/test/__snapshots__/gaugeTest.js.snap000066400000000000000000000002561430114771000230520ustar00rootroot00000000000000// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`gauge global registry with parameters as object should not allow non numbers 1`] = `"Value is not a valid number: asd"`; prom-client-14.1.0/test/__snapshots__/histogramTest.js.snap000066400000000000000000000011731430114771000237560ustar00rootroot00000000000000// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`histogram with object as params with global registry labels should not allow different number of labels 1`] = `"Invalid number of arguments"`; exports[`histogram with object as params with global registry remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; exports[`histogram with object as params with global registry should not allow le as a custom label 1`] = `"le is a reserved label keyword"`; exports[`histogram with object as params with global registry should not allow non numbers 1`] = `"Value is not a valid number: asd"`; prom-client-14.1.0/test/__snapshots__/registerTest.js.snap000066400000000000000000000023641430114771000236100ustar00rootroot00000000000000// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`register should not output all initialized metrics at value 0 if labels 1`] = ` "# HELP counter help # TYPE counter counter # HELP gauge help # TYPE gauge gauge # HELP histogram help # TYPE histogram histogram # HELP summary help # TYPE summary summary " `; exports[`register should output all initialized metrics at value 0 1`] = ` "# HELP counter help # TYPE counter counter counter 0 # HELP gauge help # TYPE gauge gauge gauge 0 # HELP histogram help # TYPE histogram histogram histogram_bucket{le=\\"0.005\\"} 0 histogram_bucket{le=\\"0.01\\"} 0 histogram_bucket{le=\\"0.025\\"} 0 histogram_bucket{le=\\"0.05\\"} 0 histogram_bucket{le=\\"0.1\\"} 0 histogram_bucket{le=\\"0.25\\"} 0 histogram_bucket{le=\\"0.5\\"} 0 histogram_bucket{le=\\"1\\"} 0 histogram_bucket{le=\\"2.5\\"} 0 histogram_bucket{le=\\"5\\"} 0 histogram_bucket{le=\\"10\\"} 0 histogram_bucket{le=\\"+Inf\\"} 0 histogram_sum 0 histogram_count 0 # HELP summary help # TYPE summary summary summary{quantile=\\"0.01\\"} 0 summary{quantile=\\"0.05\\"} 0 summary{quantile=\\"0.5\\"} 0 summary{quantile=\\"0.9\\"} 0 summary{quantile=\\"0.95\\"} 0 summary{quantile=\\"0.99\\"} 0 summary{quantile=\\"0.999\\"} 0 summary_sum 0 summary_count 0 " `; prom-client-14.1.0/test/__snapshots__/summaryTest.js.snap000066400000000000000000000010101430114771000234440ustar00rootroot00000000000000// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`summary global registry with param as object labels should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; exports[`summary global registry with param as object remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; exports[`summary global registry with param as object should validate labels when observing 1`] = `"Added label \\"baz\\" is not included in initial labelset: [ 'foo' ]"`; prom-client-14.1.0/test/aggregatorsTest.js000066400000000000000000000062631430114771000205150ustar00rootroot00000000000000'use strict'; describe('aggregators', () => { const aggregators = require('../index').aggregators; const metrics = [ { help: 'metric_help', name: 'metric_name', type: 'does not matter', values: [ { labels: [], value: 1 }, { labels: ['label1'], value: 2 }, ], }, { help: 'metric_help', name: 'metric_name', type: 'does not matter', values: [ { labels: [], value: 3 }, { labels: ['label1'], value: 4 }, ], }, ]; describe('sum', () => { it('properly sums values', () => { const result = aggregators.sum(metrics); expect(result.help).toBe('metric_help'); expect(result.name).toBe('metric_name'); expect(result.type).toBe('does not matter'); expect(result.values).toEqual([ { value: 4, labels: [] }, { value: 6, labels: ['label1'] }, ]); }); }); describe('first', () => { it('takes the first value', () => { const result = aggregators.first(metrics); expect(result.help).toBe('metric_help'); expect(result.name).toBe('metric_name'); expect(result.type).toBe('does not matter'); expect(result.values).toEqual([ { value: 1, labels: [] }, { value: 2, labels: ['label1'] }, ]); }); }); describe('omit', () => { it('returns undefined', () => { const result = aggregators.omit(metrics); expect(result).toBeUndefined(); }); }); describe('average', () => { it('properly averages values', () => { const result = aggregators.average(metrics); expect(result.help).toBe('metric_help'); expect(result.name).toBe('metric_name'); expect(result.type).toBe('does not matter'); expect(result.values).toEqual([ { value: 2, labels: [] }, { value: 3, labels: ['label1'] }, ]); }); }); describe('min', () => { it('takes the minimum of the values', () => { const result = aggregators.min(metrics); expect(result.help).toBe('metric_help'); expect(result.name).toBe('metric_name'); expect(result.type).toBe('does not matter'); expect(result.values).toEqual([ { value: 1, labels: [] }, { value: 2, labels: ['label1'] }, ]); }); }); describe('max', () => { it('takes the maximum of the values', () => { const result = aggregators.max(metrics); expect(result.help).toBe('metric_help'); expect(result.name).toBe('metric_name'); expect(result.type).toBe('does not matter'); expect(result.values).toEqual([ { value: 3, labels: [] }, { value: 4, labels: ['label1'] }, ]); }); }); describe('(common)', () => { it('separates metrics by metricName', () => { const metrics2 = [ { help: 'metric_help', name: 'metric_name', type: 'does not matter', values: [{ labels: [], value: 1, metricName: 'abc' }], }, { help: 'metric_help', name: 'metric_name', type: 'does not matter', values: [{ labels: [], value: 3, metricName: 'abc' }], }, { help: 'metric_help', name: 'metric_name', type: 'does not matter', values: [{ labels: [], value: 5, metricName: 'def' }], }, ]; const result = aggregators.sum(metrics2); expect(result.values).toEqual([ { value: 4, labels: [], metricName: 'abc' }, { value: 5, labels: [], metricName: 'def' }, ]); }); }); }); prom-client-14.1.0/test/bucketGeneratorsTest.js000066400000000000000000000034751430114771000215210ustar00rootroot00000000000000'use strict'; describe('bucketGenerators', () => { const linearBuckets = require('../index').linearBuckets; const exponentialBuckets = require('../index').exponentialBuckets; let result; describe('linear buckets', () => { beforeEach(() => { result = linearBuckets(0, 50, 10); }); it('should start on 0', () => { expect(result[0]).toEqual(0); }); it('should return 10 buckets', () => { expect(result).toHaveLength(10); }); it('should have width 50 between buckets', () => { expect(result[1] - result[0]).toEqual(50); expect(result[9] - result[8]).toEqual(50); expect(result[4] - result[3]).toEqual(50); }); it('should not allow negative count', () => { const fn = function () { linearBuckets(2, 1, 0); }; expect(fn).toThrowError(Error); }); it('should not propagate rounding errors', () => { result = linearBuckets(0.1, 0.1, 10); expect(result[9]).toEqual(1); }); }); describe('exponential buckets', () => { beforeEach(() => { result = exponentialBuckets(1, 2, 5); }); it('should start at start value', () => { expect(result[0]).toEqual(1); }); it('should return 5 items', () => { expect(result).toHaveLength(5); }); it('should increment with a factor of 2', () => { expect(result[1] / result[0]).toEqual(2); expect(result[3] / result[2]).toEqual(2); }); it('should not allow factor of equal or less than 1', () => { const fn = function () { exponentialBuckets(1, 1, 5); }; expect(fn).toThrowError(Error); }); it('should not allow negative start', () => { const fn = function () { exponentialBuckets(0, 1, 5); }; expect(fn).toThrowError(Error); }); it('should not allow negative count', () => { const fn = function () { exponentialBuckets(2, 10, 0); }; expect(fn).toThrowError(Error); }); }); }); prom-client-14.1.0/test/clusterTest.js000066400000000000000000000146201430114771000176650ustar00rootroot00000000000000'use strict'; const cluster = require('cluster'); const process = require('process'); describe('AggregatorRegistry', () => { it('requiring the cluster should not add any listeners on the cluster module', () => { const originalListenerCount = cluster.listenerCount('message'); require('../lib/cluster'); expect(cluster.listenerCount('message')).toBe(originalListenerCount); jest.resetModules(); require('../lib/cluster'); expect(cluster.listenerCount('message')).toBe(originalListenerCount); }); it('requiring the cluster should not add any listeners on the process module', () => { const originalListenerCount = process.listenerCount('message'); require('../lib/cluster'); expect(process.listenerCount('message')).toBe(originalListenerCount); jest.resetModules(); require('../lib/cluster'); expect(process.listenerCount('message')).toBe(originalListenerCount); }); describe('aggregatorRegistry.clusterMetrics()', () => { it('works properly if there are no cluster workers', async () => { const AggregatorRegistry = require('../lib/cluster'); const ar = new AggregatorRegistry(); const metrics = await ar.clusterMetrics(); expect(metrics).toEqual(''); }); }); describe('AggregatorRegistry.aggregate()', () => { const Registry = require('../lib/cluster'); // These mimic the output of `getMetricsAsJSON`. const metricsArr1 = [ { name: 'test_histogram', help: 'Example of a histogram', type: 'histogram', values: [ { labels: { le: 0.1, code: '300' }, value: 0, metricName: 'test_histogram_bucket', }, { labels: { le: 10, code: '300' }, value: 1.6486727018068046, metricName: 'test_histogram_bucket', }, ], aggregator: 'sum', }, { help: 'Example of a gauge', name: 'test_gauge', type: 'gauge', values: [ { value: 0.47, labels: { method: 'get', code: 200 } }, { value: 0.64, labels: {} }, { value: 23, labels: { method: 'post', code: '300' } }, ], aggregator: 'sum', }, { help: 'Start time of the process since unix epoch in seconds.', name: 'process_start_time_seconds', type: 'gauge', values: [{ value: 1502075832, labels: {} }], aggregator: 'omit', }, { help: 'Lag of event loop in seconds.', name: 'nodejs_eventloop_lag_seconds', type: 'gauge', values: [{ value: 0.009, labels: {} }], aggregator: 'average', }, { help: 'Node.js version info.', name: 'nodejs_version_info', type: 'gauge', values: [ { value: 1, labels: { version: 'v6.11.1', major: 6, minor: 11, patch: 1 }, }, ], aggregator: 'first', }, ]; const metricsArr2 = [ { name: 'test_histogram', help: 'Example of a histogram', type: 'histogram', values: [ { labels: { le: 0.1, code: '300' }, value: 0.235151, metricName: 'test_histogram_bucket', }, { labels: { le: 10, code: '300' }, value: 1.192591, metricName: 'test_histogram_bucket', }, ], aggregator: 'sum', }, { help: 'Example of a gauge', name: 'test_gauge', type: 'gauge', values: [ { value: 0.02, labels: { method: 'get', code: 200 } }, { value: 0.24, labels: {} }, { value: 51, labels: { method: 'post', code: '300' } }, ], aggregator: 'sum', }, { help: 'Start time of the process since unix epoch in seconds.', name: 'process_start_time_seconds', type: 'gauge', values: [{ value: 1502075849, labels: {} }], aggregator: 'omit', }, { help: 'Lag of event loop in seconds.', name: 'nodejs_eventloop_lag_seconds', type: 'gauge', values: [{ value: 0.008, labels: {} }], aggregator: 'average', }, { help: 'Node.js version info.', name: 'nodejs_version_info', type: 'gauge', values: [ { value: 1, labels: { version: 'v6.11.1', major: 6, minor: 11, patch: 1 }, }, ], aggregator: 'first', }, ]; const aggregated = Registry.aggregate([metricsArr1, metricsArr2]); it('defaults to summation, preserves histogram bins', async () => { const histogram = aggregated.getSingleMetric('test_histogram').get(); expect(histogram).toEqual({ name: 'test_histogram', help: 'Example of a histogram', type: 'histogram', values: [ { labels: { le: 0.1, code: '300' }, value: 0.235151, metricName: 'test_histogram_bucket', }, { labels: { le: 10, code: '300' }, value: 2.8412637018068043, metricName: 'test_histogram_bucket', }, ], aggregator: 'sum', }); }); it('defaults to summation, works for gauges', () => { const gauge = aggregated.getSingleMetric('test_gauge').get(); expect(gauge).toEqual({ help: 'Example of a gauge', name: 'test_gauge', type: 'gauge', values: [ { value: 0.49, labels: { method: 'get', code: 200 } }, { value: 0.88, labels: {} }, { value: 74, labels: { method: 'post', code: '300' } }, ], aggregator: 'sum', }); }); it('uses `aggregate` method defined for process_start_time', () => { const procStartTime = aggregated.getSingleMetric( 'process_start_time_seconds', ); expect(procStartTime).toBeUndefined(); }); it('uses `aggregate` method defined for nodejs_eventloop_lag_seconds', () => { const ell = aggregated .getSingleMetric('nodejs_eventloop_lag_seconds') .get(); expect(ell).toEqual({ help: 'Lag of event loop in seconds.', name: 'nodejs_eventloop_lag_seconds', type: 'gauge', values: [{ value: 0.0085, labels: {} }], aggregator: 'average', }); }); it('uses `aggregate` method defined for nodejs_evnetloop_lag_seconds', () => { const ell = aggregated .getSingleMetric('nodejs_eventloop_lag_seconds') .get(); expect(ell).toEqual({ help: 'Lag of event loop in seconds.', name: 'nodejs_eventloop_lag_seconds', type: 'gauge', values: [{ value: 0.0085, labels: {} }], aggregator: 'average', }); }); it('uses `aggregate` method defined for nodejs_version_info', () => { const version = aggregated.getSingleMetric('nodejs_version_info').get(); expect(version).toEqual({ help: 'Node.js version info.', name: 'nodejs_version_info', type: 'gauge', values: [ { value: 1, labels: { version: 'v6.11.1', major: 6, minor: 11, patch: 1 }, }, ], aggregator: 'first', }); }); }); }); prom-client-14.1.0/test/counterTest.js000066400000000000000000000151001430114771000176550ustar00rootroot00000000000000'use strict'; describe('counter', () => { const Counter = require('../index').Counter; const Registry = require('../index').Registry; const globalRegistry = require('../index').register; let instance; describe('with params as object', () => { beforeEach(() => { instance = new Counter({ name: 'gauge_test', help: 'test' }); }); afterEach(() => { globalRegistry.clear(); }); it('should increment counter', async () => { instance.inc(); expect((await instance.get()).values[0].value).toEqual(1); instance.inc(); expect((await instance.get()).values[0].value).toEqual(2); instance.inc(0); expect((await instance.get()).values[0].value).toEqual(2); }); it('should increment with a provided value', async () => { instance.inc(100); expect((await instance.get()).values[0].value).toEqual(100); }); it('should not be possible to decrease a counter', () => { const fn = function () { instance.inc(-100); }; expect(fn).toThrowErrorMatchingSnapshot(); }); it('should throw an error when the value is not a number', () => { const fn = () => { instance.inc('3ms'); }; expect(fn).toThrowErrorMatchingSnapshot(); }); it('should handle incrementing with 0', async () => { instance.inc(0); expect((await instance.get()).values[0].value).toEqual(0); }); it('should init counter to 0', async () => { const values = (await instance.get()).values; expect(values).toHaveLength(1); expect(values[0].value).toEqual(0); }); describe('labels', () => { beforeEach(() => { instance = new Counter({ name: 'gauge_test_2', help: 'help', labelNames: ['method', 'endpoint'], }); }); it('should handle 1 value per label', async () => { instance.labels('GET', '/test').inc(); instance.labels('POST', '/test').inc(); const values = (await instance.get()).values; expect(values).toHaveLength(2); }); it('should handle labels provided as an object', async () => { instance.labels({ method: 'POST', endpoint: '/test' }).inc(); const values = (await instance.get()).values; expect(values).toHaveLength(1); expect(values[0].labels).toEqual({ method: 'POST', endpoint: '/test', }); }); it('should handle labels which are provided as arguments to inc()', async () => { instance.inc({ method: 'GET', endpoint: '/test' }); instance.inc({ method: 'POST', endpoint: '/test' }); const values = (await instance.get()).values; expect(values).toHaveLength(2); }); it('should throw error if label lengths does not match', () => { const fn = function () { instance.labels('GET').inc(); }; expect(fn).toThrowErrorMatchingSnapshot(); }); it('should increment label value with provided value', async () => { instance.labels('GET', '/test').inc(100); const values = (await instance.get()).values; expect(values[0].value).toEqual(100); }); }); }); describe('remove', () => { beforeEach(() => { instance = new Counter({ name: 'gauge_test_3', help: 'help', labelNames: ['method', 'endpoint'], }); instance.inc({ method: 'GET', endpoint: '/test' }); instance.inc({ method: 'POST', endpoint: '/test' }); }); afterEach(() => { globalRegistry.clear(); }); it('should remove matching label', async () => { instance.remove('POST', '/test'); const values = (await instance.get()).values; expect(values).toHaveLength(1); expect(values[0].value).toEqual(1); expect(values[0].labels.method).toEqual('GET'); expect(values[0].labels.endpoint).toEqual('/test'); expect(values[0].timestamp).toEqual(undefined); }); it('should remove by labels object', async () => { instance.remove({ method: 'POST', endpoint: '/test' }); const values = (await instance.get()).values; expect(values).toHaveLength(1); expect(values[0].labels).toEqual({ method: 'GET', endpoint: '/test', }); }); it('should remove all labels', async () => { instance.remove('GET', '/test'); instance.remove('POST', '/test'); expect((await instance.get()).values).toHaveLength(0); }); it('should throw error if label lengths does not match', () => { const fn = function () { instance.remove('GET'); }; expect(fn).toThrowErrorMatchingSnapshot(); }); }); describe('without registry', () => { beforeEach(() => { instance = new Counter({ name: 'gauge_test', help: 'test', registers: [], }); }); it('should increment counter', async () => { instance.inc(); expect((await globalRegistry.getMetricsAsJSON()).length).toEqual(0); expect((await instance.get()).values[0].value).toEqual(1); expect((await instance.get()).values[0].timestamp).toEqual(undefined); }); }); describe('registry instance', () => { let registryInstance; beforeEach(() => { registryInstance = new Registry(); instance = new Counter({ name: 'gauge_test', help: 'test', registers: [registryInstance], }); }); it('should increment counter', async () => { instance.inc(); expect((await globalRegistry.getMetricsAsJSON()).length).toEqual(0); expect((await registryInstance.getMetricsAsJSON()).length).toEqual(1); expect((await instance.get()).values[0].value).toEqual(1); expect((await instance.get()).values[0].timestamp).toEqual(undefined); }); }); describe('counter reset', () => { afterEach(() => { globalRegistry.clear(); }); it('should reset labelless counter', async () => { const instance = new Counter({ name: 'test_metric', help: 'Another test metric', }); instance.inc(12); expect((await instance.get()).values[0].value).toEqual(12); instance.reset(); expect((await instance.get()).values[0].value).toEqual(0); instance.inc(10); expect((await instance.get()).values[0].value).toEqual(10); }); it('should reset the counter, incl labels', async () => { const instance = new Counter({ name: 'test_metric', help: 'Another test metric', labelNames: ['serial', 'active'], }); instance.inc({ serial: '12345', active: 'yes' }, 12); expect((await instance.get()).values[0].value).toEqual(12); expect((await instance.get()).values[0].labels.serial).toEqual('12345'); expect((await instance.get()).values[0].labels.active).toEqual('yes'); instance.reset(); expect((await instance.get()).values).toEqual([]); instance.inc({ serial: '12345', active: 'no' }, 10); expect((await instance.get()).values[0].value).toEqual(10); expect((await instance.get()).values[0].labels.serial).toEqual('12345'); expect((await instance.get()).values[0].labels.active).toEqual('no'); }); }); }); prom-client-14.1.0/test/defaultMetricsTest.js000066400000000000000000000060371430114771000211620ustar00rootroot00000000000000'use strict'; describe('collectDefaultMetrics', () => { const register = require('../index').register; const Registry = require('../index').Registry; const collectDefaultMetrics = require('../index').collectDefaultMetrics; let cpuUsage; beforeAll(() => { cpuUsage = process.cpuUsage; if (cpuUsage) { Object.defineProperty(process, 'cpuUsage', { value() { return { user: 1000, system: 10 }; }, }); } else { process.cpuUsage = function () { return { user: 1000, system: 10 }; }; } register.clear(); }); afterAll(() => { if (cpuUsage) { Object.defineProperty(process, 'cpuUsage', { value: cpuUsage, }); } else { delete process.cpuUsage; } }); afterEach(() => { register.clear(); }); it('should add metrics to the registry', async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); collectDefaultMetrics(); expect(await register.getMetricsAsJSON()).not.toHaveLength(0); }); it('should allow blacklisting all metrics', async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); clearInterval(collectDefaultMetrics()); register.clear(); expect(await register.getMetricsAsJSON()).toHaveLength(0); }); it('should prefix metric names when configured', async () => { collectDefaultMetrics({ prefix: 'some_prefix_' }); expect(await register.getMetricsAsJSON()).not.toHaveLength(0); for (const metric of await register.getMetricsAsJSON()) { expect(metric.name.substring(0, 12)).toEqual('some_prefix_'); } }); it('should apply labels to metrics when configured', async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); const labels = { NODE_APP_INSTANCE: 0 }; collectDefaultMetrics({ labels }); const metrics = await register.getMetricsAsJSON(); // flatten metric values into a single array const allMetricValues = metrics.reduce( (previous, metric) => previous.concat(metric.values), [], ); // this varies between 45 and 47 depending on node handles - we just wanna // assert there's at least one so we know the assertions in the loop below // are executed expect(allMetricValues.length).toBeGreaterThan(0); allMetricValues.forEach(metricValue => { expect(metricValue.labels).toMatchObject(labels); }); }); describe('disabling', () => { it('should not throw error', () => { const fn = function () { register.clear(); }; expect(fn).not.toThrowError(Error); }); }); describe('custom registry', () => { it('should allow to register metrics to custom registry', async () => { const registry = new Registry(); expect(await register.getMetricsAsJSON()).toHaveLength(0); expect(await registry.getMetricsAsJSON()).toHaveLength(0); collectDefaultMetrics(); expect(await register.getMetricsAsJSON()).not.toHaveLength(0); expect(await registry.getMetricsAsJSON()).toHaveLength(0); collectDefaultMetrics({ register: registry }); expect(await register.getMetricsAsJSON()).not.toHaveLength(0); expect(await registry.getMetricsAsJSON()).not.toHaveLength(0); }); }); }); prom-client-14.1.0/test/gaugeTest.js000066400000000000000000000172631430114771000173020ustar00rootroot00000000000000'use strict'; describe('gauge', () => { const Gauge = require('../index').Gauge; const Registry = require('../index').Registry; const globalRegistry = require('../index').register; let instance; describe('global registry', () => { afterEach(() => { globalRegistry.clear(); }); describe('with parameters as object', () => { beforeEach(() => { instance = new Gauge({ name: 'gauge_test', help: 'help' }); instance.set(10); }); it('should set a gauge to provided value', async () => { await expectValue(10); }); it('should increase with 1 if no param provided', async () => { instance.inc(); await expectValue(11); }); it('should increase with param value if provided', async () => { instance.inc(5); await expectValue(15); }); it('should decrease with 1 if no param provided', async () => { instance.dec(); await expectValue(9); }); it('should decrease with param if provided', async () => { instance.dec(5); await expectValue(5); }); it('should set to exactly zero without defaulting to 1', async () => { instance.set(0); await expectValue(0); }); it('should inc by zero without defaulting to 1', async () => { instance.inc(0); await expectValue(10); }); it('should start a timer and set a gauge to elapsed in seconds', async () => { jest.useFakeTimers('modern'); jest.setSystemTime(0); const doneFn = instance.startTimer(); jest.advanceTimersByTime(500); const dur = doneFn(); await expectValue(0.5); expect(dur).toEqual(0.5); jest.useRealTimers(); }); it('should set to current time', async () => { jest.useFakeTimers('modern'); jest.setSystemTime(0); instance.setToCurrentTime(); await expectValue(Date.now()); jest.useRealTimers(); }); it('should not allow non numbers', () => { const fn = function () { instance.set('asd'); }; expect(fn).toThrowErrorMatchingSnapshot(); }); it('should init to 0', async () => { instance = new Gauge({ name: 'init_gauge', help: 'somehelp', }); await expectValue(0); }); describe('with labels', () => { beforeEach(() => { instance = new Gauge({ name: 'name', help: 'help', labelNames: ['code'], }); instance.set({ code: '200' }, 20); }); it('should be able to increment', async () => { instance.labels('200').inc(); await expectValue(21); }); it('should be able to decrement', async () => { instance.labels('200').dec(); await expectValue(19); }); it('should be able to set value', async () => { instance.labels('200').set(500); await expectValue(500); }); it('should be able to set value to current time', async () => { jest.useFakeTimers('modern'); jest.setSystemTime(0); instance.labels('200').setToCurrentTime(); await expectValue(Date.now()); jest.useRealTimers(); }); it('should be able to start a timer', async () => { jest.useFakeTimers('modern'); jest.setSystemTime(0); const end = instance.labels('200').startTimer(); jest.advanceTimersByTime(1000); end(); await expectValue(1); jest.useRealTimers(); }); it('should be able to start a timer and set labels afterwards', async () => { jest.useFakeTimers('modern'); jest.setSystemTime(0); const end = instance.startTimer(); jest.advanceTimersByTime(1000); end({ code: 200 }); await expectValue(1); jest.useRealTimers(); }); it('should allow labels before and after timers', async () => { instance = new Gauge({ name: 'name_2', help: 'help', labelNames: ['code', 'success'], }); jest.useFakeTimers('modern'); jest.setSystemTime(0); const end = instance.startTimer({ code: 200 }); jest.advanceTimersByTime(1000); end({ success: 'SUCCESS' }); await expectValue(1); jest.useRealTimers(); }); it('should not mutate passed startLabels', () => { const startLabels = { code: '200' }; const end = instance.startTimer(startLabels); end({ code: '400' }); expect(startLabels).toEqual({ code: '200' }); }); it('should handle labels provided as an object', async () => { instance.labels({ code: '200' }).inc(); const values = (await instance.get()).values; expect(values).toHaveLength(1); expect(values[0].labels).toEqual({ code: '200' }); }); }); describe('with remove', () => { beforeEach(() => { instance = new Gauge({ name: 'name', help: 'help', labelNames: ['code'], }); instance.set({ code: '200' }, 20); instance.set({ code: '400' }, 0); }); it('should be able to remove matching label', async () => { instance.remove('200'); const values = (await instance.get()).values; expect(values.length).toEqual(1); expect(values[0].labels.code).toEqual('400'); expect(values[0].value).toEqual(0); }); it('should remove by labels object', async () => { instance.remove({ code: '200' }); const values = (await instance.get()).values; expect(values).toHaveLength(1); expect(values[0].labels).toEqual({ code: '400' }); expect(values[0].value).toEqual(0); }); it('should be able to remove all labels', async () => { instance.remove('200'); instance.remove('400'); expect((await instance.get()).values.length).toEqual(0); }); }); }); }); describe('without registry', () => { afterEach(() => { globalRegistry.clear(); }); beforeEach(() => { instance = new Gauge({ name: 'gauge_test', help: 'help', registers: [] }); instance.set(10); }); it('should set a gauge to provided value', async () => { await expectValue(10); expect((await globalRegistry.getMetricsAsJSON()).length).toEqual(0); }); }); describe('registry instance', () => { let registryInstance; beforeEach(() => { registryInstance = new Registry(); instance = new Gauge({ name: 'gauge_test', help: 'help', registers: [registryInstance], }); instance.set(10); }); it('should set a gauge to provided value', async () => { expect((await globalRegistry.getMetricsAsJSON()).length).toEqual(0); expect((await registryInstance.getMetricsAsJSON()).length).toEqual(1); await expectValue(10); }); }); describe('gauge reset', () => { afterEach(() => { globalRegistry.clear(); }); it('should reset labelless gauge', async () => { const instance = new Gauge({ name: 'test_metric', help: 'Another test metric', }); instance.set(12); expect((await instance.get()).values[0].value).toEqual(12); instance.reset(); expect((await instance.get()).values[0].value).toEqual(0); instance.set(10); expect((await instance.get()).values[0].value).toEqual(10); }); it('should reset the gauge, incl labels', async () => { const instance = new Gauge({ name: 'test_metric', help: 'Another test metric', labelNames: ['serial', 'active'], }); instance.set({ serial: '12345', active: 'yes' }, 12); expect((await instance.get()).values[0].value).toEqual(12); expect((await instance.get()).values[0].labels.serial).toEqual('12345'); expect((await instance.get()).values[0].labels.active).toEqual('yes'); instance.reset(); expect((await instance.get()).values).toEqual([]); instance.set({ serial: '12345', active: 'no' }, 10); expect((await instance.get()).values[0].value).toEqual(10); expect((await instance.get()).values[0].labels.serial).toEqual('12345'); expect((await instance.get()).values[0].labels.active).toEqual('no'); }); }); async function expectValue(val) { expect((await instance.get()).values[0].value).toEqual(val); } }); prom-client-14.1.0/test/histogramTest.js000066400000000000000000000327661430114771000202140ustar00rootroot00000000000000'use strict'; describe('histogram', () => { const Histogram = require('../index').Histogram; const Registry = require('../index').Registry; const globalRegistry = require('../index').register; let instance; afterEach(() => { instance = null; globalRegistry.clear(); }); describe('with object as params', () => { describe('with global registry', () => { beforeEach(() => { instance = new Histogram({ name: 'test_histogram', help: 'test' }); }); it('should increase count', async () => { instance.observe(0.5); const valuePair = getValueByName( 'test_histogram_count', (await instance.get()).values, ); expect(valuePair.value).toEqual(1); }); it('should be able to observe 0s', async () => { instance.observe(0); const valuePair = getValueByLabel(0.005, (await instance.get()).values); expect(valuePair.value).toEqual(1); }); it('should increase sum', async () => { instance.observe(0.5); const valuePair = getValueByName( 'test_histogram_sum', (await instance.get()).values, ); expect(valuePair.value).toEqual(0.5); }); it('should add item in upper bound bucket', async () => { instance.observe(1); const valuePair = getValueByLabel(1, (await instance.get()).values); expect(valuePair.value).toEqual(1); }); it('should be able to monitor more than one item', async () => { instance.observe(0.05); instance.observe(5); const firstValuePair = getValueByLabel( 0.05, (await instance.get()).values, ); const secondValuePair = getValueByLabel( 5, (await instance.get()).values, ); expect(firstValuePair.value).toEqual(1); expect(secondValuePair.value).toEqual(2); }); it('should add a +Inf bucket with the same value as count', async () => { instance.observe(10); const countValuePair = getValueByName( 'test_histogram_count', (await instance.get()).values, ); const infValuePair = getValueByLabel( '+Inf', (await instance.get()).values, ); expect(infValuePair.value).toEqual(countValuePair.value); }); it('should add buckets in increasing numerical order', async () => { const histogram = new Histogram({ name: 'test_histogram_2', help: 'test', buckets: [1, 5], }); histogram.observe(1.5); const values = (await histogram.get()).values; expect(values[0].labels.le).toEqual(1); expect(values[1].labels.le).toEqual(5); expect(values[2].labels.le).toEqual('+Inf'); }); it('should group counts on each label set', async () => { const histogram = new Histogram({ name: 'test_histogram_2', help: 'test', labelNames: ['code'], }); histogram.observe({ code: '200' }, 1); histogram.observe({ code: '300' }, 1); const values = getValuesByLabel(1, (await histogram.get()).values); expect(values[0].value).toEqual(1); expect(values[1].value).toEqual(1); }); it('should time requests', async () => { jest.useFakeTimers('modern'); jest.setSystemTime(0); const doneFn = instance.startTimer(); jest.advanceTimersByTime(500); doneFn(); const valuePair = getValueByLabel(0.5, (await instance.get()).values); expect(valuePair.value).toEqual(1); jest.useRealTimers(); }); it('should time requests, end function should return time spent value', () => { jest.useFakeTimers('modern'); jest.setSystemTime(0); const doneFn = instance.startTimer(); jest.advanceTimersByTime(500); const value = doneFn(); expect(value).toEqual(0.5); jest.useRealTimers(); }); it('should not allow non numbers', () => { const fn = function () { instance.observe('asd'); }; expect(fn).toThrowErrorMatchingSnapshot(); }); it('should allow custom labels', async () => { const i = new Histogram({ name: 'histo', help: 'help', labelNames: ['code'], }); i.observe({ code: 'test' }, 1); const pair = getValueByLeAndLabel( 1, 'code', 'test', (await i.get()).values, ); expect(pair.value).toEqual(1); }); it('should not allow le as a custom label', () => { const fn = function () { new Histogram({ name: 'name', help: 'help', labelNames: ['le'] }); }; expect(fn).toThrowErrorMatchingSnapshot(); }); it('should observe value if outside most upper bound', async () => { instance.observe(100000); const values = (await instance.get()).values; const count = getValueByLabel('+Inf', values, 'le'); expect(count.value).toEqual(1); }); it('should allow to be reset itself', async () => { instance.observe(0.5); let valuePair = getValueByName( 'test_histogram_count', (await instance.get()).values, ); expect(valuePair.value).toEqual(1); instance.reset(); valuePair = getValueByName( 'test_histogram_count', (await instance.get()).values, ); expect(valuePair.value).toEqual(undefined); }); it('should init to 0', async () => { (await instance.get()).values.forEach(bucket => { expect(bucket.value).toEqual(0); }); }); describe('labels', () => { beforeEach(() => { instance = new Histogram({ name: 'histogram_labels', help: 'Histogram with labels fn', labelNames: ['method'], }); }); it('should observe', async () => { instance.labels('get').observe(4); const res = getValueByLeAndLabel( 5, 'method', 'get', (await instance.get()).values, ); expect(res.value).toEqual(1); }); it('should not allow different number of labels', () => { const fn = function () { instance.labels('get', '500').observe(4); }; expect(fn).toThrowErrorMatchingSnapshot(); }); it('should start a timer', async () => { jest.useFakeTimers('modern'); jest.setSystemTime(0); const end = instance.labels('get').startTimer(); jest.advanceTimersByTime(500); end(); const res = getValueByLeAndLabel( 0.5, 'method', 'get', (await instance.get()).values, ); expect(res.value).toEqual(1); jest.useRealTimers(); }); it('should start a timer and set labels afterwards', async () => { jest.useFakeTimers('modern'); jest.setSystemTime(0); const end = instance.startTimer(); jest.advanceTimersByTime(500); end({ method: 'get' }); const res = getValueByLeAndLabel( 0.5, 'method', 'get', (await instance.get()).values, ); expect(res.value).toEqual(1); jest.useRealTimers(); }); it('should allow labels before and after timers', async () => { instance = new Histogram({ name: 'histogram_labels_2', help: 'Histogram with labels fn', labelNames: ['method', 'success'], }); jest.useFakeTimers('modern'); jest.setSystemTime(0); const end = instance.startTimer({ method: 'get' }); jest.advanceTimersByTime(500); end({ success: 'SUCCESS' }); const res1 = getValueByLeAndLabel( 0.5, 'method', 'get', (await instance.get()).values, ); const res2 = getValueByLeAndLabel( 0.5, 'success', 'SUCCESS', (await instance.get()).values, ); expect(res1.value).toEqual(1); expect(res2.value).toEqual(1); jest.useRealTimers(); }); it('should not mutate passed startLabels', () => { const startLabels = { method: 'GET' }; const end = instance.startTimer(startLabels); end({ method: 'POST' }); expect(startLabels).toEqual({ method: 'GET' }); }); it('should handle labels provided as an object', async () => { instance.labels({ method: 'GET' }).startTimer()(); const values = (await instance.get()).values; values.forEach(value => { expect(value.labels.method).toBe('GET'); }); }); }); describe('zero', () => { beforeEach(() => { instance = new Histogram({ name: 'histogram_labels', help: 'Histogram with labels fn', labelNames: ['method'], }); }); it('should zero the given label', async () => { instance.zero({ method: 'POST' }); const values = getValuesByLabel( 'POST', (await instance.get()).values, 'method', ); values.forEach(bucket => { expect(bucket.value).toEqual(0); }); }); it('should export the metric after zeroing', async () => { instance.zero({ method: 'POST' }); const values = getValuesByLabel( 'POST', (await instance.get()).values, 'method', ); expect(values).not.toHaveLength(0); }); it('should not duplicate the metric', async () => { instance.zero({ method: 'POST' }); instance.observe({ method: 'POST' }, 1); const values = getValuesByName( 'histogram_labels_count', (await instance.get()).values, ); expect(values).toHaveLength(1); }); }); describe('remove', () => { beforeEach(() => { instance = new Histogram({ name: 'histogram_labels', help: 'Histogram with labels fn', labelNames: ['method'], }); }); it('should remove matching label', async () => { instance.labels('POST').observe(3); instance.labels('GET').observe(4); instance.remove('POST'); const res = getValueByLeAndLabel( 5, 'method', 'GET', (await instance.get()).values, ); expect(res.value).toEqual(1); }); it('should remove all labels', async () => { instance.labels('POST').observe(3); instance.labels('GET').observe(4); instance.remove('POST'); instance.remove('GET'); expect((await instance.get()).values).toHaveLength(0); }); it('should throw error if label lengths does not match', () => { const fn = function () { instance.remove('GET', '/foo'); }; expect(fn).toThrowErrorMatchingSnapshot(); }); it('should remove timer labels', async () => { jest.useFakeTimers('modern'); jest.setSystemTime(0); const getEnd = instance.labels('GET').startTimer(); const postEnd = instance.labels('POST').startTimer(); jest.advanceTimersByTime(500); postEnd(); getEnd(); instance.remove('POST'); const res = getValueByLeAndLabel( 0.5, 'method', 'GET', (await instance.get()).values, ); expect(res.value).toEqual(1); jest.useRealTimers(); }); it('should remove timer labels when labels are set afterwards', async () => { jest.useFakeTimers('modern'); jest.setSystemTime(0); const end = instance.startTimer(); jest.advanceTimersByTime(500); end({ method: 'GET' }); instance.remove('GET'); expect((await instance.get()).values).toHaveLength(0); jest.useRealTimers(); }); it('should remove labels before and after timers', async () => { instance = new Histogram({ name: 'histogram_labels_2', help: 'Histogram with labels fn', labelNames: ['method', 'success'], }); jest.useFakeTimers('modern'); jest.setSystemTime(0); const end = instance.startTimer({ method: 'GET' }); jest.advanceTimersByTime(500); end({ success: 'SUCCESS' }); instance.remove('GET', 'SUCCESS'); expect((await instance.get()).values).toHaveLength(0); jest.useRealTimers(); }); it('should remove by labels object', async () => { instance.observe({ method: 'GET' }, 1); instance.remove({ method: 'GET' }); expect((await instance.get()).values).toHaveLength(0); }); }); }); describe('without registry', () => { beforeEach(() => { instance = new Histogram({ name: 'test_histogram', help: 'test', registers: [], }); }); it('should increase count', async () => { instance.observe(0.5); const valuePair = getValueByName( 'test_histogram_count', (await instance.get()).values, ); expect(valuePair.value).toEqual(1); expect((await globalRegistry.getMetricsAsJSON()).length).toEqual(0); }); }); describe('registry instance', () => { let registryInstance; beforeEach(() => { registryInstance = new Registry(); instance = new Histogram({ name: 'test_histogram', help: 'test', registers: [registryInstance], }); }); it('should increment counter', async () => { instance.observe(0.5); const valuePair = getValueByName( 'test_histogram_count', (await instance.get()).values, ); expect(valuePair.value).toEqual(1); expect((await globalRegistry.getMetricsAsJSON()).length).toEqual(0); expect((await registryInstance.getMetricsAsJSON()).length).toEqual(1); }); }); }); function getValueByName(name, values) { return ( values.length > 0 && values.reduce((acc, val) => { if (val.metricName === name) { acc = val; } return acc; }) ); } function getValuesByName(name, values) { return values.reduce((acc, val) => { if (val.metricName === name) { acc.push(val); } return acc; }, []); } function getValueByLeAndLabel(le, key, label, values) { return values.reduce((acc, val) => { if (val.labels && val.labels.le === le && val.labels[key] === label) { acc = val; } return acc; }, {}); } function getValueByLabel(label, values, key) { return values.reduce((acc, val) => { if (val.labels && val.labels[key || 'le'] === label) { acc = val; } return acc; }, {}); } function getValuesByLabel(label, values, key) { return values.reduce((acc, val) => { if (val.labels && val.labels[key || 'le'] === label) { acc.push(val); } return acc; }, []); } }); prom-client-14.1.0/test/metrics/000077500000000000000000000000001430114771000164515ustar00rootroot00000000000000prom-client-14.1.0/test/metrics/eventLoopLagTest.js000066400000000000000000000043121430114771000222460ustar00rootroot00000000000000'use strict'; describe('eventLoopLag', () => { const register = require('../../index').register; const eventLoopLag = require('../../lib/metrics/eventLoopLag'); beforeAll(() => { register.clear(); }); afterEach(() => { register.clear(); }); it('should add metric to the registry', async done => { expect(await register.getMetricsAsJSON()).toHaveLength(0); eventLoopLag(); await wait(5); const metrics = await register.getMetricsAsJSON(); expect(metrics).toHaveLength(8); expect(metrics[0].help).toEqual('Lag of event loop in seconds.'); expect(metrics[0].type).toEqual('gauge'); expect(metrics[0].name).toEqual('nodejs_eventloop_lag_seconds'); expect(metrics[1].help).toEqual('The minimum recorded event loop delay.'); expect(metrics[1].type).toEqual('gauge'); expect(metrics[1].name).toEqual('nodejs_eventloop_lag_min_seconds'); expect(metrics[2].help).toEqual('The maximum recorded event loop delay.'); expect(metrics[2].type).toEqual('gauge'); expect(metrics[2].name).toEqual('nodejs_eventloop_lag_max_seconds'); expect(metrics[3].help).toEqual( 'The mean of the recorded event loop delays.', ); expect(metrics[3].type).toEqual('gauge'); expect(metrics[3].name).toEqual('nodejs_eventloop_lag_mean_seconds'); expect(metrics[4].help).toEqual( 'The standard deviation of the recorded event loop delays.', ); expect(metrics[4].type).toEqual('gauge'); expect(metrics[4].name).toEqual('nodejs_eventloop_lag_stddev_seconds'); expect(metrics[5].help).toEqual( 'The 50th percentile of the recorded event loop delays.', ); expect(metrics[5].type).toEqual('gauge'); expect(metrics[5].name).toEqual('nodejs_eventloop_lag_p50_seconds'); expect(metrics[6].help).toEqual( 'The 90th percentile of the recorded event loop delays.', ); expect(metrics[6].type).toEqual('gauge'); expect(metrics[6].name).toEqual('nodejs_eventloop_lag_p90_seconds'); expect(metrics[7].help).toEqual( 'The 99th percentile of the recorded event loop delays.', ); expect(metrics[7].type).toEqual('gauge'); expect(metrics[7].name).toEqual('nodejs_eventloop_lag_p99_seconds'); done(); }); }); async function wait(ms) { await new Promise(resolve => setTimeout(resolve, ms)); } prom-client-14.1.0/test/metrics/gcTest.js000066400000000000000000000017231430114771000202430ustar00rootroot00000000000000'use strict'; describe('gc', () => { const register = require('../../index').register; const processHandles = require('../../lib/metrics/gc'); beforeAll(() => { register.clear(); }); afterEach(() => { register.clear(); }); it('should add metric to the registry', async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); processHandles(); const metrics = await register.getMetricsAsJSON(); // Check if perf_hooks module is available let perf_hooks; try { // eslint-disable-next-line perf_hooks = require('perf_hooks'); } catch { // node version is too old } if (perf_hooks) { expect(metrics).toHaveLength(1); expect(metrics[0].help).toEqual( 'Garbage collection duration by kind, one of major, minor, incremental or weakcb.', ); expect(metrics[0].type).toEqual('histogram'); expect(metrics[0].name).toEqual('nodejs_gc_duration_seconds'); } else { expect(metrics).toHaveLength(0); } }); }); prom-client-14.1.0/test/metrics/heapSizeAndUsedTest.js000066400000000000000000000021101430114771000226550ustar00rootroot00000000000000'use strict'; describe('heapSizeAndUsed', () => { const heapSizeAndUsed = require('../../lib/metrics/heapSizeAndUsed'); const globalRegistry = require('../../lib/registry').globalRegistry; const memoryUsedFn = process.memoryUsage; afterEach(() => { process.memoryUsage = memoryUsedFn; globalRegistry.clear(); }); it('should set gauge values from memoryUsage', async () => { process.memoryUsage = function () { return { heapTotal: 1000, heapUsed: 500, external: 100 }; }; heapSizeAndUsed(); // Note: these three gauges' values are set by the _total gauge's // "collect" function. const totalGauge = globalRegistry.getSingleMetric( 'nodejs_heap_size_total_bytes', ); expect((await totalGauge.get()).values[0].value).toEqual(1000); const usedGauge = globalRegistry.getSingleMetric( 'nodejs_heap_size_used_bytes', ); expect((await usedGauge.get()).values[0].value).toEqual(500); const externalGauge = globalRegistry.getSingleMetric( 'nodejs_external_memory_bytes', ); expect((await externalGauge.get()).values[0].value).toEqual(100); }); }); prom-client-14.1.0/test/metrics/heapSpacesSizeAndUsedTest.js000066400000000000000000000046711430114771000240320ustar00rootroot00000000000000'use strict'; jest.mock('v8', () => { return { getHeapSpaceStatistics() { return [ { space_name: 'new_space', space_size: 100, space_used_size: 50, space_available_size: 500, physical_space_size: 100, }, { space_name: 'old_space', space_size: 100, space_used_size: 50, space_available_size: 500, physical_space_size: 100, }, { space_name: 'code_space', space_size: 100, space_used_size: 50, space_available_size: 500, physical_space_size: 100, }, { space_name: 'map_space', space_size: 100, space_used_size: 50, space_available_size: 500, physical_space_size: 100, }, { space_name: 'large_object_space', space_size: 100, space_used_size: 50, space_available_size: 500, physical_space_size: 100, }, ]; }, }; }); describe('heapSpacesSizeAndUsed', () => { let heapSpacesSizeAndUsed; const globalRegistry = require('../../lib/registry').globalRegistry; beforeEach(() => { heapSpacesSizeAndUsed = require('../../lib/metrics/heapSpacesSizeAndUsed'); }); afterEach(() => { globalRegistry.clear(); }); it('should set total heap spaces size gauges with values from v8', async () => { expect(await globalRegistry.getMetricsAsJSON()).toHaveLength(0); heapSpacesSizeAndUsed(); const metrics = await globalRegistry.getMetricsAsJSON(); expect(metrics[0].name).toEqual('nodejs_heap_space_size_total_bytes'); expect(metrics[0].values).toEqual([ { labels: { space: 'new' }, value: 100 }, { labels: { space: 'old' }, value: 100 }, { labels: { space: 'code' }, value: 100 }, { labels: { space: 'map' }, value: 100 }, { labels: { space: 'large_object' }, value: 100 }, ]); expect(metrics[1].name).toEqual('nodejs_heap_space_size_used_bytes'); expect(metrics[1].values).toEqual([ { labels: { space: 'new' }, value: 50 }, { labels: { space: 'old' }, value: 50 }, { labels: { space: 'code' }, value: 50 }, { labels: { space: 'map' }, value: 50 }, { labels: { space: 'large_object' }, value: 50 }, ]); expect(metrics[2].name).toEqual('nodejs_heap_space_size_available_bytes'); expect(metrics[2].values).toEqual([ { labels: { space: 'new' }, value: 500 }, { labels: { space: 'old' }, value: 500 }, { labels: { space: 'code' }, value: 500 }, { labels: { space: 'map' }, value: 500 }, { labels: { space: 'large_object' }, value: 500 }, ]); }); }); prom-client-14.1.0/test/metrics/maxFileDescriptorsTest.js000066400000000000000000000031201430114771000234520ustar00rootroot00000000000000'use strict'; const exec = require('child_process').execSync; describe('processMaxFileDescriptors', () => { const register = require('../../index').register; const processMaxFileDescriptors = require('../../lib/metrics/processMaxFileDescriptors'); beforeAll(() => { register.clear(); }); afterEach(() => { register.clear(); }); if (process.platform !== 'linux') { it('should not add metric to the registry', async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); processMaxFileDescriptors(); expect(await register.getMetricsAsJSON()).toHaveLength(0); }); } else { it('should add metric to the registry', async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); processMaxFileDescriptors(); const metrics = await register.getMetricsAsJSON(); expect(metrics).toHaveLength(1); expect(metrics[0].help).toEqual( 'Maximum number of open file descriptors.', ); expect(metrics[0].type).toEqual('gauge'); expect(metrics[0].name).toEqual('process_max_fds'); expect(metrics[0].values).toHaveLength(1); }); it('should have a reasonable metric value', async () => { const maxFiles = Number(exec('ulimit -Hn', { encoding: 'utf8' })); expect(await register.getMetricsAsJSON()).toHaveLength(0); processMaxFileDescriptors(register, {}); const metrics = await register.getMetricsAsJSON(); expect(metrics).toHaveLength(1); expect(metrics[0].values).toHaveLength(1); expect(metrics[0].values[0].value).toBeLessThanOrEqual(maxFiles); expect(metrics[0].values[0].value).toBeGreaterThan(0); }); } }); prom-client-14.1.0/test/metrics/processHandlesTest.js000066400000000000000000000016311430114771000226250ustar00rootroot00000000000000'use strict'; describe('processHandles', () => { const register = require('../../index').register; const processHandles = require('../../lib/metrics/processHandles'); beforeAll(() => { register.clear(); }); afterEach(() => { register.clear(); }); it('should add metric to the registry', async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); processHandles(); const metrics = await register.getMetricsAsJSON(); expect(metrics).toHaveLength(2); expect(metrics[0].help).toEqual( 'Number of active libuv handles grouped by handle type. Every handle type is C++ class name.', ); expect(metrics[0].type).toEqual('gauge'); expect(metrics[0].name).toEqual('nodejs_active_handles'); expect(metrics[1].help).toEqual('Total number of active handles.'); expect(metrics[1].type).toEqual('gauge'); expect(metrics[1].name).toEqual('nodejs_active_handles_total'); }); }); prom-client-14.1.0/test/metrics/processOpenFileDescriptorsTest.js000066400000000000000000000015401430114771000251710ustar00rootroot00000000000000'use strict'; describe('processOpenFileDescriptors', () => { const register = require('../../index').register; const processOpenFileDescriptors = require('../../lib/metrics/processOpenFileDescriptors'); jest.mock( 'process', () => Object.assign({}, jest.requireActual('process'), { platform: 'linux' }), // This metric only works on Linux ); beforeAll(() => { register.clear(); }); afterEach(() => { register.clear(); }); it('should add metric to the registry', async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); processOpenFileDescriptors(); const metrics = await register.getMetricsAsJSON(); expect(metrics).toHaveLength(1); expect(metrics[0].help).toEqual('Number of open file descriptors.'); expect(metrics[0].type).toEqual('gauge'); expect(metrics[0].name).toEqual('process_open_fds'); }); }); prom-client-14.1.0/test/metrics/processRequestsTest.js000066400000000000000000000016421430114771000230640ustar00rootroot00000000000000'use strict'; describe('processRequests', () => { const register = require('../../index').register; const processRequests = require('../../lib/metrics/processRequests'); beforeAll(() => { register.clear(); }); afterEach(() => { register.clear(); }); it('should add metric to the registry', async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); processRequests(); const metrics = await register.getMetricsAsJSON(); expect(metrics).toHaveLength(2); expect(metrics[0].help).toEqual( 'Number of active libuv requests grouped by request type. Every request type is C++ class name.', ); expect(metrics[0].type).toEqual('gauge'); expect(metrics[0].name).toEqual('nodejs_active_requests'); expect(metrics[1].help).toEqual('Total number of active requests.'); expect(metrics[1].type).toEqual('gauge'); expect(metrics[1].name).toEqual('nodejs_active_requests_total'); }); }); prom-client-14.1.0/test/metrics/processResourcesTest.js000066400000000000000000000020031430114771000232130ustar00rootroot00000000000000'use strict'; describe('processRequests', () => { const register = require('../../index').register; const processResources = require('../../lib/metrics/processResources'); beforeAll(() => { register.clear(); }); afterEach(() => { register.clear(); }); it('should add metric to the registry', async () => { if (typeof process.getActiveResourcesInfo !== 'function') { return; } expect(await register.getMetricsAsJSON()).toHaveLength(0); processResources(); const metrics = await register.getMetricsAsJSON(); expect(metrics).toHaveLength(2); expect(metrics[0].help).toEqual( 'Number of active resources that are currently keeping the event loop alive, grouped by async resource type.', ); expect(metrics[0].type).toEqual('gauge'); expect(metrics[0].name).toEqual('nodejs_active_resources'); expect(metrics[1].help).toEqual('Total number of active resources.'); expect(metrics[1].type).toEqual('gauge'); expect(metrics[1].name).toEqual('nodejs_active_resources_total'); }); }); prom-client-14.1.0/test/metrics/processStartTimeTest.js000066400000000000000000000015461430114771000231700ustar00rootroot00000000000000'use strict'; describe('processStartTime', () => { const register = require('../../index').register; const op = require('../../lib/metrics/processStartTime'); beforeAll(() => { register.clear(); }); afterEach(() => { register.clear(); }); it('should add metric to the registry', async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); op(); const metrics = await register.getMetricsAsJSON(); const startTime = Math.ceil(Date.now() / 1000 - process.uptime()); expect(metrics).toHaveLength(1); expect(metrics[0].help).toEqual( 'Start time of the process since unix epoch in seconds.', ); expect(metrics[0].type).toEqual('gauge'); expect(metrics[0].name).toEqual('process_start_time_seconds'); expect(metrics[0].values).toHaveLength(1); expect(metrics[0].values[0].value).toBeLessThanOrEqual(startTime); }); }); prom-client-14.1.0/test/metrics/versionTest.js000066400000000000000000000027601430114771000213410ustar00rootroot00000000000000'use strict'; const nodeVersion = process.version; const versionSegments = nodeVersion.slice(1).split('.').map(Number); function expectVersionMetrics(metrics) { expect(metrics).toHaveLength(1); expect(metrics[0].help).toEqual('Node.js version info.'); expect(metrics[0].type).toEqual('gauge'); expect(metrics[0].name).toEqual('nodejs_version_info'); expect(metrics[0].values[0].labels.version).toEqual(nodeVersion); expect(metrics[0].values[0].labels.major).toEqual(versionSegments[0]); expect(metrics[0].values[0].labels.minor).toEqual(versionSegments[1]); expect(metrics[0].values[0].labels.patch).toEqual(versionSegments[2]); } describe('version', () => { const register = require('../../index').register; const version = require('../../lib/metrics/version'); beforeAll(() => { register.clear(); }); afterEach(() => { register.clear(); }); it('should add metric to the registry', async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); expect(typeof versionSegments[0]).toEqual('number'); expect(typeof versionSegments[1]).toEqual('number'); expect(typeof versionSegments[2]).toEqual('number'); version(); const metrics = await register.getMetricsAsJSON(); expectVersionMetrics(metrics); }); it('should still be present after resetting the registry #238', async () => { const collector = version(); expectVersionMetrics(await register.getMetricsAsJSON()); register.resetMetrics(); expectVersionMetrics(await register.getMetricsAsJSON()); }); }); prom-client-14.1.0/test/pushgatewayTest.js000066400000000000000000000132051430114771000205430ustar00rootroot00000000000000'use strict'; const nock = require('nock'); const { gzipSync } = require('zlib'); describe('pushgateway', () => { const Pushgateway = require('../index').Pushgateway; const register = require('../index').register; const Registry = require('../index').Registry; let instance; let registry = undefined; const tests = function () { describe('pushAdd', () => { it('should push metrics', () => { const mockHttp = nock('http://192.168.99.100:9091') .post( '/metrics/job/testJob', '# HELP test test\n# TYPE test counter\ntest 100\n', ) .reply(200); return instance.pushAdd({ jobName: 'testJob' }).then(() => { expect(mockHttp.isDone()); }); }); it('should use groupings', () => { const mockHttp = nock('http://192.168.99.100:9091') .post( '/metrics/job/testJob/key/value', '# HELP test test\n# TYPE test counter\ntest 100\n', ) .reply(200); return instance .pushAdd({ jobName: 'testJob', groupings: { key: 'value' }, }) .then(() => { expect(mockHttp.isDone()); }); }); it('should escape groupings', () => { const mockHttp = nock('http://192.168.99.100:9091') .post( '/metrics/job/testJob/key/va%26lue', '# HELP test test\n# TYPE test counter\ntest 100\n', ) .reply(200); return instance .pushAdd({ jobName: 'testJob', groupings: { key: 'va&lue' }, }) .then(() => { expect(mockHttp.isDone()); }); }); }); describe('push', () => { it('should push with PUT', () => { const mockHttp = nock('http://192.168.99.100:9091') .put( '/metrics/job/testJob', '# HELP test test\n# TYPE test counter\ntest 100\n', ) .reply(200); return instance.push({ jobName: 'testJob' }).then(() => { expect(mockHttp.isDone()); }); }); it('should uri encode url', () => { const mockHttp = nock('http://192.168.99.100:9091') .put( '/metrics/job/test%26Job', '# HELP test test\n# TYPE test counter\ntest 100\n', ) .reply(200); return instance.push({ jobName: 'test&Job' }).then(() => { expect(mockHttp.isDone()); }); }); }); describe('delete', () => { it('should push delete with no body', () => { const mockHttp = nock('http://192.168.99.100:9091') .delete('/metrics/job/testJob') .reply(200); return instance.delete({ jobName: 'testJob' }).then(() => { expect(mockHttp.isDone()); }); }); }); describe('when using basic authentication', () => { const USERNAME = 'unittest'; const PASSWORD = 'unittest'; const auth = `${USERNAME}:${PASSWORD}`; beforeEach(() => { instance = new Pushgateway( `http://${auth}@192.168.99.100:9091`, null, registry, ); }); it('pushAdd should send POST request with basic auth data', () => { const mockHttp = nock(`http://${auth}@192.168.99.100:9091`) .post( '/metrics/job/testJob', '# HELP test test\n# TYPE test counter\ntest 100\n', ) .reply(200); return instance.pushAdd({ jobName: 'testJob' }).then(() => { expect(mockHttp.isDone()); }); }); it('push should send PUT request with basic auth data', () => { const mockHttp = nock(`http://${auth}@192.168.99.100:9091`) .put( '/metrics/job/testJob', '# HELP test test\n# TYPE test counter\ntest 100\n', ) .reply(200); return instance.push({ jobName: 'testJob' }).then(() => { expect(mockHttp.isDone()); }); }); it('delete should send DELETE request with basic auth data', () => { const mockHttp = nock(`http://${auth}@192.168.99.100:9091`) .delete('/metrics/job/testJob') .reply(200); return instance.delete({ jobName: 'testJob' }).then(() => { expect(mockHttp.isDone()); }); }); }); it('should be possible to extend http/s requests with options', () => { const mockHttp = nock('http://192.168.99.100:9091', { reqheaders: { 'unit-test': '1', }, }) .put( '/metrics/job/testJob', '# HELP test test\n# TYPE test counter\ntest 100\n', ) .reply(200); instance = new Pushgateway( 'http://192.168.99.100:9091', { headers: { 'unit-test': '1', }, }, registry, ); return instance.push({ jobName: 'testJob' }).then(() => { expect(mockHttp.isDone()); }); }); it('should use gzip request', () => { const mockHttp = nock('http://192.168.99.100:9091', { reqheaders: { 'Content-Encoding': 'gzip', }, }) .post( '/metrics/job/testJob', gzipSync('# HELP test test\n# TYPE test counter\ntest 100\n'), ) .reply(200); instance = new Pushgateway( 'http://192.168.99.100:9091', { headers: { 'Content-Encoding': 'gzip', }, }, registry, ); return instance.pushAdd({ jobName: 'testJob' }).then(() => { expect(mockHttp.isDone()); }); }); }; describe('global registry', () => { afterEach(() => { register.clear(); }); beforeEach(() => { registry = undefined; instance = new Pushgateway('http://192.168.99.100:9091'); const promClient = require('../index'); const cnt = new promClient.Counter({ name: 'test', help: 'test' }); cnt.inc(100); }); tests(); }); describe('registry instance', () => { afterEach(() => { register.clear(); }); beforeEach(() => { registry = new Registry(); instance = new Pushgateway('http://192.168.99.100:9091', null, registry); const promeClient = require('../index'); const cnt = new promeClient.Counter({ name: 'test', help: 'test', registers: [registry], }); cnt.inc(100); }); tests(); }); }); prom-client-14.1.0/test/pushgatewayWithPathTest.js000066400000000000000000000117061430114771000222200ustar00rootroot00000000000000'use strict'; const pushGatewayPath = '/path'; const pushGatewayURL = 'http://192.168.99.100:9091'; const pushGatewayFullURL = pushGatewayURL + pushGatewayPath; const mockHttp = jest.fn().mockReturnValue({ on: jest.fn(), end: jest.fn(), write: jest.fn(), }); jest.mock('http', () => { return { request: mockHttp, }; }); describe('pushgateway with path', () => { const Pushgateway = require('../index').Pushgateway; const register = require('../index').register; const Registry = require('../index').Registry; let instance; let registry = undefined; const tests = function () { describe('pushAdd', () => { it('should push metrics', () => { instance.pushAdd({ jobName: 'testJob' }); expect(mockHttp).toHaveBeenCalledTimes(1); const invocation = mockHttp.mock.calls[0][0]; expect(invocation.method).toEqual('POST'); expect(invocation.path).toEqual('/path/metrics/job/testJob'); }); it('should use groupings', () => { instance.pushAdd({ jobName: 'testJob', groupings: { key: 'value' } }); expect(mockHttp).toHaveBeenCalledTimes(1); const invocation = mockHttp.mock.calls[0][0]; expect(invocation.method).toEqual('POST'); expect(invocation.path).toEqual('/path/metrics/job/testJob/key/value'); }); it('should escape groupings', () => { instance.pushAdd({ jobName: 'testJob', groupings: { key: 'va&lue' } }); expect(mockHttp).toHaveBeenCalledTimes(1); const invocation = mockHttp.mock.calls[0][0]; expect(invocation.method).toEqual('POST'); expect(invocation.path).toEqual( '/path/metrics/job/testJob/key/va%26lue', ); }); }); describe('push', () => { it('should push with PUT', () => { instance.push({ jobName: 'testJob' }); expect(mockHttp).toHaveBeenCalledTimes(1); const invocation = mockHttp.mock.calls[0][0]; expect(invocation.method).toEqual('PUT'); expect(invocation.path).toEqual('/path/metrics/job/testJob'); }); it('should uri encode url', () => { instance.push({ jobName: 'test&Job' }); expect(mockHttp).toHaveBeenCalledTimes(1); const invocation = mockHttp.mock.calls[0][0]; expect(invocation.method).toEqual('PUT'); expect(invocation.path).toEqual('/path/metrics/job/test%26Job'); }); }); describe('delete', () => { it('should push delete with no body', () => { instance.delete({ jobName: 'testJob' }); expect(mockHttp).toHaveBeenCalledTimes(1); const invocation = mockHttp.mock.calls[0][0]; expect(invocation.method).toEqual('DELETE'); expect(invocation.path).toEqual('/path/metrics/job/testJob'); }); }); describe('when using basic authentication', () => { const USERNAME = 'unittest'; const PASSWORD = 'unittest'; const auth = `${USERNAME}:${PASSWORD}`; beforeEach(() => { instance = new Pushgateway( `http://${auth}@192.168.99.100:9091${pushGatewayPath}`, null, registry, ); }); it('pushAdd should send POST request with basic auth data', () => { instance.pushAdd({ jobName: 'testJob' }); expect(mockHttp).toHaveBeenCalledTimes(1); const invocation = mockHttp.mock.calls[0][0]; expect(invocation.method).toEqual('POST'); expect(invocation.auth).toEqual(auth); }); it('push should send PUT request with basic auth data', () => { instance.push({ jobName: 'testJob' }); expect(mockHttp).toHaveBeenCalledTimes(1); const invocation = mockHttp.mock.calls[0][0]; expect(invocation.method).toEqual('PUT'); expect(invocation.auth).toEqual(auth); }); it('delete should send DELETE request with basic auth data', () => { instance.delete({ jobName: 'testJob' }); expect(mockHttp).toHaveBeenCalledTimes(1); const invocation = mockHttp.mock.calls[0][0]; expect(invocation.method).toEqual('DELETE'); expect(invocation.auth).toEqual(auth); }); }); it('should be possible to extend http/s requests with options', () => { instance = new Pushgateway( pushGatewayFullURL, { headers: { 'unit-test': '1', }, }, registry, ); instance.push({ jobName: 'testJob' }); expect(mockHttp).toHaveBeenCalledTimes(1); const invocation = mockHttp.mock.calls[0][0]; expect(invocation.headers).toEqual({ 'unit-test': '1' }); }); }; describe('global registry', () => { afterEach(() => { mockHttp.mockClear(); register.clear(); }); beforeEach(() => { registry = undefined; instance = new Pushgateway(pushGatewayFullURL); const promClient = require('../index'); const cnt = new promClient.Counter({ name: 'test', help: 'test' }); cnt.inc(100); }); tests(); }); describe('registry instance', () => { afterEach(() => { mockHttp.mockClear(); }); beforeEach(() => { registry = new Registry(); instance = new Pushgateway(pushGatewayFullURL, null, registry); const promClient = require('../index'); const cnt = new promClient.Counter({ name: 'test', help: 'test', registers: [registry], }); cnt.inc(100); }); tests(); }); }); prom-client-14.1.0/test/registerTest.js000066400000000000000000000370701430114771000200340ustar00rootroot00000000000000'use strict'; describe('register', () => { const register = require('../index').register; const Counter = require('../index').Counter; const Gauge = require('../index').Gauge; const Histogram = require('../index').Histogram; const Summary = require('../index').Summary; beforeEach(() => { register.clear(); }); describe('should output a counter metric', () => { let output; beforeEach(async () => { register.registerMetric(getMetric()); output = (await register.metrics()).split('\n'); }); it('with help as first item', () => { expect(output[0]).toEqual('# HELP test_metric A test metric'); }); it('with type as second item', () => { expect(output[1]).toEqual('# TYPE test_metric counter'); }); it('with first value of the metric as third item', () => { expect(output[2]).toEqual('test_metric{label="hello",code="303"} 12'); }); it('with second value of the metric as fourth item', () => { expect(output[3]).toEqual('test_metric{label="bye",code="404"} 34'); }); }); it('should throw on more than one metric', () => { register.registerMetric(getMetric()); expect(() => { register.registerMetric(getMetric()); }).toThrowError( 'A metric with the name test_metric has already been registered.', ); }); it('should handle and output a metric with a NaN value', async () => { register.registerMetric({ async get() { return { name: 'test_metric', type: 'gauge', help: 'A test metric', values: [ { value: NaN, }, ], }; }, }); const lines = (await register.metrics()).split('\n'); expect(lines).toHaveLength(4); expect(lines[2]).toEqual('test_metric Nan'); }); it('should handle and output a metric with an +Infinity value', async () => { register.registerMetric({ async get() { return { name: 'test_metric', type: 'gauge', help: 'A test metric', values: [ { value: Infinity, }, ], }; }, }); const lines = (await register.metrics()).split('\n'); expect(lines).toHaveLength(4); expect(lines[2]).toEqual('test_metric +Inf'); }); it('should handle and output a metric with an -Infinity value', async () => { register.registerMetric({ async get() { return { name: 'test_metric', type: 'gauge', help: 'A test metric', values: [ { value: -Infinity, }, ], }; }, }); const lines = (await register.metrics()).split('\n'); expect(lines).toHaveLength(4); expect(lines[2]).toEqual('test_metric -Inf'); }); it('should handle a metric without labels', async () => { register.registerMetric({ async get() { return { name: 'test_metric', type: 'counter', help: 'A test metric', values: [ { value: 1, }, ], }; }, }); expect((await register.metrics()).split('\n')).toHaveLength(4); }); it('should handle a metric with default labels', async () => { register.setDefaultLabels({ testLabel: 'testValue' }); register.registerMetric({ async get() { return { name: 'test_metric', type: 'counter', help: 'A test metric', values: [{ value: 1 }], }; }, }); const output = (await register.metrics()).split('\n')[2]; expect(output).toEqual('test_metric{testLabel="testValue"} 1'); }); it('labeled metrics should take precidence over defaulted', async () => { register.setDefaultLabels({ testLabel: 'testValue' }); register.registerMetric({ async get() { return { name: 'test_metric', type: 'counter', help: 'A test metric', values: [ { value: 1, labels: { testLabel: 'overlapped', anotherLabel: 'value123', }, }, ], }; }, }); expect((await register.metrics()).split('\n')[2]).toEqual( 'test_metric{testLabel="overlapped",anotherLabel="value123"} 1', ); }); it('should output all initialized metrics at value 0', async () => { new Counter({ name: 'counter', help: 'help' }); new Gauge({ name: 'gauge', help: 'help' }); new Histogram({ name: 'histogram', help: 'help' }); new Summary({ name: 'summary', help: 'help' }); expect(await register.metrics()).toMatchSnapshot(); }); it('should not output all initialized metrics at value 0 if labels', async () => { new Counter({ name: 'counter', help: 'help', labelNames: ['label'] }); new Gauge({ name: 'gauge', help: 'help', labelNames: ['label'] }); new Histogram({ name: 'histogram', help: 'help', labelNames: ['label'] }); new Summary({ name: 'summary', help: 'help', labelNames: ['label'] }); expect(await register.metrics()).toMatchSnapshot(); }); describe('should escape', () => { let escapedResult; beforeEach(async () => { register.registerMetric({ async get() { return { name: 'test_"_\\_\n_metric', help: 'help_help', type: 'counter', }; }, }); escapedResult = await register.metrics(); }); it('backslash to \\\\', () => { expect(escapedResult).toMatch(/\\\\/); }); it('newline to \\\\n', () => { expect(escapedResult).toMatch(/\n/); }); }); it('should escape " in label values', async () => { register.registerMetric({ async get() { return { name: 'test_metric', type: 'counter', help: 'A test metric', values: [ { value: 12, labels: { label: 'hello', code: '3"03', }, }, ], }; }, }); const escapedResult = await register.metrics(); expect(escapedResult).toMatch(/\\"/); }); describe('should output metrics as JSON', () => { it('should output metrics as JSON', async () => { register.registerMetric(getMetric()); const output = await register.getMetricsAsJSON(); expect(output.length).toEqual(1); expect(output[0].name).toEqual('test_metric'); expect(output[0].type).toEqual('counter'); expect(output[0].help).toEqual('A test metric'); expect(output[0].values.length).toEqual(2); }); it('should add default labels to JSON', async () => { register.registerMetric(getMetric()); register.setDefaultLabels({ defaultRegistryLabel: 'testValue', }); const output = await register.getMetricsAsJSON(); expect(output.length).toEqual(1); expect(output[0].name).toEqual('test_metric'); expect(output[0].type).toEqual('counter'); expect(output[0].help).toEqual('A test metric'); expect(output[0].values.length).toEqual(2); expect(output[0].values[0].labels).toEqual({ code: '303', label: 'hello', defaultRegistryLabel: 'testValue', }); }); }); it('should allow removing single metrics', async () => { register.registerMetric(getMetric()); register.registerMetric(getMetric('some other name')); let output = await register.getMetricsAsJSON(); expect(output.length).toEqual(2); register.removeSingleMetric('test_metric'); output = await register.getMetricsAsJSON(); expect(output.length).toEqual(1); expect(output[0].name).toEqual('some other name'); }); it('should allow getting single metrics', () => { const metric = getMetric(); register.registerMetric(metric); const output = register.getSingleMetric('test_metric'); expect(output).toEqual(metric); }); it('should allow gettings metrics', async () => { const metric = getMetric(); register.registerMetric(metric); const metrics = await register.metrics(); expect(metrics.split('\n')[3]).toEqual( 'test_metric{label="bye",code="404"} 34', ); }); describe('resetting', () => { it('should allow resetting all metrics', async () => { const counter = new Counter({ name: 'test_counter', help: 'test metric', labelNames: ['serial', 'active'], }); const gauge = new Gauge({ name: 'test_gauge', help: 'Another test metric', labelNames: ['level'], }); const histo = new Histogram({ name: 'test_histo', help: 'test', }); const summ = new Summary({ name: 'test_summ', help: 'test', percentiles: [0.5], }); register.registerMetric(counter); register.registerMetric(gauge); register.registerMetric(histo); register.registerMetric(summ); counter.inc({ serial: '12345', active: 'yes' }, 12); gauge.set({ level: 'low' }, -12); histo.observe(1); summ.observe(100); register.resetMetrics(); const same_counter = register.getSingleMetric('test_counter'); expect((await same_counter.get()).values).toEqual([]); const same_gauge = register.getSingleMetric('test_gauge'); expect((await same_gauge.get()).values).toEqual([]); const same_histo = register.getSingleMetric('test_histo'); expect((await same_histo.get()).values).toEqual([]); const same_summ = register.getSingleMetric('test_summ'); expect((await same_summ.get()).values[0].value).toEqual(0); }); }); describe('Registry with default labels', () => { const Registry = require('../lib/registry'); describe('mutation tests', () => { describe('registry.metrics()', () => { it('should not throw with default labels (counter)', async () => { const r = new Registry(); r.setDefaultLabels({ env: 'development', }); const counter = new Counter({ name: 'my_counter', help: 'my counter', registers: [r], labelNames: ['type'], }); const myCounter = counter.labels('myType'); myCounter.inc(); const metrics = await r.metrics(); const lines = metrics.split('\n'); expect(lines).toContain( 'my_counter{type="myType",env="development"} 1', ); myCounter.inc(); const metrics2 = await r.metrics(); const lines2 = metrics2.split('\n'); expect(lines2).toContain( 'my_counter{type="myType",env="development"} 2', ); }); it('should not throw with default labels (gauge)', async () => { const r = new Registry(); r.setDefaultLabels({ env: 'development', }); const gauge = new Gauge({ name: 'my_gauge', help: 'my gauge', registers: [r], labelNames: ['type'], }); const myGauge = gauge.labels('myType'); myGauge.inc(1); const metrics = await r.metrics(); const lines = metrics.split('\n'); expect(lines).toContain( 'my_gauge{type="myType",env="development"} 1', ); myGauge.inc(2); const metrics2 = await r.metrics(); const lines2 = metrics2.split('\n'); expect(lines2).toContain( 'my_gauge{type="myType",env="development"} 3', ); }); it('should not throw with default labels (histogram)', async () => { const r = new Registry(); r.setDefaultLabels({ env: 'development', }); const hist = new Histogram({ name: 'my_histogram', help: 'my histogram', registers: [r], labelNames: ['type'], }); const myHist = hist.labels('myType'); myHist.observe(1); const metrics = await r.metrics(); const lines = metrics.split('\n'); expect(lines).toContain( 'my_histogram_bucket{le="1",type="myType",env="development"} 1', ); myHist.observe(1); const metrics2 = await r.metrics(); const lines2 = metrics2.split('\n'); expect(lines2).toContain( 'my_histogram_bucket{le="1",type="myType",env="development"} 2', ); }); }); describe('registry.getMetricsAsJSON()', () => { it('should not throw with default labels (counter)', async () => { const r = new Registry(); r.setDefaultLabels({ env: 'development', }); const counter = new Counter({ name: 'my_counter', help: 'my counter', registers: [r], labelNames: ['type'], }); const myCounter = counter.labels('myType'); myCounter.inc(); const metrics = await r.getMetricsAsJSON(); expect(metrics).toContainEqual({ aggregator: 'sum', help: 'my counter', name: 'my_counter', type: 'counter', values: [ { labels: { env: 'development', type: 'myType' }, value: 1, }, ], }); myCounter.inc(); const metrics2 = await r.getMetricsAsJSON(); expect(metrics2).toContainEqual({ aggregator: 'sum', help: 'my counter', name: 'my_counter', type: 'counter', values: [ { labels: { env: 'development', type: 'myType' }, value: 2, }, ], }); }); it('should not throw with default labels (gauge)', async () => { const r = new Registry(); r.setDefaultLabels({ env: 'development', }); const gauge = new Gauge({ name: 'my_gauge', help: 'my gauge', registers: [r], labelNames: ['type'], }); const myGauge = gauge.labels('myType'); myGauge.inc(1); const metrics = await r.getMetricsAsJSON(); expect(metrics).toContainEqual({ aggregator: 'sum', help: 'my gauge', name: 'my_gauge', type: 'gauge', values: [ { labels: { env: 'development', type: 'myType' }, value: 1, }, ], }); myGauge.inc(2); const metrics2 = await r.getMetricsAsJSON(); expect(metrics2).toContainEqual({ aggregator: 'sum', help: 'my gauge', name: 'my_gauge', type: 'gauge', values: [ { labels: { env: 'development', type: 'myType' }, value: 3, }, ], }); }); it('should not throw with default labels (histogram)', async () => { const r = new Registry(); r.setDefaultLabels({ env: 'development', }); const hist = new Histogram({ name: 'my_histogram', help: 'my histogram', registers: [r], labelNames: ['type'], }); const myHist = hist.labels('myType'); myHist.observe(1); const metrics = await r.getMetricsAsJSON(); // NOTE: at this test we don't need to check exacte JSON schema expect(metrics[0].values).toContainEqual({ labels: { env: 'development', le: 1, type: 'myType' }, metricName: 'my_histogram_bucket', value: 1, }); myHist.observe(1); const metrics2 = await r.getMetricsAsJSON(); // NOTE: at this test we don't need to check exacte JSON schema expect(metrics2[0].values).toContainEqual({ labels: { env: 'development', le: 1, type: 'myType' }, metricName: 'my_histogram_bucket', value: 2, }); }); }); }); }); describe('merging', () => { const Registry = require('../lib/registry'); let registryOne; let registryTwo; beforeEach(() => { registryOne = new Registry(); registryTwo = new Registry(); }); it('should merge all provided registers', async () => { registryOne.registerMetric(getMetric('one')); registryTwo.registerMetric(getMetric('two')); const merged = await Registry.merge([ registryOne, registryTwo, ]).getMetricsAsJSON(); expect(merged).toHaveLength(2); }); it('should throw if same name exists on both registers', () => { registryOne.registerMetric(getMetric()); registryTwo.registerMetric(getMetric()); const fn = function () { Registry.merge([registryOne, registryTwo]); }; expect(fn).toThrowError(Error); }); }); it('should have the same contentType as the module', () => { const moduleWideContentType = require('../').contentType; expect(register.contentType).toEqual(moduleWideContentType); }); function getMetric(name) { name = name || 'test_metric'; return { name, async get() { return { name, type: 'counter', help: 'A test metric', values: [ { value: 12, labels: { label: 'hello', code: '303', }, }, { value: 34, labels: { label: 'bye', code: '404', }, }, ], }; }, }; } }); prom-client-14.1.0/test/summaryTest.js000066400000000000000000000435271430114771000177110ustar00rootroot00000000000000'use strict'; describe('summary', () => { const Summary = require('../index').Summary; const Registry = require('../index').Registry; const globalRegistry = require('../index').register; let instance; describe('global registry', () => { afterEach(() => { globalRegistry.clear(); }); describe('with param as object', () => { beforeEach(() => { instance = new Summary({ name: 'summary_test', help: 'test' }); }); it('should add a value to the summary', async () => { instance.observe(100); const { values } = await instance.get(); expect(values[0].labels.quantile).toEqual(0.01); expect(values[0].value).toEqual(100); expect(values[7].metricName).toEqual('summary_test_sum'); expect(values[7].value).toEqual(100); expect(values[8].metricName).toEqual('summary_test_count'); expect(values[8].value).toEqual(1); }); it('should be able to observe 0s', async () => { instance.observe(0); expect((await instance.get()).values[8].value).toEqual(1); }); it('should validate labels when observing', async () => { const summary = new Summary({ name: 'foobar', help: 'Foo Bar', labelNames: ['foo'], }); expect(() => { summary.observe({ foo: 'bar', baz: 'qaz' }, 10); }).toThrowErrorMatchingSnapshot(); }); it('should correctly calculate percentiles when more values are added to the summary', async () => { instance.observe(100); instance.observe(100); instance.observe(100); instance.observe(50); instance.observe(50); const { values } = await instance.get(); expect(values.length).toEqual(9); expect(values[0].labels.quantile).toEqual(0.01); expect(values[0].value).toEqual(50); expect(values[1].labels.quantile).toEqual(0.05); expect(values[1].value).toEqual(50); expect(values[2].labels.quantile).toEqual(0.5); expect(values[2].value).toEqual(80); expect(values[3].labels.quantile).toEqual(0.9); expect(values[3].value).toEqual(100); expect(values[4].labels.quantile).toEqual(0.95); expect(values[4].value).toEqual(100); expect(values[5].labels.quantile).toEqual(0.99); expect(values[5].value).toEqual(100); expect(values[6].labels.quantile).toEqual(0.999); expect(values[6].value).toEqual(100); expect(values[7].metricName).toEqual('summary_test_sum'); expect(values[7].value).toEqual(400); expect(values[8].metricName).toEqual('summary_test_count'); expect(values[8].value).toEqual(5); }); it('should correctly use calculate other percentiles when configured', async () => { globalRegistry.clear(); instance = new Summary({ name: 'summary_test', help: 'test', percentiles: [0.5, 0.9], }); instance.observe(100); instance.observe(100); instance.observe(100); instance.observe(50); instance.observe(50); const { values } = await instance.get(); expect(values.length).toEqual(4); expect(values[0].labels.quantile).toEqual(0.5); expect(values[0].value).toEqual(80); expect(values[1].labels.quantile).toEqual(0.9); expect(values[1].value).toEqual(100); expect(values[2].metricName).toEqual('summary_test_sum'); expect(values[2].value).toEqual(400); expect(values[3].metricName).toEqual('summary_test_count'); expect(values[3].value).toEqual(5); }); it('should allow to reset itself', async () => { globalRegistry.clear(); instance = new Summary({ name: 'summary_test', help: 'test', percentiles: [0.5], }); instance.observe(100); const { values } = await instance.get(); expect(values[0].labels.quantile).toEqual(0.5); expect(values[0].value).toEqual(100); expect(values[1].metricName).toEqual('summary_test_sum'); expect(values[1].value).toEqual(100); expect(values[2].metricName).toEqual('summary_test_count'); expect(values[2].value).toEqual(1); instance.reset(); const { values: valuesPost } = await instance.get(); expect(valuesPost[0].labels.quantile).toEqual(0.5); expect(valuesPost[0].value).toEqual(0); expect(valuesPost[1].metricName).toEqual('summary_test_sum'); expect(valuesPost[1].value).toEqual(0); expect(valuesPost[2].metricName).toEqual('summary_test_count'); expect(valuesPost[2].value).toEqual(0); }); describe('labels', () => { beforeEach(() => { globalRegistry.clear(); instance = new Summary({ name: 'summary_test', help: 'help', labelNames: ['method', 'endpoint'], percentiles: [0.9], }); }); it('should record and calculate the correct values per label', async () => { instance.labels('GET', '/test').observe(50); instance.labels('POST', '/test').observe(100); const { values } = await instance.get(); expect(values).toHaveLength(6); expect(values[0].labels.method).toEqual('GET'); expect(values[0].labels.endpoint).toEqual('/test'); expect(values[0].labels.quantile).toEqual(0.9); expect(values[0].value).toEqual(50); expect(values[1].metricName).toEqual('summary_test_sum'); expect(values[1].labels.method).toEqual('GET'); expect(values[1].labels.endpoint).toEqual('/test'); expect(values[1].value).toEqual(50); expect(values[2].metricName).toEqual('summary_test_count'); expect(values[2].labels.method).toEqual('GET'); expect(values[2].labels.endpoint).toEqual('/test'); expect(values[2].value).toEqual(1); expect(values[3].labels.quantile).toEqual(0.9); expect(values[3].labels.method).toEqual('POST'); expect(values[3].labels.endpoint).toEqual('/test'); expect(values[3].value).toEqual(100); expect(values[4].metricName).toEqual('summary_test_sum'); expect(values[4].labels.method).toEqual('POST'); expect(values[4].labels.endpoint).toEqual('/test'); expect(values[4].value).toEqual(100); expect(values[5].metricName).toEqual('summary_test_count'); expect(values[5].labels.method).toEqual('POST'); expect(values[5].labels.endpoint).toEqual('/test'); expect(values[5].value).toEqual(1); }); it('should throw error if label lengths does not match', () => { const fn = function () { instance.labels('GET').observe(); }; expect(fn).toThrowErrorMatchingSnapshot(); }); it('should start a timer', async () => { jest.useFakeTimers('modern'); jest.setSystemTime(0); const end = instance.labels('GET', '/test').startTimer(); jest.advanceTimersByTime(1000); const duration = end(); expect(duration).toEqual(1); const { values } = await instance.get(); expect(values).toHaveLength(3); expect(values[0].labels.method).toEqual('GET'); expect(values[0].labels.endpoint).toEqual('/test'); expect(values[0].labels.quantile).toEqual(0.9); expect(values[0].value).toEqual(1); expect(values[1].metricName).toEqual('summary_test_sum'); expect(values[1].labels.method).toEqual('GET'); expect(values[1].labels.endpoint).toEqual('/test'); expect(values[1].value).toEqual(1); expect(values[2].metricName).toEqual('summary_test_count'); expect(values[2].labels.method).toEqual('GET'); expect(values[2].labels.endpoint).toEqual('/test'); expect(values[2].value).toEqual(1); jest.useRealTimers(); }); it('should start a timer and set labels afterwards', async () => { jest.useFakeTimers('modern'); jest.setSystemTime(0); const end = instance.startTimer(); jest.advanceTimersByTime(1000); end({ method: 'GET', endpoint: '/test' }); const { values } = await instance.get(); expect(values).toHaveLength(3); expect(values[0].labels.method).toEqual('GET'); expect(values[0].labels.endpoint).toEqual('/test'); expect(values[0].labels.quantile).toEqual(0.9); expect(values[0].value).toEqual(1); expect(values[1].metricName).toEqual('summary_test_sum'); expect(values[1].labels.method).toEqual('GET'); expect(values[1].labels.endpoint).toEqual('/test'); expect(values[1].value).toEqual(1); expect(values[2].metricName).toEqual('summary_test_count'); expect(values[2].labels.method).toEqual('GET'); expect(values[2].labels.endpoint).toEqual('/test'); expect(values[2].value).toEqual(1); jest.useRealTimers(); }); it('should allow labels before and after timers', async () => { jest.useFakeTimers('modern'); jest.setSystemTime(0); const end = instance.startTimer({ method: 'GET' }); jest.advanceTimersByTime(1000); end({ endpoint: '/test' }); const { values } = await instance.get(); expect(values).toHaveLength(3); expect(values[0].labels.method).toEqual('GET'); expect(values[0].labels.endpoint).toEqual('/test'); expect(values[0].labels.quantile).toEqual(0.9); expect(values[0].value).toEqual(1); expect(values[1].metricName).toEqual('summary_test_sum'); expect(values[1].labels.method).toEqual('GET'); expect(values[1].labels.endpoint).toEqual('/test'); expect(values[1].value).toEqual(1); expect(values[2].metricName).toEqual('summary_test_count'); expect(values[2].labels.method).toEqual('GET'); expect(values[2].labels.endpoint).toEqual('/test'); expect(values[2].value).toEqual(1); jest.useRealTimers(); }); it('should not mutate passed startLabels', () => { const startLabels = { method: 'GET' }; const end = instance.startTimer(startLabels); end({ endpoint: '/test' }); expect(startLabels).toEqual({ method: 'GET' }); }); it('should handle labels provided as an object', async () => { instance.labels({ method: 'GET' }).startTimer()(); const values = (await instance.get()).values; values.forEach(value => { expect(value.labels.method).toBe('GET'); }); }); }); describe('remove', () => { beforeEach(() => { globalRegistry.clear(); instance = new Summary({ name: 'summary_test', help: 'help', labelNames: ['method', 'endpoint'], percentiles: [0.9], }); instance.labels('GET', '/test').observe(50); instance.labels('POST', '/test').observe(100); }); it('should remove matching label', async () => { instance.remove('GET', '/test'); const { values } = await instance.get(); expect(values).toHaveLength(3); expect(values[0].labels.quantile).toEqual(0.9); expect(values[0].labels.method).toEqual('POST'); expect(values[0].labels.endpoint).toEqual('/test'); expect(values[0].value).toEqual(100); expect(values[1].metricName).toEqual('summary_test_sum'); expect(values[1].labels.method).toEqual('POST'); expect(values[1].labels.endpoint).toEqual('/test'); expect(values[1].value).toEqual(100); expect(values[2].metricName).toEqual('summary_test_count'); expect(values[2].labels.method).toEqual('POST'); expect(values[2].labels.endpoint).toEqual('/test'); expect(values[2].value).toEqual(1); }); it('should remove all labels', async () => { instance.remove('GET', '/test'); instance.remove('POST', '/test'); expect((await instance.get()).values).toHaveLength(0); }); it('should throw error if label lengths does not match', () => { const fn = function () { instance.remove('GET'); }; expect(fn).toThrowErrorMatchingSnapshot(); }); it('should remove timer values', async () => { jest.useFakeTimers('modern'); jest.setSystemTime(0); const end = instance.labels('GET', '/test').startTimer(); jest.advanceTimersByTime(1000); end(); instance.remove('GET', '/test'); const { values } = await instance.get(); expect(values).toHaveLength(3); expect(values[0].labels.quantile).toEqual(0.9); expect(values[0].labels.method).toEqual('POST'); expect(values[0].labels.endpoint).toEqual('/test'); expect(values[0].value).toEqual(100); expect(values[1].metricName).toEqual('summary_test_sum'); expect(values[1].labels.method).toEqual('POST'); expect(values[1].labels.endpoint).toEqual('/test'); expect(values[1].value).toEqual(100); expect(values[2].metricName).toEqual('summary_test_count'); expect(values[2].labels.method).toEqual('POST'); expect(values[2].labels.endpoint).toEqual('/test'); expect(values[2].value).toEqual(1); jest.useRealTimers(); }); it('should remove timer values when labels are set afterwards', async () => { jest.useFakeTimers('modern'); jest.setSystemTime(0); const end = instance.startTimer(); jest.advanceTimersByTime(1000); end({ method: 'GET', endpoint: '/test' }); instance.remove('GET', '/test'); const { values } = await instance.get(); expect(values).toHaveLength(3); expect(values[0].labels.quantile).toEqual(0.9); expect(values[0].labels.method).toEqual('POST'); expect(values[0].labels.endpoint).toEqual('/test'); expect(values[0].value).toEqual(100); expect(values[1].metricName).toEqual('summary_test_sum'); expect(values[1].labels.method).toEqual('POST'); expect(values[1].labels.endpoint).toEqual('/test'); expect(values[1].value).toEqual(100); expect(values[2].metricName).toEqual('summary_test_count'); expect(values[2].labels.method).toEqual('POST'); expect(values[2].labels.endpoint).toEqual('/test'); expect(values[2].value).toEqual(1); jest.useRealTimers(); }); it('should remove timer values with before and after labels', async () => { jest.useFakeTimers('modern'); jest.setSystemTime(0); const end = instance.startTimer({ method: 'GET' }); jest.advanceTimersByTime(1000); end({ endpoint: '/test' }); instance.remove('GET', '/test'); const { values } = await instance.get(); expect(values).toHaveLength(3); expect(values[0].labels.quantile).toEqual(0.9); expect(values[0].labels.method).toEqual('POST'); expect(values[0].labels.endpoint).toEqual('/test'); expect(values[0].value).toEqual(100); expect(values[1].metricName).toEqual('summary_test_sum'); expect(values[1].labels.method).toEqual('POST'); expect(values[1].labels.endpoint).toEqual('/test'); expect(values[1].value).toEqual(100); expect(values[2].metricName).toEqual('summary_test_count'); expect(values[2].labels.method).toEqual('POST'); expect(values[2].labels.endpoint).toEqual('/test'); expect(values[2].value).toEqual(1); jest.useRealTimers(); }); it('should remove by labels object', async () => { instance.observe({ endpoint: '/test' }, 1); instance.remove({ endpoint: '/test' }); const values = (await instance.get()).values; values.forEach(value => { expect(value.labels).not.toEqual({ endpoint: '/test' }); }); }); }); }); }); describe('without registry', () => { beforeEach(() => { instance = new Summary({ name: 'summary_test', help: 'test', registers: [], }); }); it('should increase count', async () => { instance.observe(100); const { values } = await instance.get(); expect(values[0].labels.quantile).toEqual(0.01); expect(values[0].value).toEqual(100); expect(values[7].metricName).toEqual('summary_test_sum'); expect(values[7].value).toEqual(100); expect(values[8].metricName).toEqual('summary_test_count'); expect(values[8].value).toEqual(1); expect((await globalRegistry.getMetricsAsJSON()).length).toEqual(0); }); }); describe('registry instance', () => { let registryInstance; beforeEach(() => { registryInstance = new Registry(); instance = new Summary({ name: 'summary_test', help: 'test', registers: [registryInstance], }); }); it('should increment counter', async () => { instance.observe(100); const { values } = await instance.get(); expect(values[0].labels.quantile).toEqual(0.01); expect(values[0].value).toEqual(100); expect(values[7].metricName).toEqual('summary_test_sum'); expect(values[7].value).toEqual(100); expect(values[8].metricName).toEqual('summary_test_count'); expect(values[8].value).toEqual(1); expect((await globalRegistry.getMetricsAsJSON()).length).toEqual(0); expect((await registryInstance.getMetricsAsJSON()).length).toEqual(1); }); }); describe('sliding window', () => { let clock; beforeEach(() => { globalRegistry.clear(); jest.useFakeTimers('modern'); jest.setSystemTime(0); }); it('should slide when maxAgeSeconds and ageBuckets are set', async () => { const localInstance = new Summary({ name: 'summary_test', help: 'test', maxAgeSeconds: 5, ageBuckets: 5, }); localInstance.observe(100); for (let i = 0; i < 5; i++) { const { values } = await localInstance.get(); expect(values[0].labels.quantile).toEqual(0.01); expect(values[0].value).toEqual(100); expect(values[7].metricName).toEqual('summary_test_sum'); expect(values[7].value).toEqual(100); expect(values[8].metricName).toEqual('summary_test_count'); expect(values[8].value).toEqual(1); jest.advanceTimersByTime(1001); } const { values } = await localInstance.get(); expect(values[0].labels.quantile).toEqual(0.01); expect(values[0].value).toEqual(0); }); it('should not slide when maxAgeSeconds and ageBuckets are not configured', async () => { const localInstance = new Summary({ name: 'summary_test', help: 'test', }); localInstance.observe(100); for (let i = 0; i < 5; i++) { const { values } = await localInstance.get(); expect(values[0].labels.quantile).toEqual(0.01); expect(values[0].value).toEqual(100); expect(values[7].metricName).toEqual('summary_test_sum'); expect(values[7].value).toEqual(100); expect(values[8].metricName).toEqual('summary_test_count'); expect(values[8].value).toEqual(1); jest.advanceTimersByTime(1001); } const { values } = await localInstance.get(); expect(values[0].labels.quantile).toEqual(0.01); expect(values[0].value).toEqual(100); }); }); }); prom-client-14.1.0/test/timeWindowQuantilesTest.js000066400000000000000000000040751430114771000222230ustar00rootroot00000000000000'use strict'; describe('timeWindowQuantiles', () => { const TimeWindowQuantiles = require('../lib/timeWindowQuantiles'); let instance; let clock; beforeEach(() => { jest.useFakeTimers('modern'); jest.setSystemTime(0); instance = new TimeWindowQuantiles(5, 5); }); describe('methods', () => { it('#push', () => { instance.push(1); instance.ringBuffer.forEach(td => { expect(td.centroids.size).toEqual(1); }); }); it('#reset', () => { instance.push(1); instance.reset(); instance.ringBuffer.forEach(td => { expect(td.centroids.size).toEqual(0); }); }); it('#compress', () => { instance.push(1); instance.compress(); instance.ringBuffer.forEach(td => { expect(td.centroids.size).toEqual(1); }); }); it('#percentile', () => { instance.push(1); expect(instance.percentile(0.5)).toEqual(1); }); }); describe('rotatation', () => { it('rotatation interval should be configured', () => { let localInstance = new TimeWindowQuantiles(undefined, undefined); expect(localInstance.durationBetweenRotatesMillis).toEqual(Infinity); localInstance = new TimeWindowQuantiles(1, 1); expect(localInstance.durationBetweenRotatesMillis).toEqual(1000); localInstance = new TimeWindowQuantiles(10, 5); expect(localInstance.durationBetweenRotatesMillis).toEqual(2000); }); it('should rotate', () => { instance.push(1); expect(instance.currentBuffer).toEqual(0); jest.advanceTimersByTime(1001); instance.percentile(0.5); expect(instance.currentBuffer).toEqual(1); jest.advanceTimersByTime(1001); instance.percentile(0.5); expect(instance.currentBuffer).toEqual(2); jest.advanceTimersByTime(1001); instance.percentile(0.5); expect(instance.currentBuffer).toEqual(3); jest.advanceTimersByTime(1001); instance.percentile(0.5); expect(instance.currentBuffer).toEqual(4); jest.advanceTimersByTime(1001); instance.percentile(0.5); expect(instance.currentBuffer).toEqual(0); instance.ringBuffer.forEach(td => { expect(td.centroids.size).toEqual(0); }); }); }); }); prom-client-14.1.0/test/validationTest.js000066400000000000000000000010421430114771000203300ustar00rootroot00000000000000'use strict'; describe('validation', () => { describe('validateLabel', () => { const validateLabel = require('../lib/validation').validateLabel; it('should not throw on known label', () => { expect(() => { validateLabel(['exists'], { exists: null }); }).not.toThrowError(); }); it('should throw on unknown label', () => { expect(() => { validateLabel(['exists'], { somethingElse: null }); }).toThrowError( 'Added label "somethingElse" is not included in initial labelset: [ \'exists\' ]', ); }); }); }); prom-client-14.1.0/tsconfig.json000066400000000000000000000001421430114771000165300ustar00rootroot00000000000000{ "compilerOptions": { "target": "es6", "strict": true, "types": [], "noEmit": true } }