Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
### Features

- Add support for custom labels addition to metrics
- Add `httpMetricsPrefix` option to optionally add a prefix to HTTP metrics.
- Add `excludeDefaultMetricLabels` option to exclude all or certain metrics that are added by default.
- Add `useCountersForRequestSizeMetric` option to expose two counters for Request Size (`_sum` and `_count`) instead of Histogram.
- Add `useCountersForResponseSizeMetric` option to expose two counters for Response Size (`_sum` and `_count`) instead of Histogram.

### Improvements

Expand Down
108 changes: 72 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,25 @@
- [Access the metrics](#access-the-metrics)
- [Custom Metrics](#custom-metrics)
- [Note](#note)
- [Additional Metric Labels](#additional-metric-labels)
- [Request.js HTTP request duration collector](#requestjs-http-request-duration-collector)
- [Usage](#usage-1)
- [Initialize](#initialize)
- [Options](#options-1)
- [request](#request)
- [request-promise-native](#request-promise-native)
- [axios](#axios)
- [Test](#test)
- [Usage in koa](#usage-in-koa)
- [Test](#test)
- [Prometheus Examples Queries](#prometheus-examples-queries)
- [Apdex](#apdex)
- [95th Response Time by specific route and status code](#95th-response-time-by-specific-route-and-status-code)
- [Median Response Time Overall](#median-response-time-overall)
- [Median Request Size Overall](#median-request-size-overall)
- [Median Response Size Overall](#median-response-size-overall)
- [Avarage Memory Usage - All services](#avarage-memory-usage---all-services)
- [Avarage Eventloop Latency - All services](#avarage-eventloop-latency---all-services)
- [Changelog](#changelog)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

Expand Down Expand Up @@ -55,24 +66,28 @@ API and process monitoring with [Prometheus](https://prometheus.io) for Node.js

```js
const apiMetrics = require('prometheus-api-metrics');
app.use(apiMetrics())
app.use(apiMetrics());
```

### Options

| Option | Type | Description | Default Value |
|--------------------------|-----------|-------------|---------------|
| `metricsPath` | `String` | Path to access the metrics | `/metrics` |
| `defaultMetricsInterval` | `Number` | Interval to collect the process metrics in milliseconds | `10000` |
| `durationBuckets` | `Array<Number>` | Buckets for response time in seconds | `[0.001, 0.005, 0.015, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5]` |
| `requestSizeBuckets` | `Array<Number>` | Buckets for request size in bytes | `[5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000]` |
| `responseSizeBuckets` | `Array<Number>` | Buckets for response size in bytes | `[5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000]` |
| `useUniqueHistogramName` | `Boolean` | Add to metrics names the project name as a prefix (from package.json) | `false` |
| `metricsPrefix` | `String` | A custom metrics names prefix, the package will add underscore between your prefix to the metric name | |
| `excludeRoutes` | `Array<String>` | Array of routes to exclude. Routes should be in your framework syntax | |
| `includeQueryParams` | `Boolean` | Indicate if to include query params in route, the query parameters will be sorted in order to eliminate the number of unique labels | `false` |
| `additionalLabels` | `Array<String>` | Indicating custom labels that can be included on each `http_*` metric. Use in conjunction with `extractAdditionalLabelValuesFn`. |
| `extractAdditionalLabelValuesFn` | `Function` | A function that can be use to generate the value of custom labels for each of the `http_*` metrics. When using koa, the function takes `ctx`, when using express, it takes `req, res` as arguments | |
| Option | Type | Description | Default Value |
| ---------------------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| `metricsPath` | `String` | Path to access the metrics | `/metrics` |
| `defaultMetricsInterval` | `Number` | Interval to collect the process metrics in milliseconds | `10000` |
| `durationBuckets` | `Array<Number>` | Buckets for response time in seconds | `[0.001, 0.005, 0.015, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5]` |
| `requestSizeBuckets` | `Array<Number>` | Buckets for request size in bytes | `[5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000]` |
| `responseSizeBuckets` | `Array<Number>` | Buckets for response size in bytes | `[5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000]` |
| `useUniqueHistogramName` | `Boolean` | Add to metrics names the project name as a prefix (from package.json) | `false` |
| `metricsPrefix` | `String` | A custom metrics names prefix, the package will add underscore between your prefix to the metric name | |
| `httpMetricsPrefix` | `String` | A custom prefix for HTTP metrics only, the package will add underscore between your prefix to the metric name | |
| `excludeRoutes` | `Array<String>` | Array of routes to exclude. Routes should be in your framework syntax | |
| `includeQueryParams` | `Boolean` | Indicate if to include query params in route, the query parameters will be sorted in order to eliminate the number of unique labels | `false` |
| `additionalLabels` | `Array<String>` | Indicating custom labels that can be included on each `http_*` metric. Use in conjunction with `extractAdditionalLabelValuesFn`. |
| `extractAdditionalLabelValuesFn` | `Function` | A function that can be use to generate the value of custom labels for each of the `http_*` metrics. When using koa, the function takes `ctx`, when using express, it takes `req, res` as arguments | |
| `excludeDefaultMetricLabels` | `Boolean` or `Array<String>` | Excludes the metric labels added by default (`code`, `method`, `route`). If `true` is passed, it will exclude them all. An array of the labels that you need to exclude can also be passed | |
| `useCountersForRequestSizeMetric` | `Boolean` | Uses two counters for Request Size (`_sum` and `_count`) instead of Histogram | `false` |
| `useCountersForResponseSizeMetric` | `Boolean` | Uses two counters for Response Size (`_sum` and `_count`) instead of Histogram | `false` |

### Access the metrics

Expand Down Expand Up @@ -111,16 +126,16 @@ Create new metric from the kind that you like
const checkoutsTotal = new Prometheus.Counter({
name: 'checkouts_total',
help: 'Total number of checkouts',
labelNames: ['payment_method']
labelNames: ['payment_method'],
});
```

Update it:

```js
checkoutsTotal.inc({
payment_method: paymentMethod
})
payment_method: paymentMethod,
});
```

The custom metrics will be exposed under the same endpoint as the API metrics.
Expand All @@ -139,16 +154,18 @@ For instance:

```js
const apiMetrics = require('prometheus-api-metrics');
app.use(apiMetrics({
additionalLabels: ['customer', 'cluster'],
extractAdditionalLabelValuesFn: (req, res) => {
app.use(
apiMetrics({
additionalLabels: ['customer', 'cluster'],
extractAdditionalLabelValuesFn: (req, res) => {
const { headers } = req.headers;
return {
customer: headers['x-custom-header-customer'],
cluster: headers['x-custom-header-cluster']
}
}
}))
cluster: headers['x-custom-header-cluster'],
};
},
})
);
```

## Request.js HTTP request duration collector
Expand All @@ -164,15 +181,17 @@ You can choose to initialized this functionality as a Class or not
**Class:**

```js
const HttpMetricsCollector = require('prometheus-api-metrics').HttpMetricsCollector;
const HttpMetricsCollector = require('prometheus-api-metrics')
.HttpMetricsCollector;
const collector = new HttpMetricsCollector();
collector.init();
```

**Singelton:**

```js
const HttpMetricsCollector = require('prometheus-api-metrics').HttpMetricsCollector;
const HttpMetricsCollector = require('prometheus-api-metrics')
.HttpMetricsCollector;
HttpMetricsCollector.init();
```

Expand All @@ -189,25 +208,34 @@ For Example:

```js
request({ url: 'http://www.google.com', time: true }, (err, response) => {
Collector.collect(err || response);
Collector.collect(err || response);
});
```

#### request-promise-native

```js
return requestPromise({ method: 'POST', url: 'http://www.mocky.io/v2/5bd9984b2f00006d0006d1fd', route: 'v2/:id', time: true, resolveWithFullResponse: true }).then((response) => {
return requestPromise({
method: 'POST',
url: 'http://www.mocky.io/v2/5bd9984b2f00006d0006d1fd',
route: 'v2/:id',
time: true,
resolveWithFullResponse: true,
})
.then((response) => {
Collector.collect(response);
}).catch((error) => {
})
.catch((error) => {
Collector.collect(error);
});
});
```

**Notes:**

1. In order to use this feature you must use `{ time: true }` as part of your request configuration and then pass to the collector the response or error you got.
2. In order to use the timing feature in request-promise/request-promise-native you must also use `resolveWithFullResponse: true`
3. Override - you can override the `route` and `target` attribute instead of taking them from the request object. In order to do that you should set a `metrics` object on your request with those attribute:

```js
request({ method: 'POST', url: 'http://www.mocky.io/v2/5bd9984b2f00006d0006d1fd', metrics: { target: 'www.google.com', route: 'v2/:id' }, time: true }, (err, response) => {...};
});
Expand All @@ -222,10 +250,14 @@ const axiosTime = require('axios-time');
axiosTime(axios);

try {
const response = await axios({ baseURL: 'http://www.google.com', method: 'get', url: '/' });
Collector.collect(response);
const response = await axios({
baseURL: 'http://www.google.com',
method: 'get',
url: '/',
});
Collector.collect(response);
} catch (error) {
Collector.collect(error);
Collector.collect(error);
}
```

Expand All @@ -238,9 +270,9 @@ try {
This package supports koa server that uses [`koa-router`](https://www.npmjs.com/package/koa-router) and [`koa-bodyparser`](https://www.npmjs.com/package/koa-bodyparser)

```js
const { koaMiddleware } = require('prometheus-api-metrics')
const { koaMiddleware } = require('prometheus-api-metrics');

app.use(koaMiddleware())
app.use(koaMiddleware());
```

## Test
Expand Down Expand Up @@ -293,6 +325,10 @@ avg(nodejs_external_memory_bytes / 1024 / 1024) by (<SERVICE_LABLE_FIELD)
avg(nodejs_eventloop_lag_seconds) by (<SERVICE_LABLE_FIELD)
```

## Changelog

Please see the changelog [here](CHANGELOG.md)

[npm-image]: https://img.shields.io/npm/v/prometheus-api-metrics.svg?style=flat
[npm-url]: https://npmjs.org/package/prometheus-api-metrics
[travis-image]: https://travis-ci.org/PayU/prometheus-api-metrics.svg?branch=master
Expand Down
14 changes: 12 additions & 2 deletions src/express-middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,19 @@ class ExpressMiddleware {
code: res.statusCode,
...this.setupOptions.extractAdditionalLabelValuesFn(req, res)
};
this.setupOptions.requestSizeHistogram.observe(labels, req.metrics.contentLength);
if (this.setupOptions.useCountersForRequestSizeMetric) {
this.setupOptions.requestSizeSum.inc(labels, req.metrics.contentLength);
this.setupOptions.requestSizeCount.inc(labels);
} else {
this.setupOptions.requestSizeHistogram.observe(labels, req.metrics.contentLength);
}
if (this.setupOptions.useCountersForResponseSizeMetric) {
this.setupOptions.responseSizeSum.inc(labels, responseLength);
this.setupOptions.responseSizeCount.inc(labels);
} else {
this.setupOptions.responseSizeHistogram.observe(labels, responseLength);
}
req.metrics.timer(labels);
this.setupOptions.responseSizeHistogram.observe(labels, responseLength);
debug(`metrics updated, request length: ${req.metrics.contentLength}, response length: ${responseLength}`);
}
}
Expand Down
48 changes: 27 additions & 21 deletions src/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,38 @@
import { Request, RequestHandler, Response } from 'express';
import { Context, Middleware } from 'koa';

export default function middleware(options?: ApiMetricsOpts) : RequestHandler;
export function koaMiddleware(options?: ApiMetricsOpts) : Middleware;
export function expressMiddleware(options?: ApiMetricsOpts) : RequestHandler;
export default function middleware(options?: ApiMetricsOpts): RequestHandler;
export function koaMiddleware(options?: ApiMetricsOpts): Middleware;
export function expressMiddleware(options?: ApiMetricsOpts): RequestHandler;
export class HttpMetricsCollector {
constructor(options?: CollectorOpts)
static init(options?: CollectorOpts): void
static collect(res: Response | any): void
constructor(options?: CollectorOpts);
static init(options?: CollectorOpts): void;
static collect(res: Response | any): void;
}

export interface ApiMetricsOpts {
metricsPath?: string;
defaultMetricsInterval?: number;
durationBuckets?: number[];
requestSizeBuckets?: number[];
responseSizeBuckets?: number[];
useUniqueHistogramName?: boolean;
metricsPrefix?: string;
excludeRoutes?:string[];
includeQueryParams?: boolean;
additionalLabels?: string[];
extractAdditionalLabelValuesFn?: ((req: Request, res: Response) => Record<string, unknown>) | ((ctx: Context) => Record<string, unknown>)
metricsPath?: string;
defaultMetricsInterval?: number;
durationBuckets?: number[];
requestSizeBuckets?: number[];
responseSizeBuckets?: number[];
useUniqueHistogramName?: boolean;
metricsPrefix?: string;
httpMetricsPrefix?: string;
excludeRoutes?: string[];
includeQueryParams?: boolean;
additionalLabels?: string[];
extractAdditionalLabelValuesFn?:
| ((req: Request, res: Response) => Record<string, unknown>)
| ((ctx: Context) => Record<string, unknown>);
excludeDefaultMetricLabels?: boolean | string[];
useCountersForResponseSizeMetric?: boolean;
useCountersForRequestSizeMetric?: boolean;
}

export interface CollectorOpts {
durationBuckets?: number[];
countClientErrors?: boolean;
useUniqueHistogramName?: boolean
prefix?: string;
durationBuckets?: number[];
countClientErrors?: boolean;
useUniqueHistogramName?: boolean;
prefix?: string;
}
14 changes: 12 additions & 2 deletions src/koa-middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,19 @@ class KoaMiddleware {
code: ctx.res.statusCode,
...this.setupOptions.extractAdditionalLabelValuesFn(ctx)
};
this.setupOptions.requestSizeHistogram.observe(labels, ctx.req.metrics.contentLength);
if (this.setupOptions.useCountersForRequestSizeMetric) {
this.setupOptions.requestSizeSum.inc(labels, ctx.req.metrics.contentLength);
this.setupOptions.requestSizeCount.inc(labels);
} else {
this.setupOptions.requestSizeHistogram.observe(labels, ctx.req.metrics.contentLength);
}
if (this.setupOptions.useCountersForResponseSizeMetric) {
this.setupOptions.responseSizeSum.inc(labels, responseLength);
this.setupOptions.responseSizeCount.inc(labels);
} else {
this.setupOptions.responseSizeHistogram.observe(labels, responseLength);
}
ctx.req.metrics.timer(labels);
this.setupOptions.responseSizeHistogram.observe(labels, responseLength);
debug(`metrics updated, request length: ${ctx.req.metrics.contentLength}, response length: ${responseLength}`);
}
}
Expand Down
Loading