@esmj/monitor
Version:
Node.js performance measurement metrics (cpu, memory, event loop, gc)
1 lines • 14.3 kB
JavaScript
'use strict';var observable=require('@esmj/observable'),process=require('process'),perf_hooks=require('perf_hooks'),os=require('os'),v8=require('v8'),M=require('diagnostics_channel');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var M__default=/*#__PURE__*/_interopDefault(M);function w(i=5){return function(t){let r=[];for(let s=0;s<t.length;s++){let o=s-Math.floor(i/2)<0?0:s-Math.floor(i/2),a=s+Math.ceil(i/2)<=t.length?s+Math.ceil(i/2):t.length,h=t.slice(o,a);r.push(z(50)(h));}return r}}function T(){return function(e){let{sumY:t,sumX:r,sumX2:s,sumXY:o}=e.reduce((p,f,b)=>{let c=typeof f=="object"&&f!==null?f.x??b+1:b+1,R=typeof f=="object"&&f!==null?f.y??0:f;return p.sumX+=c,p.sumY+=R,p.sumX2+=c*c,p.sumXY+=c*R,p},{sumY:0,sumX:0,sumX2:0,sumXY:0}),a=e.length*s-r*r;if(a===0)return {slope:0,yIntercept:0,predict:()=>0};let h=(t*s-r*o)/a,y=(e.length*o-r*t)/a;return {slope:y,yIntercept:h,predict:(p=e.length+1)=>y*p+h}}}function z(i=50){return function(t){if(!t.length)return;let r=[...t].sort((f,b)=>f-b),s=r.length,o=100/(s+1),a=100*s/(s+1);if(i<=o)return r[0];if(i>=a)return r[s-1];let h=i/100*(s+1)-1,y=Math.floor(h),p=Math.abs(h)-y;return r[y]+p*(r[y+1]-r[y])}}function d(i){return function(t){return t.slice(!i||i>t.length?0:t.length-i,t.length)}}function k(){return function(e){return e[0]}}function O(){return function(e){return e[e.length-1]}}function E(){return function(e){return e.length?B(e)/e.length:void 0}}function re(){return B}function B(i){if(!i.length)return;let e=0;for(let t=0;t<i.length;t++){if(typeof i[t]!="number"||Number.isNaN(i[t]))return;e+=i[t];}return e}var V=Symbol("MemoSymbol");function l(i){let e={},t=(...o)=>o.join("-"),r=()=>{e={};},s=((...o)=>{let a=t(...o);return e[a]||(e[a]=i(...o)),e[a]});return s.clear=r,s[V]=true,s}var L=class extends observable.Observer{#e={limit:60};#t=[];#o=null;custom={};percentileMemo;trendMemo;constructor(e){super(),this.#e={...this.#e,...e},this.#o=T(),this.percentileMemo=l((...t)=>this.percentile(...t)),this.trendMemo=l((...t)=>this.trend(...t));}get size(){return this.#t.length}get current(){return this.#t[this.#t.length-1]}#n(){this.percentileMemo.clear(),this.trendMemo.clear(),Object.keys(this.custom).forEach(e=>{typeof this.custom[e]=="function"&&this.custom[e][V]&&this.custom[e].clear();});}complete(){this.#t=[],this.#n();}next(e){this.#t.push({timestamp:Date.now(),...e}),this.#t.length>this.#e.limit&&this.#t.shift(),this.#n();}error(e){console.error(e);}add(e,t){if(this.custom[e])throw new Error(`The key "${e}" of custom statistic function is occupied.`);this.custom[e]=t;}percentile(e,t){let r=this.getValues(e);return z(t)(r)}trend(e,t){let r=this.getValues(e);return r=r.slice(!t||t>r.length?0:r.length-t,r.length),this.#o(r)}from(e){return ()=>this.getValues(e)}getValues(e){let t=e?.split(".")??[];return this.#t.map(r=>t.reduce((s,o)=>s?.[o],r))}};var S=class extends observable.Observable{#e={interval:1e3};#t=null;#o=[];constructor(e){super(),this.#e={...this.#e,...e};}add(e){return this.#o.push(e),()=>{this.remove(e);}}remove(e){let t=this.#o.indexOf(e);this.#o.splice(t,1);}start(){this.#t||(this.#n("start",this.#e),this.#i());}stop(){clearInterval(this.#t),this.#n("stop",this.#e),this.complete();}#n(e,t){return this.#o.reduce((r,s)=>(Object.assign(r,s[e](t)),r),{})}#i(){this.#t=setInterval(()=>{this.#n("beforeMeasure",this.#e);let e=this.#n("measure",this.#e);this.#s(e),this.#n("afterMeasure",this.#e);},this.#e.interval).unref();}#s(...e){try{this.next(...e);}catch(t){this.error(t);}}};function oe(i,e){return N.indexOf(i?.level)>=N.indexOf(e)}function W(i){let e={10:10,25:25,50:50,100:100,200:200,500:500,1e3:1e3,2e3:2e3,5e3:5e3,Infinity:1e5},t=0,r=0;for(let s in i){let o=i[s],a=e[s];t+=o,r+=o*a;}return t===0?0:r/t}var J=5e3,G=J/1e3,m=Object.freeze({NORMAL:"normal",LOW:"low",MEDIUM:"medium",HIGH:"high",CRITICAL:"critical",FATAL:"fatal"}),N=[m.NORMAL,m.LOW,m.MEDIUM,m.HIGH,m.CRITICAL,m.FATAL],P={threshold:{denialOfService:10,distributedDenialOfService:20,deadlock:10,oldDataToFatalTime:4e3},experimental:{evaluateMemoryUsage:false}},I=class{#e=null;#t=null;#o=null;#n=null;#i=null;#s=null;#u=null;#r=null;#l=[];#c=null;#a=null;#h=new WeakMap;constructor(e,t,r,s,o,a,h={}){this.#e=t,this.#t=e,this.#o=r,this.#n=s,this.#i=o,this.#s=a,this.#c={...P,...h,threshold:{...P.threshold,...h?.threshold},experimental:{...P.experimental,...h?.experimental}};}init(){this.#y(),this.#l=[(e,t)=>{t.count.total>this.#c?.threshold?.denialOfService&&(this.#u={...this.#r,records:[...this.#r.records]},this.#r.score=Math.min(this.#r.score+75,100),this.#r.level=this.#m(this.#r.score),this.#r.records.push({score:80,metric:"denialOfServiceDetected"}));},(e,t)=>{t.count.total>this.#c?.threshold?.distributedDenialOfService&&(this.#u={...this.#r,records:[...this.#r.records]},this.#r.score=100,this.#r.level=this.#m(this.#r.score),this.#r.records.push({score:100,metric:"distributedDenialOfServiceDetected"}));},(e,t)=>{e.count.active>this.#c?.threshold?.deadlock&&(this.#u={...this.#r,records:[...this.#r.records]},this.#r.score=Math.min(this.#r.score+75,100),this.#r.level=this.#m(this.#r.score),this.#r.records.push({score:80,metric:"deadlockDetected"}));}],this.#t.subscribe(()=>{this.#u=this.#r,this.#r=this.#p(),this.#f();});}getThreats(e){if(e&&this.#h.has(e))return this.#h.get(e);if(this.#r||(this.#u=this.#r,this.#r=this.#p(),this.#f()),this.#b()&&(this.#r.level=m.FATAL),this.#r.score<80){let{request:t}=this.#i.measure(),{request:r}=this.#s.measure();for(let s of this.#l)if(this.#r.score<80)s(t,r);else break}return e&&this.#h.set(e,this.#r),this.#r}#p(){let e=[],t=0;return this.#d(e),this.#M(e),this.#g(e),this.#c?.experimental?.evaluateMemoryUsage&&this.#v(e),t=Math.min(e.reduce((r,{score:s})=>r+s,0),100),this.#u&&t<this.#u.score-5&&(t=this.#u.score-5,e.push(...this.#u.records),e.push({score:-5,metric:"decreasingSeverity"}),e.length>20&&e.splice(0,e.length-20)),{score:t,level:this.#m(t),records:e}}#y(){this.#e.add("getCurrentUtilization",l(observable.pipe(this.#e.from("eventLoopUtilization.utilization"),d(5),w(),O(),e=>e??0))),this.#e.add("getAverageUtilization",l(observable.pipe(this.#e.from("eventLoopUtilization.utilization"),d(15),E(),e=>e??0))),this.#e.add("getCurrentMemoryPercent",l(observable.pipe(this.#e.from("memoryUsage.percent"),d(1),O(),e=>e??0))),this.#e.add("getAverageMemoryPercent",l(observable.pipe(this.#e.from("memoryUsage.percent"),d(15),E(),e=>e??0))),this.#e.add("getEventLoopDelay",l(observable.pipe(this.#e.from("eventLoopDelay.percentile80"),d(1),k(),e=>e??0))),this.#e.add("getAverageEventLoopDelay",l(observable.pipe(this.#e.from("eventLoopDelay.percentile80"),d(15),E(),e=>e??0))),this.#e.add("getRequestsActiveCountsTrend",l(observable.pipe(this.#e.from("request.count.active"),d(Math.round(G)),T(),e=>e??{slope:0,yIntercept:0,predict:()=>0}))),this.#e.add("getRequestsDurationsTrend",l(observable.pipe(this.#e.from("request.duration"),d(Math.round(G)),e=>e.map(W),T(),e=>e??{slope:0,yIntercept:0,predict:()=>0})));}#d(e){return (this.#n.size<50||this.#e.size<5)&&e.push({score:30,metric:"insufficientMetricsHistory"}),e}#M(e){let t=this.#e.custom.getAverageUtilization(),r=this.#e.custom.getCurrentUtilization();if(t>=.3){if(r>t*2&&e.push({score:20+r/t*5,metric:"utilizationSpike"}),t>=.9)return e.push({score:80,metric:"criticalUtilization"}),e;if(t>=.8)return e.push({score:65,metric:"veryHighUtilization"}),e;if(t>=.7)return e.push({score:50,metric:"highUtilization"}),e;if(t>=.6)return e.push({score:35,metric:"elevatedUtilization"}),e;if(t>=.5)return e.push({score:15,metric:"moderateUtilization"}),e}return e}#v(e){let t=this.#e.custom.getCurrentMemoryPercent(),r=this.#e.custom.getAverageMemoryPercent();if(t*1.5>=r){if(t>=90)return e.push({score:65,metric:"criticalMemoryUsage"}),e;if(t>=80)return e.push({score:50,metric:"highMemoryUsage"}),e;if(t>=70)return e.push({score:40,metric:"elevatedMemoryUsage"}),e;if(t>=60)return e.push({score:25,metric:"moderateMemoryUsage"}),e}return e}#g(e){let t=this.#e.custom.getAverageEventLoopDelay(),s=this.#e.custom.getEventLoopDelay()/(t||1),o=Math.min(this.#e.size/15,1);return s>=2&&e.push({score:5+10*o,metric:"eventLoopDelaySpike"}),e}#b(){let e=this.#e.current,t=Date.now();return !!(t-e?.timestamp>=this.#c.threshold.oldDataToFatalTime||this.#a&&t-this.#a>=J&&this.#e.custom.getRequestsActiveCountsTrend?.().slope>0&&this.#e.custom.getRequestsDurationsTrend?.().slope>0)}#f(){this.#r?.level===m.CRITICAL?this.#a===null&&(this.#a=Date.now()):this.#a=null;}#m(e){return e>=80?m.CRITICAL:e>=65?m.HIGH:e>=50?m.MEDIUM:e>=30?m.LOW:m.NORMAL}};var u=class{start(e){}beforeMeasure(e){}measure(e){}afterMeasure(e){}stop(e){}};function n(i){return Math.round(i*100)/100}var U=class extends u{#e=null;start(){this.#e=process.cpuUsage();}measure({interval:e}){let t=process.cpuUsage(this.#e);return {cpuUsage:{user:t.user,system:t.system,percent:n(100*(t.user+t.system)/(e*1e3))}}}afterMeasure(){this.#e=process.cpuUsage();}stop(){this.#e=null;}};var _=class extends u{#e=null;start(){this.#e=perf_hooks.monitorEventLoopDelay({resolution:20}),this.#e.enable();}beforeMeasure(){this.#e.disable();}measure({interval:e}){return {eventLoopDelay:{min:n(this.#e.min/(e*1e3)),max:n(this.#e.max/(e*1e3)),mean:n(this.#e.mean/(e*1e3)),stddev:n(this.#e.stddev/(e*1e3)),percentile80:n(this.#e.percentile(80)/(e*1e3))}}}afterMeasure(){this.#e.reset(),this.#e.enable();}stop(){this.#e.reset(),this.#e.disable(),this.#e=null;}};var {eventLoopUtilization:X}=perf_hooks.performance,x=class extends u{#e=null;#t=null;start(){this.#e=X();}beforeMeasure(){this.#t=X();}measure(){let e=X(this.#t,this.#e);return {eventLoopUtilization:{idle:n(e.idle),active:n(e.active),utilization:n(e.utilization)}}}afterMeasure(){this.#e=this.#t;}stop(){this.#e=null,this.#t=null;}};var H=class extends u{#e=null;#t=null;start(){this.#e=new perf_hooks.PerformanceObserver(e=>{let t=e.getEntries();t?.[0]&&(this.#t=t[0]);}),this.#e.observe({entryTypes:["gc"]});}measure(){return {gc:{entry:this.#t}}}afterMeasure(){this.#t=null;}stop(){this.#e.disconnect();}};var A=class extends u{measure(){let[e,t,r]=os.loadavg();return {loadAverage:{minute1:n(e),minute5:n(t),minute15:n(r)}}}};var q=class extends u{#e=null;start(){this.#e=v8.getHeapStatistics();}measure(){let e=process.memoryUsage();return {memoryUsage:{percent:n(e.rss/this.#e.heap_size_limit*100),rss:this.#t(e.rss),heapTotal:this.#t(e.heapTotal),heapUsed:this.#t(e.heapUsed),external:this.#t(e.external),arrayBuffers:this.#t(e.arrayBuffers)}}}stop(){this.#e=null;}#t(e){return n(e/1024/1024)}};var C=class extends u{measure(){return {process:{pid:process.pid,ppid:process.ppid,platform:process.platform,uptime:process.uptime(),version:process.version}}}};var K="http.server.request.start",Z="http2.server.request.start",$="http.server.response.finish",Q="http2.server.response.finish",ee="net.server.socket",te=Symbol("requestStart"),D=class extends u{#e=Symbol("request key");#t=this._createRequest.bind(this);#o=this._finishRequest.bind(this);#n=this._createConnection.bind(this);#i={total:0,active:0,zombie:0,clientErrors:0,httpServerErrors:0,connections:0};#s={10:0,25:0,50:0,100:0,200:0,500:0,1e3:0,2e3:0,5e3:0,Infinity:0};start(){try{M__default.default.subscribe(K,this.#t),M__default.default.subscribe(Z,this.#t),M__default.default.subscribe($,this.#o),M__default.default.subscribe(Q,this.#o),M__default.default.subscribe(ee,this.#n);}catch(e){console.warn("Diagnostics channel is not available:",e);}}measure(){return {request:{count:{total:this.#i.total,active:this.#i.active,zombie:this.#i.zombie,clientErrors:this.#i.clientErrors,serverErrors:this.#i.httpServerErrors,connections:this.#i.connections},duration:{10:this.#s[10],25:this.#s[25],50:this.#s[50],100:this.#s[100],200:this.#s[200],500:this.#s[500],1e3:this.#s[1e3],2e3:this.#s[2e3],5e3:this.#s[5e3],Infinity:this.#s.Infinity},errorRate:this.#i.total?n(100*(this.#i.clientErrors+this.#i.httpServerErrors)/this.#i.total):0}}}afterMeasure(){this.#i={total:0,active:this.#i.active,zombie:this.#i.zombie,clientErrors:0,httpServerErrors:0,connections:0},this.#s={10:0,25:0,50:0,100:0,200:0,500:0,1e3:0,2e3:0,5e3:0,Infinity:0};}stop(){try{M__default.default.unsubscribe(K,this.#t),M__default.default.unsubscribe(Z,this.#t),M__default.default.unsubscribe($,this.#o),M__default.default.unsubscribe(Q,this.#o),M__default.default.unsubscribe(ee,this.#n);}catch{}}_createConnection(e){this.#i.connections++;}_createRequest({request:e,response:t}){e[te]=performance.now(),e[this.#e]=false;let r=()=>{e[this.#e]||(this.#i.active--,e[this.#e]=true);};e.on("close",r),t.on("finish",r),t.on("close",r);let s=setTimeout(()=>{this.#i.zombie++;},30*1e3),o=()=>clearTimeout(s);e.on("close",o),t.on("finish",o),t.on("close",o),this.#i.total++,this.#i.active++;}_finishRequest({request:e,response:t}){let r=performance.now()-e[te],s=r<=10?10:r<=25?25:r<=50?50:r<=100?100:r<=200?200:r<=500?500:r<=1e3?1e3:r<=2e3?2e3:r<=5e3?5e3:"Infinity";this.#s[s]++,t?.statusCode>=400&&t?.statusCode<500?this.#i.clientErrors++:t?.statusCode>=500&&this.#i.httpServerErrors++;}};function zt(i){let e=new U,t=new _,r=new x,s=new A,o=new q,a=new H,h=new C,y=new D,p=new x,f=new q,b=new D,c=new S(i?.monitor),R=new L(i?.metricsHistory),v=new S(i?.shortMonitor??{interval:10}),F=new L(i?.shortMetricsHistory??{limit:100}),j=new I(c,R,v,F,y,b,i?.severity);return j.init(),c.subscribe(R),v.subscribe(F),c.add(e),c.add(t),c.add(r),c.add(s),c.add(o),c.add(a),c.add(h),c.add(y),v.add(p),v.add(f),v.add(b),{monitor:c,metricsHistory:R,shortMonitor:v,shortMetricsHistory:F,severity:j,start(){c.start(),v.start();},stop(){c.stop(),v.stop();}}}Object.defineProperty(exports,"pipe",{enumerable:true,get:function(){return observable.pipe}});exports.CPUUsageMetric=U;exports.EventLoopDelayMetric=_;exports.EventLoopUtilizationMetric=x;exports.GCMetric=H;exports.LoadAverageMetric=A;exports.MemoryUsageMetric=q;exports.Metric=u;exports.MetricsHistory=L;exports.Monitor=S;exports.ProcessMetric=C;exports.RequestMetric=D;exports.SEVERITY_LEVEL=m;exports.Severity=I;exports.avg=E;exports.createMonitoring=zt;exports.first=k;exports.isSeverityLevelAtLeast=oe;exports.last=O;exports.linearRegression=T;exports.medianNoiseReduction=w;exports.memo=l;exports.percentile=z;exports.sum=re;exports.takeLast=d;