@esmj/monitor
Advanced tools
Comparing version 0.5.2 to 0.5.3
@@ -297,2 +297,4 @@ 'use strict'; | ||
const IS_MEMO = Symbol('MemoSymbol'); | ||
function memo(func) { | ||
@@ -317,2 +319,3 @@ return (function (func) { | ||
memoized.clear = clear; | ||
memoized[IS_MEMO] = true; | ||
@@ -323,5 +326,103 @@ return memoized; | ||
function medianNoiseReduction(grouping = 5) { | ||
return function _medianNoiseReduction(array) { | ||
return array.map((value, index, array) => { | ||
const startIndex = | ||
index - Math.floor(grouping / 2) < 0 | ||
? 0 | ||
: index - Math.floor(grouping / 2); | ||
const endIndex = | ||
index + Math.ceil(grouping / 2) <= array.length | ||
? index + Math.ceil(grouping / 2) | ||
: array.length; | ||
const group = array.slice(startIndex, endIndex); | ||
return percentile(50)(group); | ||
}); | ||
}; | ||
} | ||
function linearRegression() { | ||
return function _linearRegression(array) { | ||
const { sumY, sumX, sumX2, sumXY } = array.reduce( | ||
(result, value, index) => { | ||
const x = value?.x ?? index + 1; | ||
const y = value?.y ?? value; | ||
result.sumX += x; | ||
result.sumY += y; | ||
result.sumX2 += x * x; | ||
result.sumXY += x * y; | ||
return result; | ||
}, | ||
{ | ||
sumY: 0, | ||
sumX: 0, | ||
sumX2: 0, | ||
sumXY: 0, | ||
} | ||
); | ||
const divisor = array.length * sumX2 - sumX * sumX; | ||
if (divisor === 0) { | ||
return { slope: 0, yIntercept: 0, predict: () => 0 }; | ||
} | ||
const yIntercept = (sumY * sumX2 - sumX * sumXY) / divisor; | ||
const slope = (array.length * sumXY - sumX * sumY) / divisor; | ||
return { | ||
slope, | ||
yIntercept, | ||
predict: (x = array.length + 1) => slope * x + yIntercept, | ||
}; | ||
}; | ||
} | ||
function percentile(number = 50) { | ||
return function _percentile(array) { | ||
if (!array.length) { | ||
return undefined; | ||
} | ||
const sortedArray = [...array].sort((a, b) => a - b); | ||
const size = sortedArray.length; | ||
const min = 100 / (size + 1); | ||
const max = (100 * size) / (size + 1); | ||
if (number <= min) { | ||
return sortedArray[0]; | ||
} | ||
if (number >= max) { | ||
return sortedArray[size - 1]; | ||
} | ||
const estimatedIndex = (number / 100) * (size + 1) - 1; | ||
const index = Math.floor(estimatedIndex); | ||
const decimalWeight = Math.abs(estimatedIndex) - index; | ||
return ( | ||
sortedArray[index] + | ||
decimalWeight * (sortedArray[index + 1] - sortedArray[index]) | ||
); | ||
}; | ||
} | ||
function takeLast(size) { | ||
return function _takeLast(array) { | ||
return array.slice( | ||
!size || size > array.length ? 0 : array.length - size, | ||
array.length | ||
); | ||
}; | ||
} | ||
class MetricsHistory extends observable.Observer { | ||
#options = { limit: 60 }; | ||
#regression = null; | ||
#history = []; | ||
custom = {}; | ||
@@ -331,3 +432,5 @@ constructor(options) { | ||
this.#options = { ...this.#options, ...options }; | ||
this.#regression = linearRegression(); | ||
// TODO deprecated, remove in next major version | ||
this.percentileMemo = memo((...rest) => this.percentile(...rest)); | ||
@@ -345,4 +448,17 @@ this.trendMemo = memo((...rest) => this.trend(...rest)); | ||
#clearMemo() { | ||
this.percentileMemo.clear(); | ||
this.trendMemo.clear(); | ||
Object.keys(this.custom).forEach((key) => { | ||
if (typeof this.custom[key] === 'function' && this.custom[key][IS_MEMO]) { | ||
this.custom[key].clear(); | ||
} | ||
}); | ||
} | ||
complete() { | ||
this.#history = []; | ||
this.#clearMemo(); | ||
} | ||
@@ -357,4 +473,3 @@ | ||
this.percentileMemo.clear(); | ||
this.trendMemo.clear(); | ||
this.#clearMemo(); | ||
} | ||
@@ -366,8 +481,20 @@ | ||
add(name, func) { | ||
if (this.custom[name]) { | ||
throw new Error( | ||
`The key "${name}" of custom statistic function is uccupied.` | ||
); | ||
} | ||
this.custom[name] = func; | ||
} | ||
// TODO deprecated, remove in next major version | ||
percentile(key, number) { | ||
const array = this.getValues(key); | ||
return this.#calculatePercentile(array, number); | ||
return percentile(number)(array); | ||
} | ||
// TODO deprecated, remove in next major version | ||
trend(key, limit) { | ||
@@ -381,9 +508,7 @@ let array = this.getValues(key); | ||
const { slope, yIntercept } = this.#getLinearRegression(array); | ||
return this.#regression(array); | ||
} | ||
return { | ||
slope, | ||
yIntercept, | ||
predict: (x = array.length + 1) => slope * x + yIntercept, | ||
}; | ||
from(key) { | ||
return () => this.getValues(key); | ||
} | ||
@@ -400,63 +525,2 @@ | ||
} | ||
#getLinearRegression(array) { | ||
const { sumY, sumX, sumX2, sumXY } = array.reduce( | ||
(result, value, index) => { | ||
const x = value?.x ?? index + 1; | ||
const y = value?.y ?? value; | ||
result.sumX += x; | ||
result.sumY += y; | ||
result.sumX2 += x * x; | ||
result.sumXY += x * y; | ||
return result; | ||
}, | ||
{ | ||
sumY: 0, | ||
sumX: 0, | ||
sumX2: 0, | ||
sumXY: 0, | ||
} | ||
); | ||
const divisor = array.length * sumX2 - sumX * sumX; | ||
if (divisor === 0) { | ||
return { slope: 0, yIntercept: 0 }; | ||
} | ||
const yIntercept = (sumY * sumX2 - sumX * sumXY) / divisor; | ||
const slope = (array.length * sumXY - sumX * sumY) / divisor; | ||
return { slope, yIntercept }; | ||
} | ||
#calculatePercentile(array, number) { | ||
if (!array.length) { | ||
return undefined; | ||
} | ||
const sortedArray = [...array].sort((a, b) => a - b); | ||
const size = sortedArray.length; | ||
const min = 100 / (size + 1); | ||
const max = (100 * size) / (size + 1); | ||
if (number <= min) { | ||
return sortedArray[0]; | ||
} | ||
if (number >= max) { | ||
return sortedArray[size - 1]; | ||
} | ||
const estimatedIndex = (number / 100) * (size + 1) - 1; | ||
const index = Math.floor(estimatedIndex); | ||
const decimalWeight = Math.abs(estimatedIndex) - index; | ||
return ( | ||
sortedArray[index] + | ||
decimalWeight * (sortedArray[index + 1] - sortedArray[index]) | ||
); | ||
} | ||
} | ||
@@ -489,2 +553,6 @@ | ||
Object.defineProperty(exports, 'pipe', { | ||
enumerable: true, | ||
get: function () { return observable.pipe; } | ||
}); | ||
exports.CPUUsageMetric = CPUUsageMetric; | ||
@@ -501,1 +569,6 @@ exports.EventLoopDelayMetric = EventLoopDelayMetric; | ||
exports.createMonitoring = createMonitoring; | ||
exports.linearRegression = linearRegression; | ||
exports.medianNoiseReduction = medianNoiseReduction; | ||
exports.memo = memo; | ||
exports.percentile = percentile; | ||
exports.takeLast = takeLast; |
{ | ||
"name": "@esmj/monitor", | ||
"version": "0.5.2", | ||
"version": "0.5.3", | ||
"description": "Node.js performance measurement metrics (cpu, memory, event loop, gc)", | ||
@@ -51,10 +51,10 @@ "keywords": [ | ||
"devDependencies": { | ||
"@commitlint/cli": "^17.4.4", | ||
"@commitlint/config-conventional": "^17.4.4", | ||
"@rollup/plugin-node-resolve": "^15.0.1", | ||
"@commitlint/cli": "^17.6.5", | ||
"@commitlint/config-conventional": "^17.6.5", | ||
"@rollup/plugin-node-resolve": "^15.1.0", | ||
"commitizen": "^4.3.0", | ||
"conventional-changelog-cli": "^2.2.2", | ||
"conventional-changelog-cli": "^3.0.0", | ||
"cz-conventional-changelog": "^3.3.0", | ||
"eslint": "^8.36.0", | ||
"eslint-config-prettier": "^8.7.0", | ||
"eslint": "^8.42.0", | ||
"eslint-config-prettier": "^8.8.0", | ||
"eslint-plugin-jasmine": "^4.1.3", | ||
@@ -66,5 +66,5 @@ "eslint-plugin-jest": "^27.2.1", | ||
"jest": "^29.5.0", | ||
"lint-staged": "^13.2.0", | ||
"prettier": "^2.8.4", | ||
"rollup": "^3.19.1", | ||
"lint-staged": "^13.2.2", | ||
"prettier": "^2.8.8", | ||
"rollup": "^3.24.0", | ||
"to-mock": "^1.6.2" | ||
@@ -86,4 +86,4 @@ }, | ||
"dependencies": { | ||
"@esmj/observable": "^0.1.0" | ||
"@esmj/observable": "^0.1.1" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
45149
1358
Updated@esmj/observable@^0.1.1