Giter VIP home page Giter VIP logo

promjs's Introduction

DEPRECATED: promjs

A Prometheus metrics registry implemented in TypeScript

Goals

  • Stick to Prometheus client best practices as closely as possible
  • Run in Node.js or the browser
  • Fit into the modern JavaScript ecosystem
  • Minimally rely on third-party dependencies

Installation

Install via npm:

$ npm install --save promjs

or via yarn:

$ yarn add promjs

Usage

// Using es6 imports
import prom from 'promjs';
// Using CommonJS
const prom = require('promjs');

const registry = prom();
const pageRequestCounter = registry.create('counter', 'page_requests', 'A counter for page requests');

pageRequestCounter.inc();
console.log(registry.metrics());
// =>
// # HELP page_requests A counter for page requests \n
// # TYPE page_requests counter
// page_requests 1 \n

API

prom()

Returns a registry class.

Registry

registry.create(type, name, help) => collector (counter | gauge | histogram)

Returns a metric class of the specified type. The metric is already registered with the registry that creates it.

Arguments

  1. type (String): The type of metric to create. The current supported types are counter, gauge, and histogram.
  2. name (String): The name of the metric
  3. help (String): The help message for the metric

Example

import prom from 'promjs';

const registry = prom();
const counter = registry.create('counter', 'my_counter', 'A counter for things');

registry.metrics() => string

Returns a prometheus formatted string containing all existing metrics.

const counter = registry.create('counter', 'my_counter', 'A counter for things');
counter.inc();
console.log(registry.metrics());
// =>
// # HELP my_counter A counter for things \n
// # TYPE my_counter counter
// my_counter 1 \n

registry.clear() => self

Removes all metrics from internal data storage. Returns itself to allow for chaining.

registry.reset() => self

Resets all existing metrics to 0. This can be used to reset metrics after reporting to a prometheus aggregator. Returns itself to allow for chaining.

registry.get(type, name) => collector (counter | gauge | histogram) | null

Fetches an existing metric by name. Returns null if no metrics are found

Collector

All of the metric classes (Counter, Gauge, Histogram) inherit from the Collector class. Collector methods are available on each of the metic classes.

collector.reset([labels]) => self

Resets metrics in the collector. Optionally pass in labels to reset only those labels.

collector.resetAll() => self

Resets all metrics in the collector, including metrics with labels.

Counter

A counter can only ever be incremented positively.

counter.inc([labels]) => self

Increments a counter. Optionally pass in a set of labels to increment only those labels.

counter.add(amount, [labels]) => self

Increments a counter by a given amount. amount must be a Number. Optionally pass in a set of labels to increment only those labels.

const counter = registry.create('counter', 'my_counter', 'A counter for things');
counter.inc();
counter.add(2, { ok: true, status: 'success', code: 200 });
counter.add(2, { ok: false, status: 'fail', code: 403 });

console.log(registry.metrics());
// =>
// # HELP my_counter A counter for things
// # TYPE my_counter counter
// my_counter 1
// my_counter{ok="true",status="success",code="200"} 2
// my_counter{ok="false",status="fail",code="403"} 2

Gauge

A gauge is similar to a counter, but can be incremented up and down.

gauge.inc([labels]) => self

Increments a gauge by 1.

gauge.dec([lables]) => self

Decrements a gauge by 1.

gauge.add(amount, [lables]) => self

Increments a gauge by a given amount. amount must be a Number.

gauge.sub(amount, [labels]) => self

Decrements a gauge by a given amount.

const gauge = registry.create('gauge', 'my_gauge', 'A gauge for stuffs');
gauge.inc();
gauge.inc({ instance: 'some_instance' });
gauge.dec({ instance: 'some_instance' });
gauge.add(100, { instance: 'some_instance' });
gauge.sub(50, { instance: 'some_instance' });

console.log(registry.metrics());
// =>
// # HELP my_gauge A gauge for stuffs
// # TYPE my_gauge gauge
// my_gauge 1
// my_gauge{instance="some_instance"} 50

Histogram

Histograms are used to group values into pre-defined buckets. Buckets are passed in to the registry.create() call.

histogram.observe(value) => self

Adds value to a pre-existing bucket.value must be a number.

const histogram = registry.create('histogram', 'response_time', 'The response time', [
  200,
  300,
  400,
  500
]);
histogram.observe(299);
histogram.observe(253, { path: '/api/users', status: 200 });
histogram.observe(499, { path: '/api/users', status: 200 });

console.log(registry.metrics());
// =>
// # HELP response_time The response time
// # TYPE response_time histogram
// response_time_count 3
// response_time_sum 599
// response_time_bucket{le="200"} 1
// response_time_bucket{le="400",path="/api/users",status="200"} 1
// response_time_bucket{le="200",path="/api/users",status="200"} 1

Getting Help

If you have any questions about, feedback for or problems with promjs:

Weaveworks follows the CNCF Code of Conduct. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a Weaveworks project maintainer, or Alexis Richardson ([email protected]).

Your feedback is always welcome!

promjs's People

Contributors

bensaufley avatar daryl-d avatar dependabot[bot] avatar dimitropoulos avatar georgesg avatar guyfedwards avatar jpellizzari avatar morancj avatar rade avatar thibmeu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

promjs's Issues

lodash usage contributes to ~71kb (minified) when targetting Cloudflare Workers runtime

I'm using promjs with some code on Cloudflare workers and I noticed that lodash contributes ~71kb of bloat (promjs without lodash is otherwise 9kb). For example, I think lodash.filter/lodash.each/lodash.reduce/lodash.find/lodash.map are completely redundant since ES arrays offer those out of the box I think. lodash.sum can be expressed in terms of reduce. lodash.isEqual could be replacable with a custom version of since it's just being used to compare two arrays of strings I think (& even a nested one wouldn't be hard).

I'm sure other pieces might be difficult to tease out but it would be nice because of how heavy a dependency lodash is.

FWIW I'm using esbuild. I've tried https://www.npmjs.com/package/@optimize-lodash/esbuild-plugin and it makes no difference to the minified size.

clear() obliterates metrics entirely?

First: thanks for building this! And for using TypeScript, which makes my life easier.

So I see in the docs that clear() is intended to "Reset all existing metrics to 0." But looking at the code, it simply sets registry.data to empty counter/gauge/histogram, meaning all of the .create calls would need to happen again? As far as I can see, attempting to increment an existing metric fails silently after clear(). That doesn't seem to match described behavior?

const registry = promjs();
const myCounter = registry.create('counter', 'my_counter', 'This is a sample counter');
myCounter.inc({ foo: 'bar' });
registry.metrics();
// # HELP my_counter This is a sample counter
// # TYPE my_counter counter
// {foo="bar"} 1
registry.clear();
myCounter.inc({ foo: 'baz' });
registry.metrics()
// ''
registry.data;
// {"counter":{},"gauge":{},"histogram":{}}

Is the intent that metrics be re-initialized after .clear()? Is there a way to actually "reset metrics to 0" (rather than removing them), or a will to support that?

Inspecting the registry to see if there are any metrics to push

When pushing metrics on an interval, after doin a registry.reset(), if there have not been any new metrics recorded it would be nice to be able to skip pushing if all metrics are 0. As far as I can tell there isn't an easy way to check this, is there?

This is somewhat related to #40 in looking for convenient ways to make pushing metrics as efficient as possible by not pushing things we don't need to.

Communication with prom-aggregation-gateway

Hi,
Thanks for creating and open-sourcing this awesome lib.

I was wondering if you have a recommended way (or an interface to impl) for communication with prom-aggregation-gateway. I think this could be useful for this project.

Thanks again.

Using localStorage to keep registry state when page reloaded

Hi there,

Not a critical issue, but something that will be nice to have. Right now, whenever the user refresh the page, the registry state is lost forever. The way I'm doing now to solve this problem is quite brittle.

I store the JSON stringfied registry in the localStorage:

export function storePromRegistry() {
  localStorage.setItem("promjs_registry_json", JSON.stringify(promRegistry));
}

And then, whenever the registry is created, I check localStorage to see if there's any saved state there, loading it if so:

// Getting state from local storage
let promRegistryStored = localStorage.getItem("promjs_registry_json");
if (promRegistryStored) {
  let promRegistryDataJSON = JSON.parse(promRegistryStored);
  each(promRegistryDataJSON.data, (collectorGroup) => {
    each(collectorGroup, (collectorData, collectorName) => {
      let collector = promRegistry.get(collectorData.type, collectorName)
      if (collector && collectorData.instance.data[0]) (
        collector.set(collectorData.instance.data[0].value)
      )
    });
  });
}

Yep, it's an edge case and losing some metrics on refreshes is not the end of the world, but would be nice to have support from the lib for keeping the state if needed. ๐Ÿ‘

Best practice for listening for changes to the metrics registry

Hey! Great work with this library, i'm currently working on using it to start instrumenting my org's app.

I'm curious to hear what the best approach is for listening to changes to counters, histograms, etc created by a registry so you know to send an update to your Prometheus Gateway.

I was expecting that maybe the registry would expose some sort of onChange listener you could use to trigger new requests to send the latest metrics to your backend.

For now i'm just checking the registry.metrics() output for changes on an interval and using that to send the latest metrics to the backend. Curious to know if anyone has a recommended way to do this?

Update NPM package

Hey, could you please update the NPM package to the latest commit? We're using TypeScript and the currently published types definition on NPM is broken.
Many thanks in advance! โค

Update dependencies

Some of the pinned dependencies in this package causes SYNK warnings, could you please cut a new release using updated deps?

Sending large payloads with 0 values after registry.reset()

registry.reset() resets all existing metrics to 0. But registry.metrics() would still return them, even if 0, making the payload quite large with many useless 0 values in it (those metrics had been sent in previous calls and were reset after). Example:

# HELP fe_hs_app_bundle_load_seconds Time to load an HS app bundle
# TYPE fe_hs_app_bundle_load_seconds histogram
fe_hs_app_bundle_load_seconds_count 0
fe_hs_app_bundle_load_seconds_sum 0
fe_hs_app_bundle_load_seconds_bucket{le="1"} 0
fe_hs_app_bundle_load_seconds_bucket{le="2"} 0
fe_hs_app_bundle_load_seconds_bucket{le="3"} 0
fe_hs_app_bundle_load_seconds_bucket{le="5"} 0
fe_hs_app_bundle_load_seconds_bucket{le="7"} 0
fe_hs_app_bundle_load_seconds_bucket{le="10"} 0
fe_hs_app_bundle_load_seconds_bucket{le="+Inf"} 0
fe_hs_app_bundle_load_seconds_bucket{le="0.25"} 0
fe_hs_app_bundle_load_seconds_bucket{le="0.5"} 0
fe_hs_app_bundle_load_seconds_bucket{le="1.5"} 0
fe_hs_app_bundle_load_seconds_bucket{le="2.5"} 0
fe_hs_app_bundle_load_seconds_count{hsApp="hs-app-composer",hasError="false"} 0
fe_hs_app_bundle_load_seconds_sum{hsApp="hs-app-composer",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="1",hsApp="hs-app-composer",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="2",hsApp="hs-app-composer",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="3",hsApp="hs-app-composer",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="5",hsApp="hs-app-composer",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="7",hsApp="hs-app-composer",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="10",hsApp="hs-app-composer",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="+Inf",hsApp="hs-app-composer",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="0.25",hsApp="hs-app-composer",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="0.5",hsApp="hs-app-composer",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="1.5",hsApp="hs-app-composer",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="2.5",hsApp="hs-app-composer",hasError="false"} 0
fe_hs_app_bundle_load_seconds_count{hsApp="hs-app-directory",hasError="false"} 0
fe_hs_app_bundle_load_seconds_sum{hsApp="hs-app-directory",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="1",hsApp="hs-app-directory",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="2",hsApp="hs-app-directory",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="3",hsApp="hs-app-directory",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="5",hsApp="hs-app-directory",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="7",hsApp="hs-app-directory",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="10",hsApp="hs-app-directory",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="+Inf",hsApp="hs-app-directory",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="0.25",hsApp="hs-app-directory",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="0.5",hsApp="hs-app-directory",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="1.5",hsApp="hs-app-directory",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="2.5",hsApp="hs-app-directory",hasError="false"} 0
fe_hs_app_bundle_load_seconds_count{hsApp="hs-app-inbox",hasError="false"} 1
fe_hs_app_bundle_load_seconds_sum{hsApp="hs-app-inbox",hasError="false"} 29.47082499996759
fe_hs_app_bundle_load_seconds_bucket{le="1",hsApp="hs-app-inbox",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="2",hsApp="hs-app-inbox",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="3",hsApp="hs-app-inbox",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="5",hsApp="hs-app-inbox",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="7",hsApp="hs-app-inbox",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="10",hsApp="hs-app-inbox",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="+Inf",hsApp="hs-app-inbox",hasError="false"} 1
fe_hs_app_bundle_load_seconds_bucket{le="0.25",hsApp="hs-app-inbox",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="0.5",hsApp="hs-app-inbox",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="1.5",hsApp="hs-app-inbox",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="2.5",hsApp="hs-app-inbox",hasError="false"} 0

Would it be possible for registry.metrics() not to return empty metrics (or at least have a param that allows you to do this)?
Using registry.clear() is not feasible in my situation, for the reason mentioned here

Ideally this is the payload I'd like to send instead of the above:

# HELP fe_hs_app_bundle_load_seconds Time to load an HS app bundle
# TYPE fe_hs_app_bundle_load_seconds histogram
fe_hs_app_bundle_load_seconds_count{hsApp="hs-app-inbox",hasError="false"} 1
fe_hs_app_bundle_load_seconds_sum{hsApp="hs-app-inbox",hasError="false"} 29.47082499996759
fe_hs_app_bundle_load_seconds_bucket{le="1",hsApp="hs-app-inbox",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="2",hsApp="hs-app-inbox",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="3",hsApp="hs-app-inbox",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="5",hsApp="hs-app-inbox",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="7",hsApp="hs-app-inbox",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="10",hsApp="hs-app-inbox",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="+Inf",hsApp="hs-app-inbox",hasError="false"} 1
fe_hs_app_bundle_load_seconds_bucket{le="0.25",hsApp="hs-app-inbox",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="0.5",hsApp="hs-app-inbox",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="1.5",hsApp="hs-app-inbox",hasError="false"} 0
fe_hs_app_bundle_load_seconds_bucket{le="2.5",hsApp="hs-app-inbox",hasError="false"} 0

I could do this processing on my side, but it would be easier to not add those zero metrics in the first place, and maybe it can be a feature if more peeps find it useful.

Remove internal node modules directory

When installed the package has an internal node_modules directory.

Reproduce:

  1. Install promjs with yarn add promjs (or npm
  2. Do ls -l node_modules/promjs/node_modules/

There shouldn't be a node_modules dir there. This is causing lodash to be included in the bundle twice.

Angular

I am using this library in Angular application. I can see the metrics in the browser by logging in each component.
I am not sure how/what to implement in Angular so that Prometheus can pick these metrics by "/metrics" url end point.
Any ideas. Please share me some sample code if anyone has done it already.

Error when trying to run this package on react-native

I wanted to ask if there is any way to make this library run on react-native. I am succesfully able to build the app but I get this error when the code runs:

error: Error: While trying to resolve module promjs from file D:\Repos\field-rep-app\mobile\node_modules\@cabify\prom-react\dist\index.js, the package D:\Repos\field-rep-app\mobile\node_modules\promjs\package.json was successfully found. However, this package itself specifies a main module field that could not be resolved (D:\Repos\field-rep-app\mobile\node_modules\promjs\lib\index.js. Indeed, none of these files exist:

I am using "@cabify/prom-react": "^0.3.0" which utilizes promjs "^0.4.1" .

react: 17.0.2 => 17.0.2
react-native: 0.68.2 => 0.68.2

Share api with prom-client

The semi-official node.js client for prometheus (as in, linked in Prometheus' docs) is prom-client. If we adopted this library at work, we'd still use that for server-side metrics, but using two different APIs in the same language would be sorta weird. Would you be opposed to changing your api to more closely resemble prom-client's?

From a first look, the difference is mostly in how labels are handled, and how metrics are constructed.

If interested, I could probably provide a PR

Can't import as expected

Having trouble using the package with a simple import as documented

import prom from 'promjs';

Working around it by doing...

import prom from 'promjs/index';

I think this is due to a miss match between the file layout in the git repo vs the published package

package.json refrences lib/index.js

"main": "lib/index.js",

In the package on npm however the actual index.js is in the root of the package, not lib.

I suspect this is due to the cd lib in your push script

"push": "cd lib && npm login && npm publish && git push --set-upstream origin master --follow-tags"

Add default metrics

It would be nice to provide some metrics out of the box for folks unfamiliar with prometheus.

For Node.js: a basic middleware to record latencies and status codes of server responses. Also expose a /metrics path.

For the browser: wrap the XMLHttpRequest prototype to instrument client requests.

Hurdles:

  • scrub labels (URLs) for cardinality

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.