Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial #1

Merged
merged 12 commits into from
Nov 1, 2017
Merged
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
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,12 @@ typings/
# dotenv environment variables file
.env


# mac files
.DS_Store

# vim swap files
*.swp

# lock files
package-lock.json
11 changes: 11 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
language: node_js

node_js:
- "8"
- "6"
- "4"

notifications:
email:
on_success: never
on_failure: always
68 changes: 67 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,68 @@
# under-pressure
Automatic handling of "Service Unavailable" plugin

[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/)
[![Build Status](https://travis-ci.org/fastify/under-pressure.svg?branch=master)](https://travis-ci.org/fastify/under-pressure)

Measure process load with automatic handling of *"Service Unavailable"* plugin for Fastify.
It can check `maxEventLoopDelay`, `maxHeapUsedBytes` and `maxRssBytes` values.

<a name="install"></a>
## Install
```
npm i under-pressure --save
```

<a name="usage"></a>
## Usage
Require the plugin and register it into the Fastify instance.
```js
const fastify = require('fastify')()

fastify.register(require('under-pressure'), {
maxEventLoopDelay: 1000,
maxHeapUsedBytes: 100000000,
maxRssBytes: 100000000
})

fastify.get('/', (req, reply) => {
reply.send({ hello: 'world'})
})

fastify.listen(3000, err => {
if (err) throw err
console.log(`server listening on ${fastify.server.address().port}`)
})
```
`under-pressure` will automatically handle for you the `Service Unavailable` error once one of the thresholds has been reached.
You can configure the error message and the `Retry-After` header.
```js
fastify.register(require('under-pressure'), {
maxEventLoopDelay: 1000,
message: 'Under pressure!',
retryAfter: 50
})
```

The default value for `maxEventLoopDelay`, `maxHeapUsedBytes` and `maxRssBytes` is `0`.
If the value is `0` the check will not be performed.

Thanks to the encapsulation model of Fastify, you can selectively use this plugin in some subset of routes or even with different thresholds in different plugins.

#### `memoryUsage`
This plugin also exposes a function that will tell you the current values of `heapUsed`, `rssBytes` and `eventLoopDelay`.
```js
console.log(fastify.memoryUsage())
```

#### Status route
If needed you can pass `{ exposeStatusRoute: true }` and `under-pressure` will expose a `/status` route for you that sends back a `{ status: 'ok' }` object. This can be useful if you need to attach the server to an ELB on AWS for example.

<a name="acknowledgements"></a>
## Acknowledgements

This project is kindly sponsored by [LetzDoIt](http://www.letzdoitapp.com/).

<a name="license"></a>
## License

Licensed under [MIT](./LICENSE).
112 changes: 112 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
'use strict'

const fp = require('fastify-plugin')

function underPressure (fastify, opts, next) {
opts = opts || {}

const sampleInterval = opts.sampleInterval || 5
const maxEventLoopDelay = opts.maxEventLoopDelay || 0
const maxHeapUsedBytes = opts.maxHeapUsedBytes || 0
const maxRssBytes = opts.maxRssBytes || 0

const checkMaxEventLoopDelay = maxEventLoopDelay > 0
const checkMaxHeapUsedBytes = maxHeapUsedBytes > 0
const checkMaxRssBytes = maxRssBytes > 0

var heapUsed = 0
var rssBytes = 0
var eventLoopDelay = 0
var lastCheck = now()
const timer = setInterval(updateMemoryUsage, sampleInterval)

fastify.decorate('memoryUsage', memoryUsage)
fastify.addHook('onClose', onClose)

if (opts.exposeStatusRoute === true) {
fastify.route({
url: '/status',
method: 'GET',
schema: {
response: { 200: {
type: 'object',
properties: {
status: { type: 'string' }
}}
}
},
handler: onStatus
})
}

if (checkMaxEventLoopDelay === false &&
checkMaxHeapUsedBytes === false &&
checkMaxRssBytes === false) {
return next()
}

const serviceUnavailableError = new Error(opts.message || 'Service Unavailable')
const retryAfter = opts.retryAfter || 10

fastify.addHook('onRequest', onRequest)

function updateMemoryUsage () {
var mem = process.memoryUsage()
heapUsed = mem.heapUsed
rssBytes = mem.rss
var toCheck = now()
eventLoopDelay = toCheck - lastCheck - sampleInterval
lastCheck = toCheck
}

function onRequest (req, res, next) {
if (checkMaxEventLoopDelay && eventLoopDelay > maxEventLoopDelay) {
sendError(res, next)
return
}

if (checkMaxHeapUsedBytes && heapUsed > maxHeapUsedBytes) {
sendError(res, next)
return
}

if (checkMaxRssBytes && rssBytes > maxRssBytes) {
sendError(res, next)
return
}

next()
}

function sendError (res, next) {
res.statusCode = 503
res.setHeader('Retry-After', retryAfter)
next(serviceUnavailableError)
}

function memoryUsage () {
return {
eventLoopDelay,
rssBytes,
heapUsed
}
}

function onStatus (req, reply) {
reply.send({ status: 'ok' })
}

function onClose (fastify, done) {
clearInterval(timer)
done()
}

next()
}

function now () {
var ts = process.hrtime()
return (ts[0] * 1e3) + (ts[1] / 1e6)
}

module.exports = fp(underPressure, '>=0.27.0')
27 changes: 27 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "under-pressure",
"version": "0.1.0",
"description": "Measure process load with automatic handling of 'Service Unavailable' plugin for Fastify.",
"main": "index.js",
"scripts": {
"test": "standard && tap test.js"
},
"keywords": [
"fastify",
"service unavailable",
"limit",
"delay",
"retry"
],
"author": "Tomas Della Vedova - @delvedor (http://delved.org)",
"license": "MIT",
"dependencies": {
"fastify-plugin": "^0.1.1"
},
"devDependencies": {
"fastify": "^0.31.0",
"simple-get": "^2.7.0",
"standard": "^10.0.3",
"tap": "^10.7.2"
}
}
Loading