From 30ec2eddf660858a22a6677571d8f3afd022d241 Mon Sep 17 00:00:00 2001 From: Manabu Matsuzaki Date: Sat, 24 Apr 2021 10:31:36 +0900 Subject: [PATCH] feat: add metrics for request length and response length --- README.md | 16 ++++++++++++++++ src/index.js | 32 +++++++++++++++++++++++++++++++- src/metrics.js | 30 ++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d68069f..728643e 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ npm i --save express-prometheus-middleware | collectDefaultMetrics | Whether or not to collect `prom-client` default metrics. These metrics are usefull for collecting saturation metrics, for example. | `true` | | collectGCMetrics | Whether or not to collect garbage collection metrics via module `prometheus-gc-stats`. Dependency `prometheus-gc-stats` is marked as optional, hence if this option is set to `true` but npm/yarn could not install the dependency, no garbage collection metric will be collected. | `false` | | requestDurationBuckets | Buckets for the request duration metrics (in seconds) histogram | Uses `prom-client` utility: `Prometheus.exponentialBuckets(0.05, 1.75, 8)` | +| requestLengthBuckets | Buckets for the request length metrics (in bytes) histogram | no buckets (The request length metrics are not collected): `[]` | +| responseLengthBuckets | Buckets for the response length metrics (in bytes) histogram | no buckets (The response length metrics are not collected) `[]` | | extraMasks | Optional, list of regexes to be used as argument to [url-value-parser](https://www.npmjs.com/package/url-value-parser), this will cause extra route params, to be replaced with a `#val` placeholder. | no extra masks: `[]` | | authenticate | Optional authentication callback, the function should receive as argument, the `req` object and return truthy for sucessfull authentication, or falsy, otherwise. This option supports Promise results. | `null` | | prefix | Optional prefix for the metrics name | no prefix added | @@ -47,6 +49,8 @@ app.use(promMid({ metricsPath: '/metrics', collectDefaultMetrics: true, requestDurationBuckets: [0.1, 0.5, 1, 1.5], + requestLengthBuckets: [512, 1024, 5120, 10240, 51200, 102400], + responseLengthBuckets: [512, 1024, 5120, 10240, 51200, 102400], /** * Uncomenting the `authenticate` callback will make the `metricsPath` route * require authentication. This authentication callback can make a simple @@ -122,6 +126,18 @@ sum(rate(http_requests_total{status="5XX", app="myapp"}[5m])) histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{app="myapp"}[5m])) by (le)) ``` +#### 95% of request length + +```js +histogram_quantile(0.95, sum(rate(http_request_length_bytes_bucket{app="myapp"}[5m])) by (le)) +``` + +#### 95% of response length + +```js +histogram_quantile(0.95, sum(rate(http_response_length_bytes_bucket{app="myapp"}[5m])) by (le)) +``` + #### Average response time in seconds ```js diff --git a/src/index.js b/src/index.js index a502854..eb61e62 100644 --- a/src/index.js +++ b/src/index.js @@ -5,6 +5,8 @@ const ResponseTime = require('response-time'); const { requestCountGenerator, requestDurationGenerator, + requestLengthGenerator, + responseLengthGenerator, } = require('./metrics'); const { @@ -19,8 +21,10 @@ const defaultOptions = { collectDefaultMetrics: true, collectGCMetrics: false, // buckets for response time from 0.05s to 2.5s - // these are aribtrary values since i dont know any better ¯\_(ツ)_/¯ + // these are arbitrary values since i dont know any better ¯\_(ツ)_/¯ requestDurationBuckets: Prometheus.exponentialBuckets(0.05, 1.75, 8), + requestLengthBuckets: [], + responseLengthBuckets: [], extraMasks: [], customLabels: [], transformLabels: null, @@ -46,6 +50,16 @@ module.exports = (userOptions = {}) => { options.customLabels, options.prefix, ); + const requestLength = requestLengthGenerator( + options.customLabels, + options.requestLengthBuckets, + options.prefix, + ); + const responseLength = responseLengthGenerator( + options.customLabels, + options.responseLengthBuckets, + options.prefix, + ); /** * Corresponds to the R(equest rate), E(error rate), and D(uration of requests), @@ -71,6 +85,22 @@ module.exports = (userOptions = {}) => { // observe normalizing to seconds requestDuration.observe(labels, time / 1000); + + // observe request length + if (options.requestLengthBuckets.length) { + const reqLength = req.get('Content-Length'); + if (reqLength) { + requestLength.observe(labels, Number(reqLength)); + } + } + + // observe response length + if (options.responseLengthBuckets.length) { + const resLength = res.get('Content-Length'); + if (resLength) { + responseLength.observe(labels, Number(resLength)); + } + } } }); diff --git a/src/metrics.js b/src/metrics.js index d5100bf..d745859 100644 --- a/src/metrics.js +++ b/src/metrics.js @@ -26,7 +26,37 @@ function requestDurationGenerator(labelNames, buckets, prefix = '') { }); } +/** + * @param {!Array} buckets - array of numbers, representing the buckets for + * @param prefix - metrics name prefix + * request length + */ +function requestLengthGenerator(labelNames, buckets, prefix = '') { + return new Prometheus.Histogram({ + name: `${prefix}http_request_length_bytes`, + help: 'Content-Length of HTTP request', + labelNames, + buckets, + }); +} + +/** + * @param {!Array} buckets - array of numbers, representing the buckets for + * @param prefix - metrics name prefix + * response length + */ +function responseLengthGenerator(labelNames, buckets, prefix = '') { + return new Prometheus.Histogram({ + name: `${prefix}http_response_length_bytes`, + help: 'Content-Length of HTTP response', + labelNames, + buckets, + }); +} + module.exports = { requestCountGenerator, requestDurationGenerator, + requestLengthGenerator, + responseLengthGenerator, };