Comparing version 1.0.0 to 1.1.0
@@ -5,2 +5,20 @@ # Change Log | ||
<a name="1.1.0"></a> | ||
# [1.1.0](https://github.com/bucharest-gold/opossum/compare/v1.0.0...v1.1.0) (2017-06-06) | ||
### Bug Fixes | ||
* don't let circuits get stuck half open ([5e1171c](https://github.com/bucharest-gold/opossum/commit/5e1171c)) | ||
* fix logic around pendingClose ([4d89ae4](https://github.com/bucharest-gold/opossum/commit/4d89ae4)) | ||
### Features | ||
* add ETIMEDOUT error code for timeout error ([#64](https://github.com/bucharest-gold/opossum/issues/64)) ([5df9f65](https://github.com/bucharest-gold/opossum/commit/5df9f65)) | ||
* addition of rolling percentile latency's. GH-ISSUE [#38](https://github.com/bucharest-gold/opossum/issues/38) ([ce7b50d](https://github.com/bucharest-gold/opossum/commit/ce7b50d)) | ||
* remove fidelity promises. ([3f5827a](https://github.com/bucharest-gold/opossum/commit/3f5827a)) | ||
<a name="1.0.0"></a> | ||
@@ -7,0 +25,0 @@ # [1.0.0](https://github.com/bucharest-gold/opossum/compare/v0.6.0...v1.0.0) (2017-04-06) |
@@ -5,3 +5,2 @@ (() => { | ||
const CircuitBreaker = require('./lib/circuit'); | ||
const Fidelity = require('fidelity'); | ||
@@ -11,4 +10,3 @@ const defaults = { | ||
errorThresholdPercentage: 50, | ||
resetTimeout: 30000, // 30 seconds | ||
Promise: Fidelity | ||
resetTimeout: 30000 // 30 seconds | ||
}; | ||
@@ -30,5 +28,2 @@ | ||
* the breaker to `halfOpen` state, and trying the action again. | ||
* @param options.Promise {Promise} Opossum uses Fidelity promises, but works | ||
* fine with any Promise that follows the spec. You can specify your favored | ||
* implementation by providing the constructor as an option. | ||
* @return a {@link CircuitBreaker} instance | ||
@@ -35,0 +30,0 @@ */ |
@@ -42,2 +42,5 @@ 'use strict'; | ||
* @param options.name the name to use for this circuit when reporting stats | ||
* @param options.rollingPercentilesEnabled {boolean} This property indicates whether execution latencies | ||
* should be tracked and calculated as percentiles. | ||
* If they are disabled, all summary statistics (mean, percentiles) are returned as -1. | ||
*/ | ||
@@ -50,3 +53,3 @@ class CircuitBreaker extends EventEmitter { | ||
this.options.rollingCountBuckets = options.rollingCountBuckets || 10; | ||
this.Promise = options.Promise; | ||
this.options.rollingPercentilesEnabled = options.rollingPercentilesEnabled !== false; | ||
@@ -61,3 +64,3 @@ this[STATUS] = new Status(this.options); | ||
if (typeof action !== 'function') { | ||
this.action = _ => this.Promise.resolve(action); | ||
this.action = _ => Promise.resolve(action); | ||
} else this.action = action; | ||
@@ -67,3 +70,3 @@ | ||
const increment = property => _ => this[STATUS].increment(property); | ||
const increment = property => (result, runTime) => this[STATUS].increment(property, runTime); | ||
@@ -91,2 +94,3 @@ this.on('success', increment('successes')); | ||
circuit[STATE] = HALF_OPEN; | ||
circuit[PENDING_CLOSE] = true; | ||
circuit.emit('halfOpen', circuit.options.resetTimeout); | ||
@@ -101,3 +105,3 @@ }, circuit.options.resetTimeout); | ||
this.on('open', _startTimer(this)); | ||
this.on('success', () => this.close()); | ||
this.on('success', _ => this.close()); | ||
if (this.options.cache) { | ||
@@ -157,2 +161,6 @@ CACHE.set(this, undefined); | ||
get pendingClose () { | ||
return this[PENDING_CLOSE]; | ||
} | ||
/** | ||
@@ -262,3 +270,3 @@ * True if the circuit is currently closed. False otherwise. | ||
if (this.opened || (this.halfOpen && this[PENDING_CLOSE])) { | ||
if (this.opened && !this.pendingClose) { | ||
/** | ||
@@ -270,10 +278,11 @@ * Emitted when the circuit breaker is open and failing fast | ||
return fallback(this, 'Breaker is open', args) || | ||
return fallback(this, 'Breaker is open', args, 0) || | ||
Promise.reject(new Error('Breaker is open')); | ||
} | ||
this[PENDING_CLOSE] = this.halfOpen; | ||
this[PENDING_CLOSE] = false; | ||
let timeout; | ||
let timeoutError = false; | ||
return new this.Promise((resolve, reject) => { | ||
return new Promise((resolve, reject) => { | ||
const latencyStartTime = Date.now(); | ||
timeout = setTimeout( | ||
@@ -283,2 +292,3 @@ () => { | ||
const error = new Error(`Timed out after ${this.options.timeout}ms`); | ||
error.code = 'ETIMEDOUT'; | ||
/** | ||
@@ -288,4 +298,5 @@ * Emitted when the circuit breaker action takes longer than `options.timeout` | ||
*/ | ||
this.emit('timeout', error); | ||
resolve(fallback(this, error, args) || fail(this, error, args)); | ||
const latency = Date.now() - latencyStartTime; | ||
this.emit('timeout', error, latency); | ||
resolve(handleError(error, this, timeout, args, latency, resolve, reject)); | ||
}, this.options.timeout); | ||
@@ -297,23 +308,25 @@ | ||
? result | ||
: this.Promise.resolve(result); | ||
: Promise.resolve(result); | ||
promise | ||
.then((result) => { | ||
if (!timeoutError) { | ||
/** | ||
* Emitted when the circuit breaker action succeeds | ||
* @event CircuitBreaker#success | ||
*/ | ||
this.emit('success', result); | ||
resolve(result); | ||
if (this.options.cache) { | ||
CACHE.set(this, promise); | ||
} | ||
clearTimeout(timeout); | ||
promise.then((result) => { | ||
if (!timeoutError) { | ||
clearTimeout(timeout); | ||
/** | ||
* Emitted when the circuit breaker action succeeds | ||
* @event CircuitBreaker#success | ||
*/ | ||
this.emit('success', result, (Date.now() - latencyStartTime)); | ||
resolve(result); | ||
if (this.options.cache) { | ||
CACHE.set(this, promise); | ||
} | ||
}) | ||
.catch((error) => | ||
handleError(error, this, timeout, args, resolve, reject)); | ||
} | ||
}) | ||
.catch((error) => { | ||
const latencyEndTime = Date.now() - latencyStartTime; | ||
handleError(error, this, timeout, args, latencyEndTime, resolve, reject); | ||
}); | ||
} catch (error) { | ||
handleError(error, this, timeout, args, resolve, reject); | ||
const latency = Date.now() - latencyStartTime; | ||
handleError(error, this, timeout, args, latency, resolve, reject); | ||
} | ||
@@ -331,6 +344,6 @@ }); | ||
function handleError (error, circuit, timeout, args, resolve, reject) { | ||
function handleError (error, circuit, timeout, args, latency, resolve, reject) { | ||
clearTimeout(timeout); | ||
fail(circuit, error, args); | ||
const fb = fallback(circuit, error, args); | ||
fail(circuit, error, args, latency); | ||
const fb = fallback(circuit, error, args, latency); | ||
if (fb) resolve(fb); | ||
@@ -342,3 +355,3 @@ else reject(error); | ||
if (circuit[FALLBACK_FUNCTION]) { | ||
return new circuit.Promise((resolve, reject) => { | ||
return new Promise((resolve, reject) => { | ||
const result = circuit[FALLBACK_FUNCTION].apply(circuit[FALLBACK_FUNCTION], args); | ||
@@ -355,3 +368,3 @@ /** | ||
function fail (circuit, err, args) { | ||
function fail (circuit, err, args, latency) { | ||
/** | ||
@@ -361,3 +374,3 @@ * Emitted when the circuit breaker action fails | ||
*/ | ||
circuit.emit('failure', err); | ||
circuit.emit('failure', err, latency); | ||
@@ -371,4 +384,2 @@ // check stats to see if the circuit should be opened | ||
} | ||
return circuit.Promise.reject.apply(null, [err]); | ||
} | ||
@@ -375,0 +386,0 @@ |
@@ -34,16 +34,27 @@ 'use strict'; | ||
// TODO: caluclate these latency values | ||
json.latencyExecute_mean = 0; | ||
json.latencyExecute_mean = stats.latencyMean || 0; | ||
json.latencyExecute = { | ||
'0': 0, | ||
'25': 0, | ||
'50': 0, | ||
'75': 0, | ||
'90': 0, | ||
'95': 0, | ||
'99': 0, | ||
'99.5': 0, | ||
'100': 0 | ||
0: stats.percentiles['0'], | ||
25: stats.percentiles['0.25'], | ||
50: stats.percentiles['0.5'], | ||
75: stats.percentiles['0.75'], | ||
90: stats.percentiles['0.9'], | ||
95: stats.percentiles['0.95'], | ||
99: stats.percentiles['0.99'], | ||
99.5: stats.percentiles['0.995'], | ||
100: stats.percentiles['1'] | ||
}; | ||
json.latencyTotal_mean = 0; | ||
json.latencyTotal = { '0': 0, '25': 0, '50': 0, '75': 0, '90': 0, '95': 0, '99': 0, '99.5': 0, '100': 0 }; | ||
// Whats the difference between execute and total? | ||
json.latencyTotal_mean = stats.latencyMean; | ||
json.latencyTotal = { | ||
0: stats.percentiles['0'], | ||
25: stats.percentiles['0.25'], | ||
50: stats.percentiles['0.5'], | ||
75: stats.percentiles['0.75'], | ||
90: stats.percentiles['0.9'], | ||
95: stats.percentiles['0.95'], | ||
99: stats.percentiles['0.99'], | ||
99.5: stats.percentiles['0.995'], | ||
100: stats.percentiles['1'] | ||
}; | ||
json.propertyValue_circuitBreakerRequestVolumeThreshold = 5; | ||
@@ -50,0 +61,0 @@ json.propertyValue_circuitBreakerSleepWindowInMilliseconds = stats.options.resetTimeout; |
'use strict'; | ||
const Fidelity = require('fidelity'); | ||
module.exports = exports = function promisify (func) { | ||
return function promisifiedFunction () { | ||
return new Fidelity((resolve, reject) => { | ||
return new Promise((resolve, reject) => { | ||
const cb = (err, result) => { | ||
@@ -9,0 +7,0 @@ if (err) reject(err); |
@@ -6,2 +6,3 @@ 'use strict'; | ||
const TIMEOUT = Symbol('timeout'); | ||
const PERCENTILES = Symbol('percentiles'); | ||
@@ -64,3 +65,7 @@ const EventEmitter = require('events').EventEmitter; | ||
this[WINDOW] = new Array(this[BUCKETS]); | ||
this[PERCENTILES] = [0.0, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99, 0.995, 1]; | ||
// Default this value to true | ||
this.rollingPercentilesEnabled = options.rollingPercentilesEnabled; | ||
// prime the window with buckets | ||
@@ -86,8 +91,41 @@ for (let i = 0; i < this[BUCKETS]; i++) this[WINDOW][i] = bucket(); | ||
get stats () { | ||
return this[WINDOW].reduce((acc, val) => { | ||
const totals = this[WINDOW].reduce((acc, val) => { | ||
// the window starts with all but one bucket undefined | ||
if (!val) return acc; | ||
Object.keys(acc).forEach(key => (acc[key] += val[key] || 0)); | ||
Object.keys(acc).forEach(key => { | ||
if (key !== 'latencyTimes' && key !== 'percentiles') { | ||
(acc[key] += val[key] || 0); | ||
} | ||
}); | ||
if (this.rollingPercentilesEnabled) { | ||
acc.latencyTimes.push.apply(acc.latencyTimes, val.latencyTimes || []); | ||
} | ||
return acc; | ||
}, bucket()); | ||
if (this.rollingPercentilesEnabled) { | ||
// Sort the latencyTimess | ||
totals.latencyTimes.sort((a, b) => a - b); | ||
// Get the mean latency | ||
// Mean = sum of all values in the array/length of array | ||
if (totals.latencyTimes.length) { | ||
totals.latencyMean = (totals.latencyTimes.reduce((a, b) => a + b, 0)) / totals.latencyTimes.length; | ||
} else { | ||
totals.latencyMean = 0; | ||
} | ||
// Calculate Percentiles | ||
this[PERCENTILES].forEach(percentile => { | ||
totals.percentiles[percentile] = calculatePercentile(percentile, totals.latencyTimes); | ||
}); | ||
} else { | ||
totals.latencyMean = -1; | ||
this[PERCENTILES].forEach(percentile => { | ||
totals.percentiles[percentile] = -1; | ||
}); | ||
} | ||
return totals; | ||
} | ||
@@ -102,4 +140,7 @@ | ||
increment (property) { | ||
increment (property, latencyRunTime) { | ||
this[WINDOW][0][property]++; | ||
if (property === 'successes' || property === 'failures' || property === 'timeouts') { | ||
this[WINDOW][0].latencyTimes.push(latencyRunTime || 0); | ||
} | ||
} | ||
@@ -129,5 +170,15 @@ | ||
cacheHits: 0, | ||
cacheMisses: 0 | ||
cacheMisses: 0, | ||
percentiles: {}, | ||
latencyTimes: [] | ||
}); | ||
function calculatePercentile (percentile, arr) { | ||
if (percentile === 0) { | ||
return arr[0] || 0; | ||
} | ||
const idx = Math.ceil(percentile * arr.length); | ||
return arr[idx - 1] || 0; | ||
} | ||
module.exports = exports = Status; |
{ | ||
"name": "opossum", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"author": "Red Hat, Inc.", | ||
@@ -46,14 +46,16 @@ "license": "Apache-2.0", | ||
"babel-plugin-transform-mangle-names": "~2.1.2", | ||
"babel-plugin-transform-merge-sibling-variables": "6.8.2", | ||
"babel-plugin-transform-merge-sibling-variables": "6.8.3", | ||
"babel-plugin-transform-minify-booleans": "~6.8.0", | ||
"babel-plugin-transform-undefined-to-void": "~6.8.0", | ||
"browserify": "~14.1.0", | ||
"browserify": "~14.3.0", | ||
"escompress": "~0.5.0", | ||
"eslint": "~3.19.0", | ||
"eslint-config-semistandard": "~7.0.0", | ||
"eslint-config-standard": "~7.0.0", | ||
"eslint-plugin-react": "~6.10.0", | ||
"eslint-config-semistandard": "~11.0.0", | ||
"eslint-config-standard": "~10.2.0", | ||
"eslint-plugin-import": "^2.2.0", | ||
"eslint-plugin-node": "^5.0.0", | ||
"eslint-plugin-promise": "~3.5.0", | ||
"eslint-plugin-standard": "~2.1.0", | ||
"http-server": "~0.9.0", | ||
"eslint-plugin-react": "~7.0.0", | ||
"eslint-plugin-standard": "~3.0.1", | ||
"http-server": "~0.10.0", | ||
"ink-docstrap": "~1.3.0", | ||
@@ -75,5 +77,3 @@ "istanbul": "~0.4.5", | ||
], | ||
"dependencies": { | ||
"fidelity": "~4.2.0" | ||
} | ||
"dependencies": {} | ||
} |
@@ -23,3 +23,3 @@ # opossum | ||
| Issue tracker: | https://github.com/bucharest-gold/opossum/issues | | ||
| Engines: | Node.js 4.x, 5.x, 6.x, 7.x | ||
| Engines: | Node.js 4.x, 6.x, 8.x | ||
@@ -222,16 +222,2 @@ ## Usage | ||
### Promise Interoperability | ||
The `Promise` implementation used in `opossum` is compliant with both the | ||
ES6 `Promise` API as well as the `promises/A+` API. This means that it doesn't | ||
matter what flavor of promise your API uses, `opossum` should work fine with | ||
it. If you would like to control what `Promise` implementation used in | ||
`opossum`, provide a `Promise` constructor function in your options when | ||
you create the breaker. E.g. | ||
```javascript | ||
// Force opossum to use native JS promises | ||
const breaker = circuitBreaker(readFile, { Promise: Promise }); | ||
``` | ||
### Hystrix Metrics | ||
@@ -238,0 +224,0 @@ |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
0
5
0
298946
26
6026
228
- Removedfidelity@~4.2.0
- Removedfidelity@4.2.0(transitive)