diff --git a/README.md b/README.md index a65439e5..c70715a6 100644 --- a/README.md +++ b/README.md @@ -241,55 +241,15 @@ If you'd like to add them, run `npm install @types/opossum` in your project. ### Metrics #### Prometheus -Provide `{ usePrometheus: true }` in the options when creating a circuit to produce -metrics that are consumable by Prometheus. These metrics include information about -the circuit itself, for example how many times it has opened, as well as general Node.js -statistics, for example event loop lag. To get consolidated metrics for all circuits in your -application, use the `metrics()` function on the factory. +The [`opossum-prometheus`](https://github.com/nodeshift/opossum-prometheus) module +can be used to produce metrics that are consumable by Prometheus. +These metrics include information about the circuit itself, for example how many +times it has opened, as well as general Node.js statistics, for example event loop lag. -```js -const opossum = require('opossum'); - -// create a circuit -const circuit = opossum(functionThatMightFail, { usePrometheus: true }); - -// In an express app, expose the metrics to the Prometheus server -app.use('/metrics', (req, res) => { - res.type('text/plain'); - res.send(opossum.metrics()); -}); -``` - -The `prometheusRegistry` option allows to provide a existing -[prom-client](https://github.com/siimon/prom-client) registry. -The metrics about the circuit will be added to the provided registry instead -of the global registry. -The [default metrics](https://github.com/siimon/prom-client#default-metrics) -will not be added to the provided registry. - -```js -const opossum = require('opossum'); -const { Registry } = require('prom-client'); - -// Create a registry -const prometheusRegistry = new Registry(); - -// create a circuit -const circuit = opossum(functionThatMightFail, { - usePrometheus: true, - prometheusRegistry -}); -``` #### Hystrix - -**NOTE: Hystrix metrics are deprecated** - -A Hystrix Stream is available for use with a Hystrix Dashboard using the `circuitBreaker.hystrixStats.getHystrixStream` method. - -This method returns a [Node.js Stream](https://nodejs.org/api/stream.html), which makes it easy to create an SSE stream that will be compliant with a Hystrix Dashboard. - -Additional Reading: [Hystrix Metrics Event Stream](https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-metrics-event-stream), [Turbine](https://github.com/Netflix/Turbine/wiki), [Hystrix Dashboard](https://github.com/Netflix/Hystrix/wiki/Dashboard) +The [`opossum-hystrix`](https://github.com/nodeshift/opossum-hystrix) module can +be used to produce metrics that are consumable by the Hystrix Dashboard. ## Troubleshooting @@ -303,7 +263,7 @@ You may run into issues related to too many listeners on an `EventEmitter` like (node:25619) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 finish listeners added. Use emitter.setMaxListeners() to increase limit ``` -This typically occurs when you have created more than ten `CircuitBreaker` instances. This is due to the fact that every circuit created is adding a listener to the stream accessed via `circuit.stats.getHystrixStream()`, and the default `EventEmitter` listener limit is 10. In some cases, seeing this error might indicate a bug in client code, where many `CircuitBreaker`s are inadvertently being created. But there are legitimate scenarios where this may not be the case. For example, it could just be that you need more than 10 `CircuitBreaker`s in your app. That's ok. +In some cases, seeing this error might indicate a bug in client code, where many `CircuitBreaker`s are inadvertently being created. But there are legitimate scenarios where this may not be the case. For example, it could just be that you need more than 10 `CircuitBreaker`s in your app. That's ok. To get around the error, you can set the number of listeners on the stream. diff --git a/docs/assets/split.js b/docs/assets/split.js index fc492d2e..71f9a60b 100644 --- a/docs/assets/split.js +++ b/docs/assets/split.js @@ -1,586 +1,782 @@ -/*! Split.js - v1.3.5 */ -// https://github.com/nathancahill/Split.js -// Copyright (c) 2017 Nathan Cahill; Licensed MIT - -(function(global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' - ? (module.exports = factory()) - : typeof define === 'function' && define.amd - ? define(factory) - : (global.Split = factory()); -})(this, function() { - 'use strict'; - // The programming goals of Split.js are to deliver readable, understandable and - // maintainable code, while at the same time manually optimizing for tiny minified file size, - // browser compatibility without additional requirements, graceful fallback (IE8 is supported) - // and very few assumptions about the user's page layout. - var global = window; - var document = global.document; - - // Save a couple long function names that are used frequently. - // This optimization saves around 400 bytes. - var addEventListener = 'addEventListener'; - var removeEventListener = 'removeEventListener'; - var getBoundingClientRect = 'getBoundingClientRect'; - var NOOP = function() { - return false; - }; - - // Figure out if we're in IE8 or not. IE8 will still render correctly, - // but will be static instead of draggable. - var isIE8 = global.attachEvent && !global[addEventListener]; - - // This library only needs two helper functions: - // - // The first determines which prefixes of CSS calc we need. - // We only need to do this once on startup, when this anonymous function is called. - // - // Tests -webkit, -moz and -o prefixes. Modified from StackOverflow: - // http://stackoverflow.com/questions/16625140/js-feature-detection-to-detect-the-usage-of-webkit-calc-over-calc/16625167#16625167 - var calc = - ['', '-webkit-', '-moz-', '-o-'] - .filter(function(prefix) { - var el = document.createElement('div'); - el.style.cssText = 'width:' + prefix + 'calc(9px)'; - - return !!el.style.length; - }) - .shift() + 'calc'; - - // The second helper function allows elements and string selectors to be used - // interchangeably. In either case an element is returned. This allows us to - // do `Split([elem1, elem2])` as well as `Split(['#id1', '#id2'])`. - var elementOrSelector = function(el) { - if (typeof el === 'string' || el instanceof String) { - return document.querySelector(el); - } - - return el; - }; - - // The main function to initialize a split. Split.js thinks about each pair - // of elements as an independant pair. Dragging the gutter between two elements - // only changes the dimensions of elements in that pair. This is key to understanding - // how the following functions operate, since each function is bound to a pair. - // - // A pair object is shaped like this: - // - // { - // a: DOM element, - // b: DOM element, - // aMin: Number, - // bMin: Number, - // dragging: Boolean, - // parent: DOM element, - // isFirst: Boolean, - // isLast: Boolean, - // direction: 'horizontal' | 'vertical' - // } - // - // The basic sequence: - // - // 1. Set defaults to something sane. `options` doesn't have to be passed at all. - // 2. Initialize a bunch of strings based on the direction we're splitting. - // A lot of the behavior in the rest of the library is paramatized down to - // rely on CSS strings and classes. - // 3. Define the dragging helper functions, and a few helpers to go with them. - // 4. Loop through the elements while pairing them off. Every pair gets an - // `pair` object, a gutter, and special isFirst/isLast properties. - // 5. Actually size the pair elements, insert gutters and attach event listeners. - var Split = function(ids, options) { - if (options === void 0) options = {}; - - var dimension; - var clientDimension; - var clientAxis; - var position; - var paddingA; - var paddingB; - var elements; - - // All DOM elements in the split should have a common parent. We can grab - // the first elements parent and hope users read the docs because the - // behavior will be whacky otherwise. - var parent = elementOrSelector(ids[0]).parentNode; - var parentFlexDirection = global.getComputedStyle(parent).flexDirection; - - // Set default options.sizes to equal percentages of the parent element. - var sizes = - options.sizes || - ids.map(function() { - return 100 / ids.length; - }); - - // Standardize minSize to an array if it isn't already. This allows minSize - // to be passed as a number. - var minSize = options.minSize !== undefined ? options.minSize : 100; - var minSizes = Array.isArray(minSize) - ? minSize - : ids.map(function() { - return minSize; - }); - var gutterSize = options.gutterSize !== undefined ? options.gutterSize : 10; - var snapOffset = options.snapOffset !== undefined ? options.snapOffset : 30; - var direction = options.direction || 'horizontal'; - var cursor = - options.cursor || - (direction === 'horizontal' ? 'ew-resize' : 'ns-resize'); - var gutter = - options.gutter || - function(i, gutterDirection) { +/*! Split.js - v1.5.11 */ + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.Split = factory()); +}(this, (function () { 'use strict'; + + // The programming goals of Split.js are to deliver readable, understandable and + // maintainable code, while at the same time manually optimizing for tiny minified file size, + // browser compatibility without additional requirements, graceful fallback (IE8 is supported) + // and very few assumptions about the user's page layout. + var global = window; + var document = global.document; + + // Save a couple long function names that are used frequently. + // This optimization saves around 400 bytes. + var addEventListener = 'addEventListener'; + var removeEventListener = 'removeEventListener'; + var getBoundingClientRect = 'getBoundingClientRect'; + var gutterStartDragging = '_a'; + var aGutterSize = '_b'; + var bGutterSize = '_c'; + var HORIZONTAL = 'horizontal'; + var NOOP = function () { return false; }; + + // Figure out if we're in IE8 or not. IE8 will still render correctly, + // but will be static instead of draggable. + var isIE8 = global.attachEvent && !global[addEventListener]; + + // Helper function determines which prefixes of CSS calc we need. + // We only need to do this once on startup, when this anonymous function is called. + // + // Tests -webkit, -moz and -o prefixes. Modified from StackOverflow: + // http://stackoverflow.com/questions/16625140/js-feature-detection-to-detect-the-usage-of-webkit-calc-over-calc/16625167#16625167 + var calc = (['', '-webkit-', '-moz-', '-o-'] + .filter(function (prefix) { + var el = document.createElement('div'); + el.style.cssText = "width:" + prefix + "calc(9px)"; + + return !!el.style.length + }) + .shift()) + "calc"; + + // Helper function checks if its argument is a string-like type + var isString = function (v) { return typeof v === 'string' || v instanceof String; }; + + // Helper function allows elements and string selectors to be used + // interchangeably. In either case an element is returned. This allows us to + // do `Split([elem1, elem2])` as well as `Split(['#id1', '#id2'])`. + var elementOrSelector = function (el) { + if (isString(el)) { + var ele = document.querySelector(el); + if (!ele) { + throw new Error(("Selector " + el + " did not match a DOM element")) + } + return ele + } + + return el + }; + + // Helper function gets a property from the properties object, with a default fallback + var getOption = function (options, propName, def) { + var value = options[propName]; + if (value !== undefined) { + return value + } + return def + }; + + var getGutterSize = function (gutterSize, isFirst, isLast, gutterAlign) { + if (isFirst) { + if (gutterAlign === 'end') { + return 0 + } + if (gutterAlign === 'center') { + return gutterSize / 2 + } + } else if (isLast) { + if (gutterAlign === 'start') { + return 0 + } + if (gutterAlign === 'center') { + return gutterSize / 2 + } + } + + return gutterSize + }; + + // Default options + var defaultGutterFn = function (i, gutterDirection) { var gut = document.createElement('div'); - gut.className = 'gutter gutter-' + gutterDirection; - return gut; - }; - var elementStyle = - options.elementStyle || - function(dim, size, gutSize) { + gut.className = "gutter gutter-" + gutterDirection; + return gut + }; + + var defaultElementStyleFn = function (dim, size, gutSize) { var style = {}; - if (typeof size !== 'string' && !(size instanceof String)) { - if (!isIE8) { - style[dim] = calc + '(' + size + '% - ' + gutSize + 'px)'; - } else { - style[dim] = size + '%'; - } + if (!isString(size)) { + if (!isIE8) { + style[dim] = calc + "(" + size + "% - " + gutSize + "px)"; + } else { + style[dim] = size + "%"; + } } else { - style[dim] = size; + style[dim] = size; } - return style; - }; - var gutterStyle = - options.gutterStyle || - function(dim, gutSize) { - return (obj = {}), (obj[dim] = gutSize + 'px'), obj; + return style + }; + + var defaultGutterStyleFn = function (dim, gutSize) { var obj; - }; - // 2. Initialize a bunch of strings based on the direction we're splitting. - // A lot of the behavior in the rest of the library is paramatized down to - // rely on CSS strings and classes. - if (direction === 'horizontal') { - dimension = 'width'; - clientDimension = 'clientWidth'; - clientAxis = 'clientX'; - position = 'left'; - paddingA = 'paddingLeft'; - paddingB = 'paddingRight'; - } else if (direction === 'vertical') { - dimension = 'height'; - clientDimension = 'clientHeight'; - clientAxis = 'clientY'; - position = 'top'; - paddingA = 'paddingTop'; - paddingB = 'paddingBottom'; - } + return (( obj = {}, obj[dim] = (gutSize + "px"), obj )); + }; - // 3. Define the dragging helper functions, and a few helpers to go with them. - // Each helper is bound to a pair object that contains it's metadata. This - // also makes it easy to store references to listeners that that will be - // added and removed. - // - // Even though there are no other functions contained in them, aliasing - // this to self saves 50 bytes or so since it's used so frequently. - // - // The pair object saves metadata like dragging state, position and - // event listener references. - - function setElementSize(el, size, gutSize) { - // Split.js allows setting sizes via numbers (ideally), or if you must, - // by string, like '300px'. This is less than ideal, because it breaks - // the fluid layout that `calc(% - px)` provides. You're on your own if you do that, - // make sure you calculate the gutter size by hand. - var style = elementStyle(dimension, size, gutSize); - - // eslint-disable-next-line no-param-reassign - Object.keys(style).forEach(function(prop) { - return (el.style[prop] = style[prop]); - }); - } - - function setGutterSize(gutterElement, gutSize) { - var style = gutterStyle(dimension, gutSize); - - // eslint-disable-next-line no-param-reassign - Object.keys(style).forEach(function(prop) { - return (gutterElement.style[prop] = style[prop]); - }); - } - - // Actually adjust the size of elements `a` and `b` to `offset` while dragging. - // calc is used to allow calc(percentage + gutterpx) on the whole split instance, - // which allows the viewport to be resized without additional logic. - // Element a's size is the same as offset. b's size is total size - a size. - // Both sizes are calculated from the initial parent percentage, - // then the gutter size is subtracted. - function adjust(offset) { - var a = elements[this.a]; - var b = elements[this.b]; - var percentage = a.size + b.size; - - a.size = (offset / this.size) * percentage; - b.size = percentage - (offset / this.size) * percentage; - - setElementSize(a.element, a.size, this.aGutterSize); - setElementSize(b.element, b.size, this.bGutterSize); - } - - // drag, where all the magic happens. The logic is really quite simple: + // The main function to initialize a split. Split.js thinks about each pair + // of elements as an independant pair. Dragging the gutter between two elements + // only changes the dimensions of elements in that pair. This is key to understanding + // how the following functions operate, since each function is bound to a pair. // - // 1. Ignore if the pair is not dragging. - // 2. Get the offset of the event. - // 3. Snap offset to min if within snappable range (within min + snapOffset). - // 4. Actually adjust each element in the pair to offset. + // A pair object is shaped like this: // - // --------------------------------------------------------------------- - // | | <- a.minSize || b.minSize -> | | - // | | | <- this.snapOffset || this.snapOffset -> | | | - // | | | || | | | - // | | | || | | | - // --------------------------------------------------------------------- - // | <- this.start this.size -> | - function drag(e) { - var offset; - - if (!this.dragging) { - return; - } - - // Get the offset of the event from the first side of the - // pair `this.start`. Supports touch events, but not multitouch, so only the first - // finger `touches[0]` is counted. - if ('touches' in e) { - offset = e.touches[0][clientAxis] - this.start; - } else { - offset = e[clientAxis] - this.start; - } - - // If within snapOffset of min or max, set offset to min or max. - // snapOffset buffers a.minSize and b.minSize, so logic is opposite for both. - // Include the appropriate gutter sizes to prevent overflows. - if (offset <= elements[this.a].minSize + snapOffset + this.aGutterSize) { - offset = elements[this.a].minSize + this.aGutterSize; - } else if ( - offset >= - this.size - (elements[this.b].minSize + snapOffset + this.bGutterSize) - ) { - offset = this.size - (elements[this.b].minSize + this.bGutterSize); - } - - // Actually adjust the size. - adjust.call(this, offset); - - // Call the drag callback continously. Don't do anything too intensive - // in this callback. - if (options.onDrag) { - options.onDrag(); - } - } - - // Cache some important sizes when drag starts, so we don't have to do that - // continously: + // { + // a: DOM element, + // b: DOM element, + // aMin: Number, + // bMin: Number, + // dragging: Boolean, + // parent: DOM element, + // direction: 'horizontal' | 'vertical' + // } // - // `size`: The total size of the pair. First + second + first gutter + second gutter. - // `start`: The leading side of the first element. + // The basic sequence: // - // ------------------------------------------------ - // | aGutterSize -> ||| | - // | ||| | - // | ||| | - // | ||| <- bGutterSize | - // ------------------------------------------------ - // | <- start size -> | - function calculateSizes() { - // Figure out the parent size minus padding. - var a = elements[this.a].element; - var b = elements[this.b].element; - - this.size = - a[getBoundingClientRect]()[dimension] + - b[getBoundingClientRect]()[dimension] + - this.aGutterSize + - this.bGutterSize; - this.start = a[getBoundingClientRect]()[position]; - } - - // stopDragging is very similar to startDragging in reverse. - function stopDragging() { - var self = this; - var a = elements[self.a].element; - var b = elements[self.b].element; - - if (self.dragging && options.onDragEnd) { - options.onDragEnd(); - } - - self.dragging = false; - - // Remove the stored event listeners. This is why we store them. - global[removeEventListener]('mouseup', self.stop); - global[removeEventListener]('touchend', self.stop); - global[removeEventListener]('touchcancel', self.stop); - - self.parent[removeEventListener]('mousemove', self.move); - self.parent[removeEventListener]('touchmove', self.move); - - // Delete them once they are removed. I think this makes a difference - // in memory usage with a lot of splits on one page. But I don't know for sure. - delete self.stop; - delete self.move; - - a[removeEventListener]('selectstart', NOOP); - a[removeEventListener]('dragstart', NOOP); - b[removeEventListener]('selectstart', NOOP); - b[removeEventListener]('dragstart', NOOP); - - a.style.userSelect = ''; - a.style.webkitUserSelect = ''; - a.style.MozUserSelect = ''; - a.style.pointerEvents = ''; - - b.style.userSelect = ''; - b.style.webkitUserSelect = ''; - b.style.MozUserSelect = ''; - b.style.pointerEvents = ''; - - self.gutter.style.cursor = ''; - self.parent.style.cursor = ''; - } - - // startDragging calls `calculateSizes` to store the inital size in the pair object. - // It also adds event listeners for mouse/touch events, - // and prevents selection while dragging so avoid the selecting text. - function startDragging(e) { - // Alias frequently used variables to save space. 200 bytes. - var self = this; - var a = elements[self.a].element; - var b = elements[self.b].element; - - // Call the onDragStart callback. - if (!self.dragging && options.onDragStart) { - options.onDragStart(); - } - - // Don't actually drag the element. We emulate that in the drag function. - e.preventDefault(); - - // Set the dragging property of the pair object. - self.dragging = true; - - // Create two event listeners bound to the same pair object and store - // them in the pair object. - self.move = drag.bind(self); - self.stop = stopDragging.bind(self); - - // All the binding. `window` gets the stop events in case we drag out of the elements. - global[addEventListener]('mouseup', self.stop); - global[addEventListener]('touchend', self.stop); - global[addEventListener]('touchcancel', self.stop); - - self.parent[addEventListener]('mousemove', self.move); - self.parent[addEventListener]('touchmove', self.move); - - // Disable selection. Disable! - a[addEventListener]('selectstart', NOOP); - a[addEventListener]('dragstart', NOOP); - b[addEventListener]('selectstart', NOOP); - b[addEventListener]('dragstart', NOOP); - - a.style.userSelect = 'none'; - a.style.webkitUserSelect = 'none'; - a.style.MozUserSelect = 'none'; - a.style.pointerEvents = 'none'; - - b.style.userSelect = 'none'; - b.style.webkitUserSelect = 'none'; - b.style.MozUserSelect = 'none'; - b.style.pointerEvents = 'none'; - - // Set the cursor, both on the gutter and the parent element. - // Doing only a, b and gutter causes flickering. - self.gutter.style.cursor = cursor; - self.parent.style.cursor = cursor; - - // Cache the initial sizes of the pair. - calculateSizes.call(self); - } - - // 5. Create pair and element objects. Each pair has an index reference to - // elements `a` and `b` of the pair (first and second elements). - // Loop through the elements while pairing them off. Every pair gets a - // `pair` object, a gutter, and isFirst/isLast properties. - // - // Basic logic: - // - // - Starting with the second element `i > 0`, create `pair` objects with - // `a = i - 1` and `b = i` - // - Set gutter sizes based on the _pair_ being first/last. The first and last - // pair have gutterSize / 2, since they only have one half gutter, and not two. - // - Create gutter elements and add event listeners. - // - Set the size of the elements, minus the gutter sizes. - // - // ----------------------------------------------------------------------- - // | i=0 | i=1 | i=2 | i=3 | - // | | isFirst | | isLast | - // | pair 0 pair 1 pair 2 | - // | | | | | - // ----------------------------------------------------------------------- - var pairs = []; - elements = ids.map(function(id, i) { - // Create the element object. - var element = { - element: elementOrSelector(id), - size: sizes[i], - minSize: minSizes[i] - }; - - var pair; - - if (i > 0) { - // Create the pair object with it's metadata. - pair = { - a: i - 1, - b: i, - dragging: false, - isFirst: i === 1, - isLast: i === ids.length - 1, - direction: direction, - parent: parent - }; - - // For first and last pairs, first and last gutter width is half. - pair.aGutterSize = gutterSize; - pair.bGutterSize = gutterSize; - - if (pair.isFirst) { - pair.aGutterSize = gutterSize / 2; + // 1. Set defaults to something sane. `options` doesn't have to be passed at all. + // 2. Initialize a bunch of strings based on the direction we're splitting. + // A lot of the behavior in the rest of the library is paramatized down to + // rely on CSS strings and classes. + // 3. Define the dragging helper functions, and a few helpers to go with them. + // 4. Loop through the elements while pairing them off. Every pair gets an + // `pair` object and a gutter. + // 5. Actually size the pair elements, insert gutters and attach event listeners. + var Split = function (idsOption, options) { + if ( options === void 0 ) options = {}; + + var ids = idsOption; + var dimension; + var clientAxis; + var position; + var positionEnd; + var clientSize; + var elements; + + // Allow HTMLCollection to be used as an argument when supported + if (Array.from) { + ids = Array.from(ids); + } + + // All DOM elements in the split should have a common parent. We can grab + // the first elements parent and hope users read the docs because the + // behavior will be whacky otherwise. + var firstElement = elementOrSelector(ids[0]); + var parent = firstElement.parentNode; + var parentStyle = getComputedStyle ? getComputedStyle(parent) : null; + var parentFlexDirection = parentStyle ? parentStyle.flexDirection : null; + + // Set default options.sizes to equal percentages of the parent element. + var sizes = getOption(options, 'sizes') || ids.map(function () { return 100 / ids.length; }); + + // Standardize minSize to an array if it isn't already. This allows minSize + // to be passed as a number. + var minSize = getOption(options, 'minSize', 100); + var minSizes = Array.isArray(minSize) ? minSize : ids.map(function () { return minSize; }); + + // Get other options + var expandToMin = getOption(options, 'expandToMin', false); + var gutterSize = getOption(options, 'gutterSize', 10); + var gutterAlign = getOption(options, 'gutterAlign', 'center'); + var snapOffset = getOption(options, 'snapOffset', 30); + var dragInterval = getOption(options, 'dragInterval', 1); + var direction = getOption(options, 'direction', HORIZONTAL); + var cursor = getOption( + options, + 'cursor', + direction === HORIZONTAL ? 'col-resize' : 'row-resize' + ); + var gutter = getOption(options, 'gutter', defaultGutterFn); + var elementStyle = getOption( + options, + 'elementStyle', + defaultElementStyleFn + ); + var gutterStyle = getOption(options, 'gutterStyle', defaultGutterStyleFn); + + // 2. Initialize a bunch of strings based on the direction we're splitting. + // A lot of the behavior in the rest of the library is paramatized down to + // rely on CSS strings and classes. + if (direction === HORIZONTAL) { + dimension = 'width'; + clientAxis = 'clientX'; + position = 'left'; + positionEnd = 'right'; + clientSize = 'clientWidth'; + } else if (direction === 'vertical') { + dimension = 'height'; + clientAxis = 'clientY'; + position = 'top'; + positionEnd = 'bottom'; + clientSize = 'clientHeight'; + } + + // 3. Define the dragging helper functions, and a few helpers to go with them. + // Each helper is bound to a pair object that contains its metadata. This + // also makes it easy to store references to listeners that that will be + // added and removed. + // + // Even though there are no other functions contained in them, aliasing + // this to self saves 50 bytes or so since it's used so frequently. + // + // The pair object saves metadata like dragging state, position and + // event listener references. + + function setElementSize(el, size, gutSize, i) { + // Split.js allows setting sizes via numbers (ideally), or if you must, + // by string, like '300px'. This is less than ideal, because it breaks + // the fluid layout that `calc(% - px)` provides. You're on your own if you do that, + // make sure you calculate the gutter size by hand. + var style = elementStyle(dimension, size, gutSize, i); + + Object.keys(style).forEach(function (prop) { + // eslint-disable-next-line no-param-reassign + el.style[prop] = style[prop]; + }); + } + + function setGutterSize(gutterElement, gutSize, i) { + var style = gutterStyle(dimension, gutSize, i); + + Object.keys(style).forEach(function (prop) { + // eslint-disable-next-line no-param-reassign + gutterElement.style[prop] = style[prop]; + }); } - if (pair.isLast) { - pair.bGutterSize = gutterSize / 2; + function getSizes() { + return elements.map(function (element) { return element.size; }) } - // if the parent has a reverse flex-direction, switch the pair elements. - if ( - parentFlexDirection === 'row-reverse' || - parentFlexDirection === 'column-reverse' - ) { - var temp = pair.a; - pair.a = pair.b; - pair.b = temp; + // Supports touch events, but not multitouch, so only the first + // finger `touches[0]` is counted. + function getMousePosition(e) { + if ('touches' in e) { return e.touches[0][clientAxis] } + return e[clientAxis] } - } - - // Determine the size of the current element. IE8 is supported by - // staticly assigning sizes without draggable gutters. Assigns a string - // to `size`. - // - // IE9 and above - if (!isIE8) { - // Create gutter elements for each pair. - if (i > 0) { - var gutterElement = gutter(i, direction); - setGutterSize(gutterElement, gutterSize); - - gutterElement[addEventListener]( - 'mousedown', - startDragging.bind(pair) - ); - gutterElement[addEventListener]( - 'touchstart', - startDragging.bind(pair) - ); - - parent.insertBefore(gutterElement, element.element); - - pair.gutter = gutterElement; + + // Actually adjust the size of elements `a` and `b` to `offset` while dragging. + // calc is used to allow calc(percentage + gutterpx) on the whole split instance, + // which allows the viewport to be resized without additional logic. + // Element a's size is the same as offset. b's size is total size - a size. + // Both sizes are calculated from the initial parent percentage, + // then the gutter size is subtracted. + function adjust(offset) { + var a = elements[this.a]; + var b = elements[this.b]; + var percentage = a.size + b.size; + + a.size = (offset / this.size) * percentage; + b.size = percentage - (offset / this.size) * percentage; + + setElementSize(a.element, a.size, this[aGutterSize], a.i); + setElementSize(b.element, b.size, this[bGutterSize], b.i); + } + + // drag, where all the magic happens. The logic is really quite simple: + // + // 1. Ignore if the pair is not dragging. + // 2. Get the offset of the event. + // 3. Snap offset to min if within snappable range (within min + snapOffset). + // 4. Actually adjust each element in the pair to offset. + // + // --------------------------------------------------------------------- + // | | <- a.minSize || b.minSize -> | | + // | | | <- this.snapOffset || this.snapOffset -> | | | + // | | | || | | | + // | | | || | | | + // --------------------------------------------------------------------- + // | <- this.start this.size -> | + function drag(e) { + var offset; + var a = elements[this.a]; + var b = elements[this.b]; + + if (!this.dragging) { return } + + // Get the offset of the event from the first side of the + // pair `this.start`. Then offset by the initial position of the + // mouse compared to the gutter size. + offset = + getMousePosition(e) - + this.start + + (this[aGutterSize] - this.dragOffset); + + if (dragInterval > 1) { + offset = Math.round(offset / dragInterval) * dragInterval; + } + + // If within snapOffset of min or max, set offset to min or max. + // snapOffset buffers a.minSize and b.minSize, so logic is opposite for both. + // Include the appropriate gutter sizes to prevent overflows. + if (offset <= a.minSize + snapOffset + this[aGutterSize]) { + offset = a.minSize + this[aGutterSize]; + } else if ( + offset >= + this.size - (b.minSize + snapOffset + this[bGutterSize]) + ) { + offset = this.size - (b.minSize + this[bGutterSize]); + } + + // Actually adjust the size. + adjust.call(this, offset); + + // Call the drag callback continously. Don't do anything too intensive + // in this callback. + getOption(options, 'onDrag', NOOP)(); + } + + // Cache some important sizes when drag starts, so we don't have to do that + // continously: + // + // `size`: The total size of the pair. First + second + first gutter + second gutter. + // `start`: The leading side of the first element. + // + // ------------------------------------------------ + // | aGutterSize -> ||| | + // | ||| | + // | ||| | + // | ||| <- bGutterSize | + // ------------------------------------------------ + // | <- start size -> | + function calculateSizes() { + // Figure out the parent size minus padding. + var a = elements[this.a].element; + var b = elements[this.b].element; + + var aBounds = a[getBoundingClientRect](); + var bBounds = b[getBoundingClientRect](); + + this.size = + aBounds[dimension] + + bBounds[dimension] + + this[aGutterSize] + + this[bGutterSize]; + this.start = aBounds[position]; + this.end = aBounds[positionEnd]; + } + + function innerSize(element) { + // Return nothing if getComputedStyle is not supported (< IE9) + // Or if parent element has no layout yet + if (!getComputedStyle) { return null } + + var computedStyle = getComputedStyle(element); + + if (!computedStyle) { return null } + + var size = element[clientSize]; + + if (size === 0) { return null } + + if (direction === HORIZONTAL) { + size -= + parseFloat(computedStyle.paddingLeft) + + parseFloat(computedStyle.paddingRight); + } else { + size -= + parseFloat(computedStyle.paddingTop) + + parseFloat(computedStyle.paddingBottom); + } + + return size + } + + // When specifying percentage sizes that are less than the computed + // size of the element minus the gutter, the lesser percentages must be increased + // (and decreased from the other elements) to make space for the pixels + // subtracted by the gutters. + function trimToMin(sizesToTrim) { + // Try to get inner size of parent element. + // If it's no supported, return original sizes. + var parentSize = innerSize(parent); + if (parentSize === null) { + return sizesToTrim + } + + if (minSizes.reduce(function (a, b) { return a + b; }, 0) > parentSize) { + return sizesToTrim + } + + // Keep track of the excess pixels, the amount of pixels over the desired percentage + // Also keep track of the elements with pixels to spare, to decrease after if needed + var excessPixels = 0; + var toSpare = []; + + var pixelSizes = sizesToTrim.map(function (size, i) { + // Convert requested percentages to pixel sizes + var pixelSize = (parentSize * size) / 100; + var elementGutterSize = getGutterSize( + gutterSize, + i === 0, + i === sizesToTrim.length - 1, + gutterAlign + ); + var elementMinSize = minSizes[i] + elementGutterSize; + + // If element is too smal, increase excess pixels by the difference + // and mark that it has no pixels to spare + if (pixelSize < elementMinSize) { + excessPixels += elementMinSize - pixelSize; + toSpare.push(0); + return elementMinSize + } + + // Otherwise, mark the pixels it has to spare and return it's original size + toSpare.push(pixelSize - elementMinSize); + return pixelSize + }); + + // If nothing was adjusted, return the original sizes + if (excessPixels === 0) { + return sizesToTrim + } + + return pixelSizes.map(function (pixelSize, i) { + var newPixelSize = pixelSize; + + // While there's still pixels to take, and there's enough pixels to spare, + // take as many as possible up to the total excess pixels + if (excessPixels > 0 && toSpare[i] - excessPixels > 0) { + var takenPixels = Math.min( + excessPixels, + toSpare[i] - excessPixels + ); + + // Subtract the amount taken for the next iteration + excessPixels -= takenPixels; + newPixelSize = pixelSize - takenPixels; + } + + // Return the pixel size adjusted as a percentage + return (newPixelSize / parentSize) * 100 + }) + } + + // stopDragging is very similar to startDragging in reverse. + function stopDragging() { + var self = this; + var a = elements[self.a].element; + var b = elements[self.b].element; + + if (self.dragging) { + getOption(options, 'onDragEnd', NOOP)(getSizes()); + } + + self.dragging = false; + + // Remove the stored event listeners. This is why we store them. + global[removeEventListener]('mouseup', self.stop); + global[removeEventListener]('touchend', self.stop); + global[removeEventListener]('touchcancel', self.stop); + global[removeEventListener]('mousemove', self.move); + global[removeEventListener]('touchmove', self.move); + + // Clear bound function references + self.stop = null; + self.move = null; + + a[removeEventListener]('selectstart', NOOP); + a[removeEventListener]('dragstart', NOOP); + b[removeEventListener]('selectstart', NOOP); + b[removeEventListener]('dragstart', NOOP); + + a.style.userSelect = ''; + a.style.webkitUserSelect = ''; + a.style.MozUserSelect = ''; + a.style.pointerEvents = ''; + + b.style.userSelect = ''; + b.style.webkitUserSelect = ''; + b.style.MozUserSelect = ''; + b.style.pointerEvents = ''; + + self.gutter.style.cursor = ''; + self.parent.style.cursor = ''; + document.body.style.cursor = ''; } - } - - // Set the element size to our determined size. - // Half-size gutters for first and last elements. - if (i === 0 || i === ids.length - 1) { - setElementSize(element.element, element.size, gutterSize / 2); - } else { - setElementSize(element.element, element.size, gutterSize); - } - - var computedSize = element.element[getBoundingClientRect]()[dimension]; - - if (computedSize < element.minSize) { - element.minSize = computedSize; - } - - // After the first iteration, and we have a pair object, append it to the - // list of pairs. - if (i > 0) { - pairs.push(pair); - } - - return element; - }); - - function setSizes(newSizes) { - newSizes.forEach(function(newSize, i) { - if (i > 0) { - var pair = pairs[i - 1]; - var a = elements[pair.a]; - var b = elements[pair.b]; - - a.size = newSizes[i - 1]; - b.size = newSize; - - setElementSize(a.element, a.size, pair.aGutterSize); - setElementSize(b.element, b.size, pair.bGutterSize); + + // startDragging calls `calculateSizes` to store the inital size in the pair object. + // It also adds event listeners for mouse/touch events, + // and prevents selection while dragging so avoid the selecting text. + function startDragging(e) { + // Right-clicking can't start dragging. + if ('button' in e && e.button !== 0) { + return + } + + // Alias frequently used variables to save space. 200 bytes. + var self = this; + var a = elements[self.a].element; + var b = elements[self.b].element; + + // Call the onDragStart callback. + if (!self.dragging) { + getOption(options, 'onDragStart', NOOP)(getSizes()); + } + + // Don't actually drag the element. We emulate that in the drag function. + e.preventDefault(); + + // Set the dragging property of the pair object. + self.dragging = true; + + // Create two event listeners bound to the same pair object and store + // them in the pair object. + self.move = drag.bind(self); + self.stop = stopDragging.bind(self); + + // All the binding. `window` gets the stop events in case we drag out of the elements. + global[addEventListener]('mouseup', self.stop); + global[addEventListener]('touchend', self.stop); + global[addEventListener]('touchcancel', self.stop); + global[addEventListener]('mousemove', self.move); + global[addEventListener]('touchmove', self.move); + + // Disable selection. Disable! + a[addEventListener]('selectstart', NOOP); + a[addEventListener]('dragstart', NOOP); + b[addEventListener]('selectstart', NOOP); + b[addEventListener]('dragstart', NOOP); + + a.style.userSelect = 'none'; + a.style.webkitUserSelect = 'none'; + a.style.MozUserSelect = 'none'; + a.style.pointerEvents = 'none'; + + b.style.userSelect = 'none'; + b.style.webkitUserSelect = 'none'; + b.style.MozUserSelect = 'none'; + b.style.pointerEvents = 'none'; + + // Set the cursor at multiple levels + self.gutter.style.cursor = cursor; + self.parent.style.cursor = cursor; + document.body.style.cursor = cursor; + + // Cache the initial sizes of the pair. + calculateSizes.call(self); + + // Determine the position of the mouse compared to the gutter + self.dragOffset = getMousePosition(e) - self.end; } - }); - } - - function destroy() { - pairs.forEach(function(pair) { - pair.parent.removeChild(pair.gutter); - elements[pair.a].element.style[dimension] = ''; - elements[pair.b].element.style[dimension] = ''; - }); - } - - if (isIE8) { - return { - setSizes: setSizes, - destroy: destroy - }; - } - - return { - setSizes: setSizes, - getSizes: function getSizes() { - return elements.map(function(element) { - return element.size; + + // adjust sizes to ensure percentage is within min size and gutter. + sizes = trimToMin(sizes); + + // 5. Create pair and element objects. Each pair has an index reference to + // elements `a` and `b` of the pair (first and second elements). + // Loop through the elements while pairing them off. Every pair gets a + // `pair` object and a gutter. + // + // Basic logic: + // + // - Starting with the second element `i > 0`, create `pair` objects with + // `a = i - 1` and `b = i` + // - Set gutter sizes based on the _pair_ being first/last. The first and last + // pair have gutterSize / 2, since they only have one half gutter, and not two. + // - Create gutter elements and add event listeners. + // - Set the size of the elements, minus the gutter sizes. + // + // ----------------------------------------------------------------------- + // | i=0 | i=1 | i=2 | i=3 | + // | | | | | + // | pair 0 pair 1 pair 2 | + // | | | | | + // ----------------------------------------------------------------------- + var pairs = []; + elements = ids.map(function (id, i) { + // Create the element object. + var element = { + element: elementOrSelector(id), + size: sizes[i], + minSize: minSizes[i], + i: i, + }; + + var pair; + + if (i > 0) { + // Create the pair object with its metadata. + pair = { + a: i - 1, + b: i, + dragging: false, + direction: direction, + parent: parent, + }; + + pair[aGutterSize] = getGutterSize( + gutterSize, + i - 1 === 0, + false, + gutterAlign + ); + pair[bGutterSize] = getGutterSize( + gutterSize, + false, + i === ids.length - 1, + gutterAlign + ); + + // if the parent has a reverse flex-direction, switch the pair elements. + if ( + parentFlexDirection === 'row-reverse' || + parentFlexDirection === 'column-reverse' + ) { + var temp = pair.a; + pair.a = pair.b; + pair.b = temp; + } + } + + // Determine the size of the current element. IE8 is supported by + // staticly assigning sizes without draggable gutters. Assigns a string + // to `size`. + // + // IE9 and above + if (!isIE8) { + // Create gutter elements for each pair. + if (i > 0) { + var gutterElement = gutter(i, direction, element.element); + setGutterSize(gutterElement, gutterSize, i); + + // Save bound event listener for removal later + pair[gutterStartDragging] = startDragging.bind(pair); + + // Attach bound event listener + gutterElement[addEventListener]( + 'mousedown', + pair[gutterStartDragging] + ); + gutterElement[addEventListener]( + 'touchstart', + pair[gutterStartDragging] + ); + + parent.insertBefore(gutterElement, element.element); + + pair.gutter = gutterElement; + } + } + + setElementSize( + element.element, + element.size, + getGutterSize( + gutterSize, + i === 0, + i === ids.length - 1, + gutterAlign + ), + i + ); + + // After the first iteration, and we have a pair object, append it to the + // list of pairs. + if (i > 0) { + pairs.push(pair); + } + + return element }); - }, - collapse: function collapse(i) { - if (i === pairs.length) { - var pair = pairs[i - 1]; - calculateSizes.call(pair); + function adjustToMin(element) { + var isLast = element.i === pairs.length; + var pair = isLast ? pairs[element.i - 1] : pairs[element.i]; - if (!isIE8) { - adjust.call(pair, pair.size - pair.bGutterSize); - } - } else { - var pair$1 = pairs[i]; + calculateSizes.call(pair); - calculateSizes.call(pair$1); + var size = isLast + ? pair.size - element.minSize - pair[bGutterSize] + : element.minSize + pair[aGutterSize]; - if (!isIE8) { - adjust.call(pair$1, pair$1.aGutterSize); - } + adjust.call(pair, size); + } + + elements.forEach(function (element) { + var computedSize = element.element[getBoundingClientRect]()[dimension]; + + if (computedSize < element.minSize) { + if (expandToMin) { + adjustToMin(element); + } else { + // eslint-disable-next-line no-param-reassign + element.minSize = computedSize; + } + } + }); + + function setSizes(newSizes) { + var trimmed = trimToMin(newSizes); + trimmed.forEach(function (newSize, i) { + if (i > 0) { + var pair = pairs[i - 1]; + + var a = elements[pair.a]; + var b = elements[pair.b]; + + a.size = trimmed[i - 1]; + b.size = newSize; + + setElementSize(a.element, a.size, pair[aGutterSize], a.i); + setElementSize(b.element, b.size, pair[bGutterSize], b.i); + } + }); + } + + function destroy(preserveStyles, preserveGutter) { + pairs.forEach(function (pair) { + if (preserveGutter !== true) { + pair.parent.removeChild(pair.gutter); + } else { + pair.gutter[removeEventListener]( + 'mousedown', + pair[gutterStartDragging] + ); + pair.gutter[removeEventListener]( + 'touchstart', + pair[gutterStartDragging] + ); + } + + if (preserveStyles !== true) { + var style = elementStyle( + dimension, + pair.a.size, + pair[aGutterSize] + ); + + Object.keys(style).forEach(function (prop) { + elements[pair.a].element.style[prop] = ''; + elements[pair.b].element.style[prop] = ''; + }); + } + }); + } + + if (isIE8) { + return { + setSizes: setSizes, + destroy: destroy, + } + } + + return { + setSizes: setSizes, + getSizes: getSizes, + collapse: function collapse(i) { + adjustToMin(elements[i]); + }, + destroy: destroy, + parent: parent, + pairs: pairs, } - }, - destroy: destroy }; - }; - return Split; -}); + return Split; + +}))); diff --git a/docs/assets/style.css b/docs/assets/style.css index 5265ea1f..0618f437 100644 --- a/docs/assets/style.css +++ b/docs/assets/style.css @@ -26,7 +26,7 @@ h4 { } a { - color: #1184CE; + color: #1184ce; text-decoration: none; } @@ -51,12 +51,12 @@ a:hover { } section:target h3 { - font-weight:700; + font-weight: 700; } .documentation td, .documentation th { - padding: .25rem .25rem; + padding: 0.25rem 0.25rem; } h1:hover .anchorjs-link, @@ -82,13 +82,16 @@ h4:hover .anchorjs-link { } } -.pre, pre, code, .code { - font-family: Source Code Pro,Menlo,Consolas,Liberation Mono,monospace; +.pre, +pre, +code, +.code { + font-family: Source Code Pro, Menlo, Consolas, Liberation Mono, monospace; font-size: 14px; } .fill-light { - background: #F9F9F9; + background: #f9f9f9; } .width2 { @@ -100,10 +103,10 @@ h4:hover .anchorjs-link { display: block; width: 100%; height: 2rem; - padding: .5rem; + padding: 0.5rem; margin-bottom: 1rem; border: 1px solid #ccc; - font-size: .875rem; + font-size: 0.875rem; border-radius: 3px; box-sizing: border-box; } @@ -115,15 +118,19 @@ table { .prose table th, .prose table td { text-align: left; - padding:8px; - border:1px solid #ddd; + padding: 8px; + border: 1px solid #ddd; } -.prose table th:nth-child(1) { border-right: none; } -.prose table th:nth-child(2) { border-left: none; } +.prose table th:nth-child(1) { + border-right: none; +} +.prose table th:nth-child(2) { + border-left: none; +} .prose table { - border:1px solid #ddd; + border: 1px solid #ddd; } .prose-big { diff --git a/docs/index.html b/docs/index.html index 5be1c664..f2de69ae 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,14 +1,14 @@
- +Get the hystrixStats.
- -- Type: - HystrixStats -
- - - - - - - - - - - - - - - - - - - - - - - - - - - -Get the prometheus metrics for this circuit
- -- Type: - PrometheusMetrics -
- - - - - - - - - - - - - - - - - - - - @@ -2873,6 +2634,54 @@Emitted when the circuit breaker has been shut down.
+ +Stream Hystrix Metrics for a given CircuitBreaker. -A HystrixStats instance is created for every CircuitBreaker -and does not typically need to be created by a user.
-A HystrixStats instance will listen for all events on the -Status#snapshot -and format the data to the proper Hystrix format. -Making it easy to construct an Event Stream for a client
- -(CircuitBreaker)
- circuit breaker
-
- const circuit = circuitBreaker(fs.readFile, {});
-
-circuit.hystrixStats.getHystrixStream().pipe(response);
-
-
-
-
-
-
- A convenience function that returns the hystrixStream
- -ReadableStream
:
- the statistics stream
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Shuts down this instance, freeing memory. -When a circuit is shutdown, it should call shutdown() on -its HystrixStats instance to avoid memory leaks.
- -void
:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Iterator
of all available circuits
**Extends EventEmitter**
-Constructs a [CircuitBreaker][5].
+Constructs a [CircuitBreaker][4].
### Parameters
-- `action` **[Function][57]** The action to fire for this [CircuitBreaker][5]
-- `options` **[Object][58]** Options for the [CircuitBreaker][5]
- - `options.timeout` **[Number][59]** The time in milliseconds that action should
+- `action` **[Function][50]** The action to fire for this [CircuitBreaker][4]
+- `options` **[Object][51]** Options for the [CircuitBreaker][4]
+ - `options.timeout` **[Number][52]** The time in milliseconds that action should
be allowed to execute before timing out. Default 10000 (10 seconds)
- - `options.maxFailures` **[Number][59]** (Deprecated) The number of times the
+ - `options.maxFailures` **[Number][52]** (Deprecated) The number of times the
circuit can fail before opening. Default 10.
- - `options.resetTimeout` **[Number][59]** The time in milliseconds to wait before
+ - `options.resetTimeout` **[Number][52]** The time in milliseconds to wait before
setting the breaker to `halfOpen` state, and trying the action again.
Default: 30000 (30 seconds)
- - `options.rollingCountTimeout` **[Number][59]** Sets the duration of the
+ - `options.rollingCountTimeout` **[Number][52]** Sets the duration of the
statistical rolling window, in milliseconds. This is how long Opossum keeps
metrics for the circuit breaker to use and for publishing. Default: 10000
- - `options.rollingCountBuckets` **[Number][59]** Sets the number of buckets the
+ - `options.rollingCountBuckets` **[Number][52]** Sets the number of buckets the
rolling statistical window is divided into. So, if
options.rollingCountTimeout is 10000, and options.rollingCountBuckets is 10,
then the statistical window will be 1000 1 second snapshots in the
statistical window. Default: 10
- - `options.name` **[String][60]** the circuit name to use when reporting stats.
+ - `options.name` **[String][53]** the circuit name to use when reporting stats.
Default: the name of the function this circuit controls.
- - `options.rollingPercentilesEnabled` **[boolean][61]** This property indicates
+ - `options.rollingPercentilesEnabled` **[boolean][54]** 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. Default: false
- - `options.capacity` **[Number][59]** the number of concurrent requests allowed.
+ - `options.capacity` **[Number][52]** the number of concurrent requests allowed.
If the number currently executing function calls is equal to
options.capacity, further calls to `fire()` are rejected until at least one
of the current requests completes. Default: `Number.MAX_SAFE_INTEGER`.
- - `options.errorThresholdPercentage` **[Number][59]** the error percentage at
+ - `options.errorThresholdPercentage` **[Number][52]** the error percentage at
which to open the circuit and start short-circuiting requests to fallback.
Default: 50
- - `options.enabled` **[boolean][61]** whether this circuit is enabled upon
+ - `options.enabled` **[boolean][54]** whether this circuit is enabled upon
construction. Default: true
- - `options.allowWarmUp` **[boolean][61]** determines whether to allow failures
+ - `options.allowWarmUp` **[boolean][54]** determines whether to allow failures
without opening the circuit during a brief warmup period (this is the
`rollingCountDuration` property). Default: false
allow before enabling the circuit. This can help in situations where no
matter what your `errorThresholdPercentage` is, if the first execution
times out or fails, the circuit immediately opens. Default: 0
- - `options.volumeThreshold` **[Number][59]** the minimum number of requests within
+ - `options.volumeThreshold` **[Number][52]** the minimum number of requests within
the rolling statistical window that must exist before the circuit breaker
can open. This is similar to `options.allowWarmUp` in that no matter how many
failures there are, if the number of requests within the statistical window
does not exceed this threshold, the circuit will remain closed. Default: 0
- - `options.errorFilter` **[Function][57]** an optional function that will be
+ - `options.errorFilter` **[Function][50]** an optional function that will be
called when the circuit's function fails (returns a rejected Promise). If
- this function returns truthy, the circuit's failure statistics will not be
+ this function returns truthy, the circuit's failPure statistics will not be
incremented. This is useful, for example, when you don't want HTTP 404 to
trip the circuit, but still want to handle it as a failure case.
@@ -212,49 +198,49 @@ Returns **void**
Determines if the circuit has been shutdown.
-Type: [Boolean][61]
+Type: [Boolean][54]
### name
Gets the name of this circuit
-Type: [String][60]
+Type: [String][53]
### group
Gets the name of this circuit group
-Type: [String][60]
+Type: [String][53]
### pendingClose
Gets whether this cicruit is in the `pendingClosed` state
-Type: [Boolean][61]
+Type: [Boolean][54]
### closed
True if the circuit is currently closed. False otherwise.
-Type: [Boolean][61]
+Type: [Boolean][54]
### opened
True if the circuit is currently opened. False otherwise.
-Type: [Boolean][61]
+Type: [Boolean][54]
### halfOpen
True if the circuit is currently half opened. False otherwise.
-Type: [Boolean][61]
+Type: [Boolean][54]
### status
-The current [Status][17] of this [CircuitBreaker][5]
+The current [Status][16] of this [CircuitBreaker][4]
-Type: [Status][63]
+Type: [Status][56]
### stats
@@ -262,51 +248,39 @@ Type: [Status][63]
Get the current stats for the circuit.
-Type: [Object][58]
-
-### hystrixStats
-
-Get the hystrixStats.
-
-Type: [HystrixStats][64]
-
-### metrics
-
-Get the prometheus metrics for this circuit
-
-Type: PrometheusMetrics
+Type: [Object][51]
### enabled
Gets whether the circuit is enabled or not
-Type: [Boolean][61]
+Type: [Boolean][54]
### warmUp
Gets whether the circuit is currently in warm up phase
-Type: [Boolean][61]
+Type: [Boolean][54]
### volumeThreshold
Gets the volume threshold for this circuit
-Type: [Boolean][61]
+Type: [Boolean][54]
### fallback
-Provide a fallback function for this [CircuitBreaker][5]. This
+Provide a fallback function for this [CircuitBreaker][4]. This
function will be executed when the circuit is `fire`d and fails.
It will always be preceded by a `failure` event, and `breaker.fire` returns
a rejected Promise.
#### Parameters
-- `func` **([Function][57] \| [CircuitBreaker][62])** the fallback function to execute
+- `func` **([Function][50] \| [CircuitBreaker][55])** the fallback function to execute
when the breaker has opened or when a timeout or error occurs.
-Returns **[CircuitBreaker][62]** this
+Returns **[CircuitBreaker][55]** this
### fire
@@ -315,12 +289,12 @@ returned promise will be rejected. If the action succeeds, the promise will
resolve with the resolved value from action. If a fallback function was
provided, it will be invoked in the event of any failure or timeout.
-Returns **[Promise][65]<any>** promise resolves with the circuit function's return
+Returns **[Promise][57]<any>** promise resolves with the circuit function's return
value on success or is rejected on failure of the action.
### clearCache
-Clears the cache of this [CircuitBreaker][5]
+Clears the cache of this [CircuitBreaker][4]
Returns **void**
@@ -338,12 +312,12 @@ circuit breaker itself.
#### Parameters
-- `func` **[Function][57]** a health check function which returns a promise.
-- `interval` **[Number][59]?** the amount of time between calls to the health
+- `func` **[Function][50]** a health check function which returns a promise.
+- `interval` **[Number][52]?** the amount of time between calls to the health
check function. Default: 5000 (5 seconds)
-- Throws **[TypeError][66]** if `interval` is supplied but not a number
+- Throws **[TypeError][58]** if `interval` is supplied but not a number
Returns **void**
@@ -377,7 +351,7 @@ Emitted after `options.resetTimeout` has elapsed, allowing for
a single attempt to call the service again. If that attempt is
successful, the circuit will be closed. Otherwise it remains open.
-Type: [Number][59]
+Type: [Number][52]
## CircuitBreaker#close
@@ -388,6 +362,10 @@ Emitted when the breaker is reset allowing the action to execute again
Emitted when the breaker opens because the action has
failed more than `options.maxFailures` number of times.
+## CircuitBreaker#shutdown
+
+Emitted when the circuit breaker has been shut down.
+
## CircuitBreaker#fire
Emitted when the circuit breaker action is executed
@@ -408,14 +386,14 @@ the cache, but the cache option is enabled.
Emitted when the circuit breaker is open and failing fast
-Type: [Error][67]
+Type: [Error][59]
## CircuitBreaker#timeout
Emitted when the circuit breaker action takes longer than
`options.timeout`
-Type: [Error][67]
+Type: [Error][59]
## CircuitBreaker#success
@@ -428,14 +406,14 @@ Type: any
Emitted when the rate limit has been reached and there
are no more locks to be obtained.
-Type: [Error][67]
+Type: [Error][59]
## CircuitBreaker#healthCheckFailed
Emitted with the user-supplied health check function
returns a rejected promise.
-Type: [Error][67]
+Type: [Error][59]
## CircuitBreaker#fallback
@@ -447,7 +425,7 @@ Type: any
Emitted when the circuit breaker action fails
-Type: [Error][67]
+Type: [Error][59]
## Status
@@ -455,27 +433,27 @@ Type: [Error][67]
- **See: CircuitBreaker#status**
-Tracks execution status for a given [CircuitBreaker][5].
-A Status instance is created for every [CircuitBreaker][5]
+Tracks execution status for a given [CircuitBreaker][4].
+A Status instance is created for every [CircuitBreaker][4]
and does not typically need to be created by a user.
-A Status instance will listen for all events on the [CircuitBreaker][5]
+A Status instance will listen for all events on the [CircuitBreaker][4]
and track them in a rolling statistical window. The window duration is
determined by the `rollingCountTimeout` option provided to the
-[CircuitBreaker][5]. The window consists of an array of Objects,
-each representing the counts for a [CircuitBreaker][5]'s events.
+[CircuitBreaker][4]. The window consists of an array of Objects,
+each representing the counts for a [CircuitBreaker][4]'s events.
-The array's length is determined by the [CircuitBreaker][5]'s
+The array's length is determined by the [CircuitBreaker][4]'s
`rollingCountBuckets` option. The duration of each slice of the window
is determined by dividing the `rollingCountTimeout` by
`rollingCountBuckets`.
### Parameters
-- `options` **[Object][58]** for the status window
- - `options.rollingCountBuckets` **[Number][59]** number of buckets in the window
- - `options.rollingCountTimeout` **[Number][59]** the duration of the window
- - `options.rollingPercentilesEnabled` **[Boolean][61]** whether to calculate
+- `options` **[Object][51]** for the status window
+ - `options.rollingCountBuckets` **[Number][52]** number of buckets in the window
+ - `options.rollingCountTimeout` **[Number][52]** the duration of the window
+ - `options.rollingPercentilesEnabled` **[Boolean][54]** whether to calculate
percentiles
### Examples
@@ -497,194 +475,137 @@ circuit.status.window;
Get the cumulative stats for the current window
-Type: [Object][58]
+Type: [Object][51]
### window
Gets the stats window as an array of time-sliced objects.
-Type: [Array][68]
+Type: [Array][60]
## Status#snapshot
Emitted at each time-slice. Listeners for this
event will receive a cumulative snapshot of the current status window.
-Type: [Object][58]
-
-## HystrixStats
-
-- **See: CircuitBreaker#hystrixStats**
-
-Stream Hystrix Metrics for a given [CircuitBreaker][5].
-A HystrixStats instance is created for every [CircuitBreaker][5]
-and does not typically need to be created by a user.
-
-A HystrixStats instance will listen for all events on the
-[Status#snapshot][69]
-and format the data to the proper Hystrix format.
-Making it easy to construct an Event Stream for a client
-
-### Parameters
-
-- `the` **[CircuitBreaker][62]** circuit breaker
-
-### Examples
-
-```javascript
-const circuit = circuitBreaker(fs.readFile, {});
-
-circuit.hystrixStats.getHystrixStream().pipe(response);
-```
-
-### getHystrixStream
-
-A convenience function that returns the hystrixStream
-
-Returns **ReadableStream** the statistics stream
-
-### shutdown
-
-Shuts down this instance, freeing memory.
-When a circuit is shutdown, it should call shutdown() on
-its HystrixStats instance to avoid memory leaks.
-
-Returns **void**
+Type: [Object][51]
[1]: #factory
[2]: #parameters
-[3]: #factorymetrics
-
-[4]: #factorycircuits
-
-[5]: #circuitbreaker
-
-[6]: #parameters-1
-
-[7]: #close
-
-[8]: #open
-
-[9]: #shutdown
-
-[10]: #isshutdown
-
-[11]: #name
-
-[12]: #group
+[3]: #factorycircuits
-[13]: #pendingclose
+[4]: #circuitbreaker
-[14]: #closed
+[5]: #parameters-1
-[15]: #opened
+[6]: #close
-[16]: #halfopen
+[7]: #open
-[17]: #status
+[8]: #shutdown
-[18]: #stats
+[9]: #isshutdown
-[19]: #hystrixstats
+[10]: #name
-[20]: #metrics
+[11]: #group
-[21]: #enabled
+[12]: #pendingclose
-[22]: #warmup
+[13]: #closed
-[23]: #volumethreshold
+[14]: #opened
-[24]: #fallback
+[15]: #halfopen
-[25]: #parameters-2
+[16]: #status
-[26]: #fire
+[17]: #stats
-[27]: #clearcache
+[18]: #enabled
-[28]: #healthcheck
+[19]: #warmup
-[29]: #parameters-3
+[20]: #volumethreshold
-[30]: #enable
+[21]: #fallback
-[31]: #disable
+[22]: #parameters-2
-[32]: #circuits
+[23]: #fire
-[33]: #circuitbreakerhalfopen
+[24]: #clearcache
-[34]: #circuitbreakerclose
+[25]: #healthcheck
-[35]: #circuitbreakeropen
+[26]: #parameters-3
-[36]: #circuitbreakerfire
+[27]: #enable
-[37]: #circuitbreakercachehit
+[28]: #disable
-[38]: #circuitbreakercachemiss
+[29]: #circuits
-[39]: #circuitbreakerreject
+[30]: #circuitbreakerhalfopen
-[40]: #circuitbreakertimeout
+[31]: #circuitbreakerclose
-[41]: #circuitbreakersuccess
+[32]: #circuitbreakeropen
-[42]: #circuitbreakersemaphorelocked
+[33]: #circuitbreakershutdown
-[43]: #circuitbreakerhealthcheckfailed
+[34]: #circuitbreakerfire
-[44]: #circuitbreakerfallback
+[35]: #circuitbreakercachehit
-[45]: #circuitbreakerfailure
+[36]: #circuitbreakercachemiss
-[46]: #status-1
+[37]: #circuitbreakerreject
-[47]: #parameters-4
+[38]: #circuitbreakertimeout
-[48]: #examples
+[39]: #circuitbreakersuccess
-[49]: #stats-1
+[40]: #circuitbreakersemaphorelocked
-[50]: #window
+[41]: #circuitbreakerhealthcheckfailed
-[51]: #statussnapshot
+[42]: #circuitbreakerfallback
-[52]: #hystrixstats-1
+[43]: #circuitbreakerfailure
-[53]: #parameters-5
+[44]: #status-1
-[54]: #examples-1
+[45]: #parameters-4
-[55]: #gethystrixstream
+[46]: #examples
-[56]: #shutdown-1
+[47]: #stats-1
-[57]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
+[48]: #window
-[58]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
+[49]: #statussnapshot
-[59]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
+[50]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
-[60]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
+[51]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
-[61]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
+[52]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
-[62]: #circuitbreaker
+[53]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
-[63]: #status
+[54]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
-[64]: #hystrixstats
+[55]: #circuitbreaker
-[65]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
+[56]: #status
-[66]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/TypeError
+[57]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
-[67]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
+[58]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/TypeError
-[68]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
+[59]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
-[69]: Status#snapshot
+[60]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
diff --git a/index.js b/index.js
index a055ce95..cbce0884 100644
--- a/index.js
+++ b/index.js
@@ -1,7 +1,6 @@
'use strict';
const CircuitBreaker = require('./lib/circuit');
-let lastCircuit;
const defaults = {
timeout: 10000, // 10 seconds
@@ -60,39 +59,13 @@ const defaults = {
* this function returns truthy, the circuit's failure statistics will not be
* incremented. This is useful, for example, when you don't want HTTP 404 to
* trip the circuit, but still want to handle it as a failure case.
-
* @return {CircuitBreaker} a newly created {@link CircuitBreaker} instance
*/
-function factory (action, options) {
- lastCircuit = new CircuitBreaker(action,
+function factory(action, options) {
+ return new CircuitBreaker(action,
Object.assign({}, defaults, options));
- return lastCircuit;
}
-/**
- * Get the Prometheus metrics for all circuits.
- * @function factory.metrics
- * @return {String} the metrics for all circuits or
- * undefined if no circuits have been created
- */
-factory.metrics = function metrics() {
- // Just get the metrics for the last circuit that was created
- // since prom-client is additive
- if (lastCircuit && lastCircuit.metrics) return lastCircuit.metrics.metrics;
-}
-
-let warningIssued = false;
-Object.defineProperty(factory, 'stats', {
- get: _ => {
- if (!warningIssued) {
- warningIssued = true;
- console.warn(`WARNING: Hystrics stats are deprecated
- See: https://github.com/Netflix/Hystrix#dashboard`)
- }
- return require('./lib/hystrix-stats').stream;
- }
-});
-
/**
* Get an Iterator
object containing all
* circuits that have been created but not subsequently shut down.
@@ -100,7 +73,7 @@ Object.defineProperty(factory, 'stats', {
* @return {Iterator} an Iterator
of all available circuits
*/
factory.circuits = CircuitBreaker.circuits;
-
+
module.exports = exports = factory;
// Allow use of default import syntax in TypeScript
module.exports.default = factory;
diff --git a/lib/circuit.js b/lib/circuit.js
index 0b84e5e5..1f712fb3 100644
--- a/lib/circuit.js
+++ b/lib/circuit.js
@@ -2,12 +2,7 @@
const EventEmitter = require('events');
const Status = require('./status');
-const HystrixStats = require('./hystrix-stats');
const Semaphore = require('./semaphore');
-let PrometheusMetrics;
-if (!process.env.WEB) {
- PrometheusMetrics = require('./prometheus-metrics');
-}
const STATE = Symbol('state');
const OPEN = Symbol('open');
@@ -19,8 +14,6 @@ const FALLBACK_FUNCTION = Symbol('fallback');
const STATUS = Symbol('status');
const NAME = Symbol('name');
const GROUP = Symbol('group');
-const HYSTRIX_STATS = Symbol('hystrix-stats');
-const PROMETHEUS_METRICS = Symbol('prometheus-metrics');
const CACHE = new WeakMap();
const ENABLED = Symbol('Enabled');
const WARMING_UP = Symbol('warming-up');
@@ -29,9 +22,6 @@ const deprecation = `options.maxFailures is deprecated. \
Please use options.errorThresholdPercentage`;
const CIRCUITS = new Set();
-let warningIssued = false;
-
-
/**
* Constructs a {@link CircuitBreaker}.
*
@@ -82,7 +72,7 @@ let warningIssued = false;
* does not exceed this threshold, the circuit will remain closed. Default: 0
* @param {Function} options.errorFilter an optional function that will be
* called when the circuit's function fails (returns a rejected Promise). If
- * this function returns truthy, the circuit's failure statistics will not be
+ * this function returns truthy, the circuit's failPure statistics will not be
* incremented. This is useful, for example, when you don't want HTTP 404 to
* trip the circuit, but still want to handle it as a failure case.
*
@@ -182,16 +172,6 @@ class CircuitBreaker extends EventEmitter {
CACHE.set(this, undefined);
}
- // Register with the hystrix stats listener
- this[HYSTRIX_STATS] = new HystrixStats(this);
-
- // Add Prometheus metrics if not running in a web env
- if (PrometheusMetrics && options.usePrometheus) {
- this[PROMETHEUS_METRICS] = new PrometheusMetrics(
- this,
- options.prometheusRegistry
- );
- }
CIRCUITS.add(this);
}
@@ -241,10 +221,13 @@ class CircuitBreaker extends EventEmitter {
this.disable();
this.removeAllListeners();
this.status.shutdown();
- this.hystrixStats.shutdown();
- this.metrics && this.metrics.clear();
this[STATE] = SHUTDOWN;
CIRCUITS.delete(this);
+ /**
+ * Emitted when the circuit breaker has been shut down.
+ * @event CircuitBreaker#shutdown
+ */
+ this.emit('shutdown');
}
/**
@@ -320,27 +303,6 @@ class CircuitBreaker extends EventEmitter {
return this[STATUS].stats;
}
- /**
- * Get the hystrixStats.
- * @type {HystrixStats}
- */
- get hystrixStats () {
- if (!warningIssued) {
- warningIssued = true;
- console.warn(`WARNING: Hystrics stats are deprecated
- See: https://github.com/Netflix/Hystrix#dashboard`)
- }
- return this[HYSTRIX_STATS];
- }
-
- /**
- * Get the prometheus metrics for this circuit
- * @type {PrometheusMetrics}
- */
- get metrics () {
- return this[PROMETHEUS_METRICS];
- }
-
/**
* Gets whether the circuit is enabled or not
* @type {Boolean}
diff --git a/lib/hystrix-formatter.js b/lib/hystrix-formatter.js
deleted file mode 100644
index 29bec29a..00000000
--- a/lib/hystrix-formatter.js
+++ /dev/null
@@ -1,82 +0,0 @@
-'use strict';
-
-/* eslint max-len: ["error", { "ignoreUrls": true }] */
-
-// Data reference:
-// https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring#metrics-publisher
-// A function to map our stats data to the hystrix format
-// returns JSON
-function hystrixFormatter (stats) {
- return {
- type: 'HystrixCommand',
- name: stats.name,
- group: stats.group,
- currentTime: Date.now(),
- isCircuitBreakerOpen: !stats.closed,
- errorPercentage:
- stats.fires === 0 ? 0 : (stats.failures / stats.fires) * 100,
- errorCount: stats.failures,
- requestCount: stats.fires,
- rollingCountBadRequests: stats.failures,
- rollingCountCollapsedRequests: 0,
- rollingCountEmit: stats.fires,
- rollingCountExceptionsThrown: 0,
- rollingCountFailure: stats.failures,
- rollingCountFallbackEmit: stats.fallbacks,
- rollingCountFallbackFailure: 0,
- rollingCountFallbackMissing: 0,
- rollingCountFallbackRejection: 0,
- rollingCountFallbackSuccess: 0,
- rollingCountResponsesFromCache: stats.cacheHits,
- rollingCountSemaphoreRejected: stats.semaphoreRejections,
- rollingCountShortCircuited: stats.rejects,
- rollingCountSuccess: stats.successes,
- rollingCountThreadPoolRejected: 0,
- rollingCountTimeout: stats.timeouts,
- currentConcurrentExecutionCount: 0,
- rollingMaxConcurrentExecutionCount: 0,
- // TODO: calculate these latency values
- latencyExecute_mean: stats.latencyMean || 0,
- latencyExecute: percentiles(stats),
- // Whats the difference between execute and total?
- latencyTotal_mean: stats.latencyMean,
- latencyTotal: percentiles(stats),
- propertyValue_circuitBreakerRequestVolumeThreshold: 5,
- propertyValue_circuitBreakerSleepWindowInMilliseconds:
- stats.options.resetTimeout,
- propertyValue_circuitBreakerErrorThresholdPercentage:
- stats.options.errorThresholdPercentage,
- propertyValue_circuitBreakerForceOpen: false,
- propertyValue_circuitBreakerForceClosed: false,
- propertyValue_circuitBreakerEnabled: true,
- propertyValue_executionIsolationStrategy: 'THREAD',
- propertyValue_executionIsolationThreadTimeoutInMilliseconds: 300,
- propertyValue_executionTimeoutInMilliseconds: stats.options.timeout,
- propertyValue_executionIsolationThreadInterruptOnTimeout: true,
- propertyValue_executionIsolationThreadPoolKeyOverride: null,
- propertyValue_executionIsolationSemaphoreMaxConcurrentRequests:
- stats.options.capacity,
- propertyValue_fallbackIsolationSemaphoreMaxConcurrentRequests:
- stats.options.capacity,
- propertyValue_metricsRollingStatisticalWindowInMilliseconds: 10000,
- propertyValue_requestCacheEnabled: stats.options.cache || false,
- propertyValue_requestLogEnabled: true,
- reportingHosts: 1
- };
-}
-
-function percentiles (stats) {
- return {
- 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']
- };
-}
-
-module.exports = exports = hystrixFormatter;
diff --git a/lib/hystrix-stats.js b/lib/hystrix-stats.js
deleted file mode 100644
index 09522e7c..00000000
--- a/lib/hystrix-stats.js
+++ /dev/null
@@ -1,79 +0,0 @@
-'use strict';
-
-const { Transform, Readable } = require('stream');
-const formatter = require('./hystrix-formatter');
-
-// use a single hystrix stream for all circuits
-const hystrixStream = new Transform({
- objectMode: true,
- transform (stats, encoding, cb) {
- return cb(null, `data: ${JSON.stringify(formatter(stats))}\n\n`);
- }
-});
-
-hystrixStream.resume();
-
-/**
- * Stream Hystrix Metrics for a given {@link CircuitBreaker}.
- * A HystrixStats instance is created for every {@link CircuitBreaker}
- * and does not typically need to be created by a user.
- *
- * A HystrixStats instance will listen for all events on the
- * {@link Status#snapshot}
- * and format the data to the proper Hystrix format.
- * Making it easy to construct an Event Stream for a client
- *
- * @class HystrixStats
- * @example
- * const circuit = circuitBreaker(fs.readFile, {});
- *
- * circuit.hystrixStats.getHystrixStream().pipe(response);
- * @param {CircuitBreaker} the circuit breaker
- * @see CircuitBreaker#hystrixStats
- */
-class HystrixStats {
- constructor (circuit) {
- this._readableStream = new Readable({
- objectMode: true,
- read () {}
- });
-
- // Listen for the stats's snapshot event
- circuit.status.on('snapshot', function snapshotListener (stats) {
- // when we get a snapshot push it onto the stream
- this._readableStream.push(
- Object.assign({},
- {
- name: circuit.name,
- closed: circuit.closed,
- group: circuit.group,
- options: circuit.options
- }, stats));
- }.bind(this));
-
- this._readableStream.resume();
- this._readableStream.pipe(hystrixStream);
- }
-
- /**
- A convenience function that returns the hystrixStream
- @returns {ReadableStream} the statistics stream
- */
- getHystrixStream () {
- return hystrixStream;
- }
-
- /**
- * Shuts down this instance, freeing memory.
- * When a circuit is shutdown, it should call shutdown() on
- * its HystrixStats instance to avoid memory leaks.
- * @returns {void}
- */
- shutdown () {
- this._readableStream.unpipe(hystrixStream);
- }
-}
-
-HystrixStats.stream = hystrixStream;
-
-module.exports = exports = HystrixStats;
diff --git a/lib/prometheus-metrics.js b/lib/prometheus-metrics.js
deleted file mode 100644
index 6b8f94aa..00000000
--- a/lib/prometheus-metrics.js
+++ /dev/null
@@ -1,56 +0,0 @@
-'use strict';
-
-const client = require('prom-client');
-
-// The current tests has circuit names like:
-// 'circuit one' (with blank space) and others like
-// 3beb8f49-62c0-46e0-b458-dcd4a62d0f48.
-// So to avoid "Error: Invalid metric name" we are changing the
-// circuit name to pass the tests.
-// More details:
-// https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels
-function normalizePrefix(prefixName) {
- return `circuit_${prefixName.replace(/[ |-]/g, '_')}_`;
-}
-
-class PrometheusMetrics {
- constructor (circuit, registry) {
- this.circuit = circuit;
- this._registry = registry || client.register
- this._client = client;
- this.counters = [];
- const prefix = normalizePrefix(this.circuit.name);
-
- if (!registry) {
- this.interval = this._client
- .collectDefaultMetrics({ prefix, timeout: 5000 });
- }
-
- for (let eventName of this.circuit.eventNames()) {
- const counter = new this._client.Counter({
- name: `${prefix}${eventName}`,
- help: `A count of the ${circuit.name} circuit's ${eventName} event`,
- registers: [this._registry]
- });
- this.circuit.on(eventName, _ => {
- counter.inc();
- });
- this.counters.push(counter);
- }
- }
-
- clear () {
- clearInterval(this.interval);
- this._registry.clear();
- }
-
- get metrics () {
- return this._registry.metrics();
- }
-
- get client () {
- return this._client;
- }
-}
-
-module.exports = PrometheusMetrics;
diff --git a/test/browser/generate-index.sh b/test/browser/generate-index.sh
index 1493e6b0..b184238c 100755
--- a/test/browser/generate-index.sh
+++ b/test/browser/generate-index.sh
@@ -3,7 +3,7 @@
echo $PWD
cd test
-file_list=$(ls -1 --ignore="*prometheus*" | grep .js)
+file_list=$(ls -1 | grep .js)
cd ..
requires=""
diff --git a/test/browser/index.js b/test/browser/index.js
index 75281557..b3d27e19 100644
--- a/test/browser/index.js
+++ b/test/browser/index.js
@@ -6,7 +6,6 @@ require('../enable-disable-test.js');
require('../error-filter-test.js');
require('../half-open-test.js');
require('../health-check-test.js');
-require('../hystrix-test.js');
require('../semaphore-test.js');
require('../test.js');
require('../volume-threshold-test.js');
diff --git a/test/hystrix-test.js b/test/hystrix-test.js
deleted file mode 100644
index 7587e0bc..00000000
--- a/test/hystrix-test.js
+++ /dev/null
@@ -1,38 +0,0 @@
-'use strict';
-
-const test = require('tape');
-const cb = require('../');
-const { passFail } = require('./common');
-
-test('A circuit stats should be available on the creator function', t => {
- t.plan(1);
- t.ok(cb.stats);
- t.end();
-});
-
-test('A circuit should provide stats to a hystrix compatible stream', t => {
- t.plan(2);
- const circuitOne = cb(passFail, {
- rollingCountTimeout: 100,
- rollingCountBuckets: 1,
- name: 'circuit one'
- });
- const circuitTwo = cb(passFail, {
- rollingCountTimeout: 100,
- rollingCountBuckets: 1,
- name: 'circuit two'
- });
- const stream = circuitOne.hystrixStats.getHystrixStream();
- let circuitOneStatsSeen = false;
- let circuitTwoStatsSeen = false;
- stream.on('data', blob => {
- const obj = JSON.parse(blob.substring(6));
- if (obj.name === 'circuit one') circuitOneStatsSeen = true;
- else if (obj.name === 'circuit two') circuitTwoStatsSeen = true;
- });
- circuitOne.fire(10).then(_ => circuitTwo.fire(10)).then(_ => {
- t.ok(circuitOneStatsSeen, 'circuit one stats seen');
- t.ok(circuitTwoStatsSeen, 'circuit two stats seen');
- t.end();
- });
-});
diff --git a/test/prometheus-test.js b/test/prometheus-test.js
deleted file mode 100644
index b1c87943..00000000
--- a/test/prometheus-test.js
+++ /dev/null
@@ -1,165 +0,0 @@
-'use strict';
-
-const test = require('tape');
-const circuitBreaker = require('../');
-const { passFail } = require('./common');
-const client = require('prom-client');
-
-const { Registry } = client;
-
-test('Factory metrics func does not fail if no circuits yet', t => {
- t.plan(1);
- t.equal(circuitBreaker.metrics(), undefined);
- t.end();
-});
-
-test('A circuit provides prometheus metrics when not in a web env', t => {
- t.plan(1);
- const circuit = circuitBreaker(passFail, {usePrometheus: true});
- t.ok(process.env.WEB ? circuit.metrics : !!circuit.metrics);
- circuit.metrics.clear();
- t.end();
-});
-
-test('Does not load Prometheus when the option is not provided', t => {
- t.plan(1);
- const circuit = circuitBreaker(passFail);
- t.ok(!circuit.metrics);
- circuit.shutdown();
- t.end();
-});
-
-test('The factory function provides access to metrics for all circuits', t => {
- t.plan(4);
- const c1 = circuitBreaker(passFail, { usePrometheus: true, name: 'fred' });
- const c2 = circuitBreaker(passFail, { usePrometheus: true, name: 'bob' });
- t.equal(c1.name, 'fred');
- t.equal(c2.name, 'bob');
- t.ok(/circuit_fred_/.test(circuitBreaker.metrics()));
- t.ok(/circuit_bob_/.test(circuitBreaker.metrics()));
- t.end();
-});
-
-test('The factory function uses a custom prom-client registry', t => {
- t.plan(4);
- const registry = new Registry();
- const c1 = circuitBreaker(passFail, {
- usePrometheus: true,
- name: 'fred',
- prometheusRegistry: registry
- });
- const c2 = circuitBreaker(passFail, {
- usePrometheus: true,
- name: 'bob',
- prometheusRegistry: registry
- });
- t.equal(c1.name, 'fred');
- t.equal(c2.name, 'bob');
- t.ok(/circuit_fred_/.test(registry.metrics()));
- t.ok(/circuit_bob_/.test(registry.metrics()));
- t.end();
-});
-
-// All of the additional tests only make sense when running in a Node.js context
-if (!process.env.WEB) {
- test('Circuit fire/success/failure are counted', t => {
- const circuit = circuitBreaker(passFail, {usePrometheus: true});
- const fire = /circuit_passFail_fire 2/;
- const success = /circuit_passFail_success 1/;
- const failure = /circuit_passFail_failure 1/;
- t.plan(3);
- circuit.fire(1)
- .then(_ => circuit.fire(-1))
- .catch(_ => {
- const metrics = circuit.metrics.metrics;
- process.stdout.write(metrics);
- t.ok(fire.test(metrics), fire);
- t.ok(success.test(metrics), success);
- t.ok(failure.test(metrics), failure);
- circuit.metrics.clear();
- t.end();
- });
- });
-
- test('Metrics are enabled for all circuit events', t => {
- const circuit = circuitBreaker(passFail, {usePrometheus: true});
- const metrics = circuit.metrics.metrics;
- t.plan(circuit.eventNames().length);
- for (let name of circuit.eventNames()) {
- const match = new RegExp(`circuit_passFail_${name}`);
- t.ok(match.test(metrics), name);
- }
- circuit.metrics.clear();
- t.end();
- });
-
- test('Default prometheus metrics are enabled', t => {
- const circuit = circuitBreaker(passFail, {usePrometheus: true});
- const metrics = circuit.metrics.metrics;
- const names = [
- 'process_cpu_seconds_total',
- 'process_open_fds',
- 'process_max_fds',
- 'process_virtual_memory_bytes',
- 'process_resident_memory_bytes',
- 'process_heap_bytes',
- 'process_start_time_seconds'
- ];
- t.plan(names.length);
- for (let name of names) {
- const match = new RegExp(`circuit_passFail_${name}`);
- t.ok(match.test(metrics), name);
- }
- circuit.metrics.clear();
- t.end();
- });
-
- test('Should not add default metrics to custom registry', t => {
- const registry = new Registry();
- const circuit = circuitBreaker(passFail, {
- usePrometheus: true,
- prometheusRegistry: registry
- });
- const metrics = circuit.metrics.metrics;
- const names = [
- 'process_cpu_seconds_total',
- 'process_open_fds',
- 'process_max_fds',
- 'process_virtual_memory_bytes',
- 'process_resident_memory_bytes',
- 'process_heap_bytes',
- 'process_start_time_seconds'
- ];
- t.plan(names.length);
- for (let name of names) {
- const match = new RegExp(`circuit_passFail_${name}`);
- t.notOk(match.test(metrics), name);
- }
- circuit.metrics.clear();
- t.end();
- });
-
- test('Node.js specific metrics are enabled', t => {
- const circuit = circuitBreaker(passFail, {usePrometheus: true});
- const metrics = circuit.metrics.metrics;
- const names = [
- 'nodejs_eventloop_lag',
- 'nodejs_active_handles',
- 'nodejs_active_requests',
- 'nodejs_heap_size_total_bytes',
- 'nodejs_heap_size_used_bytes',
- 'nodejs_external_memory_bytes',
- 'nodejs_heap_space_size_total_bytes',
- 'nodejs_heap_space_size_used_bytes',
- 'nodejs_heap_space_size_available_bytes',
- 'nodejs_version_info'
- ];
- t.plan(names.length);
- for (let name of names) {
- const match = new RegExp(`circuit_passFail_${name}`);
- t.ok(match.test(metrics), name);
- }
- circuit.metrics.clear();
- t.end();
- });
-}
diff --git a/test/test.js b/test/test.js
index a614858b..efaf4355 100644
--- a/test/test.js
+++ b/test/test.js
@@ -16,7 +16,6 @@ test('api', t => {
t.ok(breaker.closed, 'CircuitBreaker.closed');
t.ok(breaker.status, 'CircuitBreaker.status');
t.ok(breaker.options, 'CircuitBreaker.options');
- t.ok(breaker.hystrixStats, 'CircuitBreaker.hystrixStats');
t.equals(breaker.action, passFail, 'CircuitBreaker.action');
breaker.shutdown();
t.end();