Skip to content

Commit

Permalink
Merge pull request #1 from fastify/init
Browse files Browse the repository at this point in the history
Initial
  • Loading branch information
delvedor authored Nov 1, 2017
2 parents 75a26f7 + bf4dc9e commit bafa5a2
Show file tree
Hide file tree
Showing 6 changed files with 460 additions and 1 deletion.
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

0 comments on commit bafa5a2

Please sign in to comment.