node-red-contrib-prib-functions 0.19.2 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/codeql-analysis.yml +3 -3
- package/.github/workflows/npmpublish.yml +6 -6
- package/.vs/VSWorkspaceState.json +7 -0
- package/.vs/node-red-contrib-prib-functions/v17/.wsuo +0 -0
- package/README.md +84 -70
- package/dataAnalysis/arrayAllRowsSwap.js +15 -0
- package/dataAnalysis/arrayCompareToPrecision.js +34 -0
- package/dataAnalysis/arrayDifference.js +14 -0
- package/dataAnalysis/arrayDifferenceSeasonal.js +15 -0
- package/dataAnalysis/arrayDifferenceSeasonalSecondOrder.js +20 -0
- package/dataAnalysis/arrayDifferenceSecondOrder.js +14 -0
- package/dataAnalysis/arrayForEachRange.js +38 -0
- package/dataAnalysis/arrayOverlay.js +13 -0
- package/dataAnalysis/arrayProduct.js +11 -0
- package/dataAnalysis/arrayRandom.js +14 -0
- package/dataAnalysis/arrayReduceRange.js +11 -0
- package/dataAnalysis/arrayScale.js +11 -0
- package/dataAnalysis/arraySum.js +11 -0
- package/dataAnalysis/arraySumSquared.js +11 -0
- package/dataAnalysis/arraySwap.js +11 -0
- package/dataAnalysis/dataAnalysis.html +52 -21
- package/dataAnalysis/dataAnalysis.js +73 -44
- package/dataAnalysis/generateMatrixFunction.js +89 -0
- package/dataAnalysis/generateVectorFunction.js +25 -0
- package/dataAnalysis/pca.js +472 -325
- package/dataAnalysis/svd.js +239 -0
- package/documentation/DataAnalysisRealtime.JPG +0 -0
- package/documentation/monitorSystem.JPG +0 -0
- package/documentation/monitorSystemTest.JPG +0 -0
- package/echart/echart.html +68 -0
- package/echart/echart.js +85 -0
- package/echart/icons/chart-671.png +0 -0
- package/echart/lib/echarts.js +95886 -0
- package/lib/Chart.js +177 -0
- package/lib/Column.js +99 -0
- package/lib/GraphDB.js +14 -0
- package/lib/Table.js +185 -0
- package/lib/objectExtensions.js +361 -0
- package/matrix/matrix.js +95 -56
- package/matrix/matrixNode.html +88 -55
- package/matrix/matrixNode.js +12 -5
- package/monitor/BarGauge.js +8 -0
- package/monitor/Dataset.js +29 -0
- package/monitor/DialGauge.js +109 -0
- package/monitor/DialNeedle.js +36 -0
- package/monitor/Format.js +74 -0
- package/monitor/centerElement.js +14 -0
- package/monitor/compareElements.js +95 -0
- package/monitor/defs.js +23 -0
- package/monitor/extensions.js +906 -0
- package/monitor/functions.js +36 -0
- package/monitor/json2xml.js +103 -0
- package/monitor/monitorSystem.html +199 -0
- package/monitor/monitorSystem.js +322 -0
- package/monitor/svgHTML.js +179 -0
- package/monitor/svgObjects.js +64 -0
- package/package.json +20 -6
- package/test/00-objectExtensions.js +94 -0
- package/test/04-tables.js +33 -0
- package/test/data/.config.nodes.json +608 -0
- package/test/data/.config.nodes.json.backup +608 -0
- package/test/data/.config.runtime.json +4 -0
- package/test/data/.config.runtime.json.backup +3 -0
- package/test/data/.config.users.json +21 -0
- package/test/data/.config.users.json.backup +21 -0
- package/test/data/.flow.json.backup +2820 -2003
- package/test/data/float32vector10.npy +0 -0
- package/test/data/flow.json +2830 -2033
- package/test/data/int2matrix2x3.npy +0 -0
- package/test/data/package-lock.json +158 -0
- package/test/data/package.json +11 -0
- package/test/dataAnalysisExtensions.js +471 -0
- package/test/dataAnalysisPCA.js +54 -0
- package/test/dataAnalysisSVD.js +31 -0
- package/test/euclideanDistance.js +2 -2
- package/test/transformConfluence.js +1 -1
- package/test/transformNumPy.js +132 -0
- package/testing/test.html +1 -1
- package/testing/test.js +78 -53
- package/transform/NumPy.js +303 -0
- package/transform/transform.html +12 -0
- package/transform/transform.js +34 -2
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module.exports={
|
|
2
|
+
coalesce:()=>{
|
|
3
|
+
const args = coalesce.arguments;
|
|
4
|
+
for (let i=0; i<args.length; ++i)
|
|
5
|
+
if (args[i] != null) return args[i];
|
|
6
|
+
return null;
|
|
7
|
+
},
|
|
8
|
+
deepClone:(deepObject)=>{
|
|
9
|
+
if(deepObject==null) return null;
|
|
10
|
+
if(deepObject instanceof Array)
|
|
11
|
+
let newObj=[];
|
|
12
|
+
else if(deepObject instanceof String)
|
|
13
|
+
return new String(deepObject);
|
|
14
|
+
else if(deepObject instanceof Number)
|
|
15
|
+
return new Number(deepObject);
|
|
16
|
+
else if(deepObject instanceof Date)
|
|
17
|
+
return new Date(deepObject);
|
|
18
|
+
else if(typeof deepObject == "object")
|
|
19
|
+
let newObj={};
|
|
20
|
+
else return deepObject;
|
|
21
|
+
for (i in deepObject)
|
|
22
|
+
newObj[i]=deepClone(deepObject[i]);
|
|
23
|
+
return newObj;
|
|
24
|
+
},
|
|
25
|
+
getWord:(value,wordPosition)=>value.split(/\s+/g,wordPosition+1)[wordPosition-1],
|
|
26
|
+
rangeLimit (value,min,max) {
|
|
27
|
+
if(min!=null)
|
|
28
|
+
if(value<min)
|
|
29
|
+
return min;
|
|
30
|
+
if(max!=null)
|
|
31
|
+
if(value>max)
|
|
32
|
+
return max;
|
|
33
|
+
return value;
|
|
34
|
+
},
|
|
35
|
+
nullif:(a,b)=>(a==b?null:a)
|
|
36
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const Format=require('./Format')
|
|
2
|
+
function json2xml(tag, obj, emptyIsNull, hexObjectWhenTag) {
|
|
3
|
+
try{
|
|
4
|
+
if(hexObjectWhenTag!=null) {
|
|
5
|
+
const l=hexObjectWhenTag.length
|
|
6
|
+
for(let i=0;i<l;i++)
|
|
7
|
+
if(hexObjectWhenTag[i]==tag)
|
|
8
|
+
return '<' + tag + '><jsonObject>'
|
|
9
|
+
+ Format.StringToHex(JSON.stringify(obj))
|
|
10
|
+
+ '</jsonObject></' + tag + '>'
|
|
11
|
+
}
|
|
12
|
+
if (typeof obj === 'undefined' || obj === null)
|
|
13
|
+
return '<' + tag + '/>';
|
|
14
|
+
if (typeof obj !== 'object')
|
|
15
|
+
return '<' + tag + '>'
|
|
16
|
+
+ Format.xmlEncodeString(obj)
|
|
17
|
+
+ '</' + tag + '>';
|
|
18
|
+
|
|
19
|
+
let elementValue ='';
|
|
20
|
+
if (obj.constructor === Array) {
|
|
21
|
+
for (let i = 0; i < obj.length; i++) {
|
|
22
|
+
if (typeof obj[i] !== 'object'
|
|
23
|
+
|| obj[i].constructor == Object) {
|
|
24
|
+
elementValue += json2xml('jsonArrayElement', obj[i],emptyIsNull, hexObjectWhenTag);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
throw new Error((typeof obj[i]) + ' is not supported.');
|
|
28
|
+
}
|
|
29
|
+
return '<'+tag+'>' + elementValue + '</'+tag+'>';
|
|
30
|
+
}
|
|
31
|
+
if (obj.constructor !== Object)
|
|
32
|
+
return '<' + tag + '/>';
|
|
33
|
+
let attributes ='';
|
|
34
|
+
if (typeof obj['#text'] !== 'undefined') {
|
|
35
|
+
if (typeof obj['#text'] == 'object')
|
|
36
|
+
throw new Error((typeof obj['#text']) + ' which is #text, not supported.');
|
|
37
|
+
elementValue += Format.xmlEncodeString(obj['#text']);
|
|
38
|
+
}
|
|
39
|
+
for (let name in obj) {
|
|
40
|
+
if(name==null) continue;
|
|
41
|
+
let objElement=obj[name];
|
|
42
|
+
if(objElement==null) continue
|
|
43
|
+
if (name.charAt(0) == '$') {
|
|
44
|
+
elementValue += '<jsonDollarParameter name="' + name + '">'+Format.xmlEncodeString(objElement)+'</jsonDollarParameter>';
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (name.charAt(0) == '@') {
|
|
48
|
+
if (typeof obj[b] == 'object')
|
|
49
|
+
throw new Error((typeof objElement) + ' attribute not supported.');
|
|
50
|
+
attributes += ' ' + name.substring(1) + '="' + Format.xmlEncodeString(objElement) + '"';
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
switch (obj[name].constructor) {
|
|
54
|
+
case Array :
|
|
55
|
+
if(hexObjectWhenTag!=null) {
|
|
56
|
+
for(let i=0;i<hexObjectWhenTag.length;i++)
|
|
57
|
+
if(hexObjectWhenTag[i]==name) {
|
|
58
|
+
elementValue += '<' + name + '><jsonObject>'
|
|
59
|
+
+ Format.StringToHex(JSON.stringify(objElement))
|
|
60
|
+
+ '</jsonObject></' + name + '>';
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
if( i<hexObjectWhenTag.length) continue;
|
|
64
|
+
}
|
|
65
|
+
elementValue+='<'+name+'>';
|
|
66
|
+
for (let i = 0; i < objElement.length; i++) {
|
|
67
|
+
if (typeof objElement[i] !== 'object'
|
|
68
|
+
|| objElement[i].constructor == Object) {
|
|
69
|
+
elementValue += json2xml('jsonArrayElement', objElement[i],emptyIsNull, hexObjectWhenTag);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
throw new Error((typeof objElement[i]) + ' is not supported.');
|
|
73
|
+
}
|
|
74
|
+
elementValue+='</'+name+'>';
|
|
75
|
+
continue;
|
|
76
|
+
case Object :
|
|
77
|
+
elementValue += json2xml(name, objElement,emptyIsNull, hexObjectWhenTag);
|
|
78
|
+
continue;
|
|
79
|
+
case String :
|
|
80
|
+
if(objElement==null) continue;
|
|
81
|
+
if(emptyIsNull&&objElement=="") continue;
|
|
82
|
+
if(objElement.length<36) {
|
|
83
|
+
attributes += ' '+name+'="'+Format.xmlEncodeString(objElement)+'"';
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
elementValue += '<' + name + '>' + Format.xmlEncodeString(objElement) + '</' + name + '>';
|
|
87
|
+
continue;
|
|
88
|
+
case Number :
|
|
89
|
+
attributes += ' '+name+'="'+objElement+'"';
|
|
90
|
+
continue;
|
|
91
|
+
case Boolean :
|
|
92
|
+
attributes += ' '+name+'="'+(objElement?'jsonTrue':'jsonFalse')+'"';
|
|
93
|
+
continue;
|
|
94
|
+
default:
|
|
95
|
+
throw new Error((typeof objElement) + ' is not supported.');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return '<' + tag + attributes + ( elementValue =='' ? '/>' : '>'+ elementValue + '</' + tag + '>' );
|
|
99
|
+
} catch(e) {
|
|
100
|
+
throw new Error('jso2xml error: '+e);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
module.exports=json2xml
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
const nodeNameURL="MonitorSystem";
|
|
3
|
+
const geval=eval
|
|
4
|
+
try{
|
|
5
|
+
const url=nodeNameURL+"/svgHTML"
|
|
6
|
+
console.log("fetching "+url)
|
|
7
|
+
const xhr = new XMLHttpRequest()
|
|
8
|
+
xhr.open("GET", url)
|
|
9
|
+
xhr.onload = () => {
|
|
10
|
+
console.log("fetched "+url)
|
|
11
|
+
if (xhr.readyState == 4 && xhr.status == 200) {
|
|
12
|
+
try{
|
|
13
|
+
geval?.(xhr.response)
|
|
14
|
+
} catch(ex){
|
|
15
|
+
console.log("fetch: "+xhr.status);
|
|
16
|
+
}
|
|
17
|
+
} else {
|
|
18
|
+
console.error("fetch: "+xhr.status);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
xhr.send()
|
|
22
|
+
} catch(ex) {
|
|
23
|
+
console.error("fetch: " +ex.message)
|
|
24
|
+
}
|
|
25
|
+
const nodeSvg={}
|
|
26
|
+
const nodeDetails={}
|
|
27
|
+
let svgUpdate=false
|
|
28
|
+
function drawAll() {
|
|
29
|
+
Object.keys(nodeDetails).forEach(id=>{
|
|
30
|
+
try{drawBase(id)} catch(ex) {console.error("draw "+id+" "+ex.stack)}
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
function drawBase(id) {
|
|
34
|
+
const svgBase=nodeSvg[id]
|
|
35
|
+
if(svgBase.element==null) {
|
|
36
|
+
svgBase.element=document.getElementById(id)
|
|
37
|
+
if(svgBase.element==null) return
|
|
38
|
+
}
|
|
39
|
+
if(svgBase.g) svgBase.g.remove()
|
|
40
|
+
try{
|
|
41
|
+
drawSVGElement(svgBase.element,
|
|
42
|
+
{action:"g",id:"_systemMonitor","font-size":10,style:"z-index:99999;",children:nodeDetails[id]},
|
|
43
|
+
id
|
|
44
|
+
)
|
|
45
|
+
} catch(ex) {
|
|
46
|
+
svgUpdate=false
|
|
47
|
+
if(ex.message.startsWith("element id not found")) return
|
|
48
|
+
console.error(ex.message + " for "+JSON.stringify(nodeDetails[id]))
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function refreshState(){
|
|
52
|
+
const action=svgUpdate==true?"getDataUpdateSVG":"getDataSVG"
|
|
53
|
+
$.get( "/"+nodeNameURL+"/"+action+"/" )
|
|
54
|
+
.done(function(json) {
|
|
55
|
+
Object.assign(nodeDetails,json)
|
|
56
|
+
try{
|
|
57
|
+
drawAll()
|
|
58
|
+
svgUpdate=true
|
|
59
|
+
} catch (ex) {
|
|
60
|
+
svgUpdate=false
|
|
61
|
+
}
|
|
62
|
+
}).fail(function( jqXHR, textStatus, error ) {
|
|
63
|
+
console.error("Monitor System refresh state failed: "+jqXHR.status+" "+textStatus+ " error "+error)
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if(!systemMonitor)
|
|
67
|
+
var systemMonitor=setInterval(refreshState, 1000)
|
|
68
|
+
/*globals RED */
|
|
69
|
+
RED.nodes.registerType('Monitor System', {
|
|
70
|
+
category: 'Monitor',
|
|
71
|
+
color: '#fdeea2',
|
|
72
|
+
defaults: {
|
|
73
|
+
name: {value: "",required:false},
|
|
74
|
+
metrics: {value: null,required:true},
|
|
75
|
+
graphs: {value: null,required:true}
|
|
76
|
+
},
|
|
77
|
+
inputs:0,
|
|
78
|
+
inputLabels: "in",
|
|
79
|
+
outputs:1,
|
|
80
|
+
outputLabels: ["out"],
|
|
81
|
+
icon: "icons8-heart-monitor-40.png",
|
|
82
|
+
label: function() {
|
|
83
|
+
console.log("*****+++++")
|
|
84
|
+
if(!nodeSvg.hasOwnProperty(this.id)) {
|
|
85
|
+
nodeSvg[this.id]={element:document.getElementById(this.id),actions:[{action:"text",x:0 ,y:60 ,children:["Waiting Refresh"]}]}
|
|
86
|
+
}
|
|
87
|
+
drawBase(this.id)
|
|
88
|
+
return this.name||this._("Monitor System");
|
|
89
|
+
},
|
|
90
|
+
labelStyle: function() {
|
|
91
|
+
return "node_label_italic";
|
|
92
|
+
},
|
|
93
|
+
oneditprepare: function() {
|
|
94
|
+
$("#node-input-metrics").typedInput({type:"metrics", types:[{
|
|
95
|
+
value: "metrics",
|
|
96
|
+
multiple: true,
|
|
97
|
+
options: [
|
|
98
|
+
{ value: "freeMemory", label: "Free Memory"},
|
|
99
|
+
{ value: "totalMemory", label: "Total Memory"},
|
|
100
|
+
{ value: "gaugeMemory", label: "Gauge Memory"},
|
|
101
|
+
{ value: "chartMemoryUsage", label: "Chart Memory"},
|
|
102
|
+
{ value: "heapStatistics", label: "Heap Statistics"},
|
|
103
|
+
{ value: "heapCodeStatistics", label: "Heap Code Statistics"},
|
|
104
|
+
{ value: "heapSpaceStatistics", label: "Heap Space Statistics"},
|
|
105
|
+
{ value: "loadAvg", label: "Load Avg"},
|
|
106
|
+
{ value: "memoryUsage", label: "Memory Usage"},
|
|
107
|
+
{ value: "resourceUsage", label: "Resource Usage"},
|
|
108
|
+
{ value: "chartLineResource", label: "Resource Chart"},
|
|
109
|
+
]
|
|
110
|
+
}]})
|
|
111
|
+
},
|
|
112
|
+
oneditsave: function() {
|
|
113
|
+
},
|
|
114
|
+
oneditresize: function() {
|
|
115
|
+
},
|
|
116
|
+
resizeRule : function(file,newWidth) {
|
|
117
|
+
},
|
|
118
|
+
button: {
|
|
119
|
+
enabled: function() {
|
|
120
|
+
return !this.changed;
|
|
121
|
+
},
|
|
122
|
+
onclick: function() {
|
|
123
|
+
if (this.changed) {
|
|
124
|
+
return RED.notify(RED._("workflow undeployed changes"),"warning");
|
|
125
|
+
}
|
|
126
|
+
let label=this._def.label.call(this);
|
|
127
|
+
if (label.length > 30) {
|
|
128
|
+
label = label.substring(0,50)+"...";
|
|
129
|
+
}
|
|
130
|
+
label=label.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
|
131
|
+
const node=this;
|
|
132
|
+
function sendCommand(element,action) {
|
|
133
|
+
$(element).dialog("close");
|
|
134
|
+
$.get( "/"+nodeNameURL+"/"+node.id+"/"+action )
|
|
135
|
+
.done(function(json) {
|
|
136
|
+
RED.notify(node._(nodeNameURL+" signal success",{label:label}),{type:"success",id:"Load Injector"});
|
|
137
|
+
$('<div></div>').appendTo('body').html(json2html(json))
|
|
138
|
+
.dialog({
|
|
139
|
+
modal: true,
|
|
140
|
+
title: (node.name||nodeNameURL)+" "+action,
|
|
141
|
+
zIndex: 10000,
|
|
142
|
+
autoOpen: true,
|
|
143
|
+
width: 'auto',
|
|
144
|
+
resizable: false,
|
|
145
|
+
buttons: {
|
|
146
|
+
close: function (event, ui) {
|
|
147
|
+
$(this).remove();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}).fail(function( jqXHR, textStatus, error ) {
|
|
152
|
+
if (jqXHR.status === 404) {
|
|
153
|
+
RED.notify(node._(nodeNameURL+" signal not deployed"),"error");
|
|
154
|
+
} else if (jqXHR.status === 500) {
|
|
155
|
+
RED.notify(node._(nodeNameURL+" signal inject failed with error "+(jqXHR.responseText||textStatus||error||"")),"error");
|
|
156
|
+
} else if (jqXHR.status === 0) {
|
|
157
|
+
RED.notify(node._(nodeNameURL+" signal no response"),"error");
|
|
158
|
+
} else {
|
|
159
|
+
RED.notify(node._(nodeNameURL+" signal unexpected status:"+jqXHR.status+" message:"+jqXHR.responseText||textStatus+" "+error),"error");
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
$('<div></div>').appendTo('body').html('<div>Choose Action</div>')
|
|
164
|
+
.dialog({
|
|
165
|
+
modal: true, title: (node.name||'Monitor System'), zIndex: 10000, autoOpen: true,
|
|
166
|
+
width: 'auto', resizable: false,
|
|
167
|
+
buttons: {
|
|
168
|
+
"Start": function () {
|
|
169
|
+
sendCommand(this,"Start");
|
|
170
|
+
},
|
|
171
|
+
"Stop": function () {
|
|
172
|
+
sendCommand(this,"Stop");
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
close: function (event, ui) {
|
|
176
|
+
$(this).remove();
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
</script>
|
|
183
|
+
|
|
184
|
+
<script type="text/x-red" data-template-name="Monitor System">
|
|
185
|
+
<div class="form-row">
|
|
186
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
187
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
|
188
|
+
</div>
|
|
189
|
+
<div class="form-row">
|
|
190
|
+
<label for="node-input-metrics"><i class="fa fa-tag"></i> Metrics</label>
|
|
191
|
+
<input type="text" id="node-input-metrics">
|
|
192
|
+
</div>
|
|
193
|
+
</script>
|
|
194
|
+
|
|
195
|
+
<script type="text/x-red" data-help-name="Monitor System">
|
|
196
|
+
<p>Displays counts of flows in diagram
|
|
197
|
+
</p>
|
|
198
|
+
</script>
|
|
199
|
+
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
const logger = new (require("node-red-contrib-logger"))("Monitor System")
|
|
2
|
+
logger.sendInfo("Copyright 2020 Jaroslav Peter Prib");
|
|
3
|
+
const os = require('node:os');
|
|
4
|
+
const v8 = require('node:v8');
|
|
5
|
+
const { memoryUsage, resourceUsage } = require('node:process')
|
|
6
|
+
const fs = require('fs')
|
|
7
|
+
const path = require("path");
|
|
8
|
+
const svgObject = require("./svgObjects.js");
|
|
9
|
+
const resourceLabels={
|
|
10
|
+
userCPUTime: "User Time (ms)",
|
|
11
|
+
systemCPUTime: "System Time (ms)",
|
|
12
|
+
maxRSS: "Max RSS (kb)",
|
|
13
|
+
// sharedMemorySize": "Shared Memeory", //<integer> maps to ru_ixrss but is not supported by any platform."
|
|
14
|
+
// unsharedDataSize <integer> maps to ru_idrss but is not supported by any platform.
|
|
15
|
+
// unsharedStackSize <integer> maps to ru_isrss but is not supported by any platform.
|
|
16
|
+
minorPageFault: "Minor Page Faults",
|
|
17
|
+
majorPageFault: "Major Page Faults",
|
|
18
|
+
// swappedOut: // <integer> maps to ru_nswap but is not supported by any platform.
|
|
19
|
+
fsRead: "Reads",
|
|
20
|
+
fsWrite: "Writes",
|
|
21
|
+
//ipcSent <integer> maps to ru_msgsnd but is not supported by any platform.
|
|
22
|
+
//ipcReceived <integer> maps to ru_msgrcv but is not supported by any platform.
|
|
23
|
+
//signalsCount <integer> maps to ru_nsignals but is not supported by any platform.
|
|
24
|
+
// voluntaryContextSwitches: "Voluntary Context Switches" // This field is not supported on Windows.
|
|
25
|
+
// involuntaryContextSwitches: "Involuntary Context Switches" // This field is not supported on Window
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const chartLine=new svgObject.Chart({type:"line",
|
|
29
|
+
labels:["RSS: Resident Set Size","Heap Total","Heap Used","External","Array Buffer","Free Memory","Total Memory"]})
|
|
30
|
+
const getPoints=(samples,metrics,metric,...properties)=>{
|
|
31
|
+
const points=[],size=Math.min(samples,metrics.length)
|
|
32
|
+
for(let i=0; i<size ; i++){
|
|
33
|
+
const m=metrics[i][metric]
|
|
34
|
+
points.push(properties.map(p=>m[p]))
|
|
35
|
+
}
|
|
36
|
+
return points
|
|
37
|
+
}
|
|
38
|
+
const getDiffPoints=(samples,metrics,metric,...properties)=>{
|
|
39
|
+
const points=getPoints(samples+1,metrics,metric,...properties)
|
|
40
|
+
return Array.from({ length: points.length-1}, (_, i) =>{
|
|
41
|
+
const next=points[i+1]
|
|
42
|
+
return points[i].map((c,j)=>c-next[j])
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const properties=Object.keys(resourceLabels)
|
|
47
|
+
const labels=properties.map(c=>resourceLabels[c])
|
|
48
|
+
const chartLineResource=new svgObject.Chart({type:"line",labels:labels,normaliseDimension:"column",
|
|
49
|
+
getData:()=>getDiffPoints(11,metrics,"resourceUsage",...properties)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
let svgHTMLCache
|
|
53
|
+
const {
|
|
54
|
+
performance,
|
|
55
|
+
PerformanceObserver,
|
|
56
|
+
} = require("node:perf_hooks");
|
|
57
|
+
const { action, children } = require("./defs.js");
|
|
58
|
+
const obs = new PerformanceObserver(list => {
|
|
59
|
+
const entry = list.getEntries()[0]
|
|
60
|
+
})
|
|
61
|
+
/*
|
|
62
|
+
https://nodejs.org/api/perf_hooks.html
|
|
63
|
+
|
|
64
|
+
PerformanceEntry {
|
|
65
|
+
name: 'gc',
|
|
66
|
+
entryType: 'gc',
|
|
67
|
+
startTime: 2820.567669,
|
|
68
|
+
duration: 1.315709,
|
|
69
|
+
kind: 1
|
|
70
|
+
}
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
process.resourceUsage()
|
|
74
|
+
const systemMetrics=()=>{
|
|
75
|
+
return {
|
|
76
|
+
freeMemory: os.freemem(),
|
|
77
|
+
heapCodeStatistics:v8.getHeapCodeStatistics(),
|
|
78
|
+
heapSpaceStatistics:v8.getHeapSpaceStatistics(),
|
|
79
|
+
heapStatistics:v8.getHeapStatistics(),
|
|
80
|
+
memoryUsage:memoryUsage(),
|
|
81
|
+
loadAvg: os.loadavg(),
|
|
82
|
+
timestamp: Date.now(),
|
|
83
|
+
resourceUsage: resourceUsage(),
|
|
84
|
+
totalMemory: os.totalmem(),
|
|
85
|
+
uptime: os.uptime()
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const metricLabel={
|
|
89
|
+
freeMemory: "Free Memory",
|
|
90
|
+
heapCodeStatistics:"Heap Code",
|
|
91
|
+
heapSpaceStatistics:"Heap Space",
|
|
92
|
+
heapStatistics:"Heap",
|
|
93
|
+
memoryUsage:"Memory Usage",
|
|
94
|
+
loadAvg: "Load avg",
|
|
95
|
+
resourceUsage: "Resource Usage",
|
|
96
|
+
totalMemory: "Total Memory"
|
|
97
|
+
}
|
|
98
|
+
const svcTextLine=(metric,value)=>{
|
|
99
|
+
return {action:"text",id:metric,height:10 ,children:[metricLabel[metric]+": "+(typeof value== "object"?JSON.stringify(value):value)]}
|
|
100
|
+
}
|
|
101
|
+
const svcTextLineUpdate=(metric,value)=>{
|
|
102
|
+
return {action:"update",id:metric,children:[metricLabel[metric]+": "+(typeof value== "object"?JSON.stringify(value):value)]}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const mappingMemory=["freeMemory","memoryUsage.","totalMemory",]
|
|
106
|
+
|
|
107
|
+
const getPointsMemory=()=>{
|
|
108
|
+
const points=[],size=Math.min(11,metrics.length)
|
|
109
|
+
for(let i=0; i<size ; i++){
|
|
110
|
+
const mi=metrics[i]
|
|
111
|
+
const m=mi.memoryUsage
|
|
112
|
+
points.push([m.rss,m.heapTotal,m.heapUsed,m.external,m.arrayBuffers,mi.freeMemory,mi.totalMemory])
|
|
113
|
+
}
|
|
114
|
+
return points
|
|
115
|
+
}
|
|
116
|
+
//const gapAngle=100
|
|
117
|
+
//const dialAngleArea=360-gapAngle
|
|
118
|
+
const memoryDial=new svgObject.DialGauge({title:"% used memory"})
|
|
119
|
+
const metricFunction={
|
|
120
|
+
chartMemoryUsage: ()=>({action:"g",id:"chartMemoryUsage",height:100,children:[chartLine.setData(getPointsMemory()).get()]}),
|
|
121
|
+
freeMemory: svcTextLine,
|
|
122
|
+
gaugeMemory: ()=>{
|
|
123
|
+
const currentState=metrics[0]
|
|
124
|
+
memoryDial.setMax(currentState.totalMemory)
|
|
125
|
+
return memoryDial.get(currentState.totalMemory-currentState.memoryUsage)
|
|
126
|
+
},
|
|
127
|
+
heapCodeStatistics:svcTextLine,
|
|
128
|
+
heapSpaceStatistics:svcTextLine,
|
|
129
|
+
heapStatistics:svcTextLine,
|
|
130
|
+
memoryUsage:svcTextLine,
|
|
131
|
+
loadAvg:svcTextLine,
|
|
132
|
+
resourceUsage:svcTextLine,
|
|
133
|
+
totalMemory:svcTextLine,
|
|
134
|
+
chartLineResource:()=>({action:"g",id:"chartLineResource",height:100,children:[chartLineResource.get()]})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const metricFunctionUpdate={
|
|
138
|
+
chartMemoryUsage: ()=>chartLine.setData(getPointsMemory()).getUpdate(),
|
|
139
|
+
freeMemory: svcTextLineUpdate,
|
|
140
|
+
gaugeMemory: ()=>{
|
|
141
|
+
const currentState=metrics[0]
|
|
142
|
+
return memoryDial.setMax(currentState.totalMemory).getUpdate(currentState.totalMemory-currentState.freeMemory)
|
|
143
|
+
},
|
|
144
|
+
heapCodeStatistics:svcTextLineUpdate,
|
|
145
|
+
heapSpaceStatistics:svcTextLineUpdate,
|
|
146
|
+
heapStatistics:svcTextLineUpdate,
|
|
147
|
+
memoryUsage:svcTextLineUpdate,
|
|
148
|
+
loadAvg:svcTextLineUpdate,
|
|
149
|
+
resourceUsage:svcTextLineUpdate,
|
|
150
|
+
totalMemory:svcTextLineUpdate,
|
|
151
|
+
chartLineResource:()=>chartLineResource.getUpdate()
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let metrics=[]
|
|
155
|
+
const nodes=[]
|
|
156
|
+
function getJson(){
|
|
157
|
+
return nodes.reduce((p,node)=>{
|
|
158
|
+
p[node.id]=metrics.length?node.getSendData(metrics[0]):null
|
|
159
|
+
return p;
|
|
160
|
+
},{})
|
|
161
|
+
}
|
|
162
|
+
function updateJson(){
|
|
163
|
+
return nodes.reduce((p,node)=>{
|
|
164
|
+
try{
|
|
165
|
+
if(metrics[0]) p[node.id]=node.getSendDataUpdate(metrics[0])
|
|
166
|
+
} catch (ex) {
|
|
167
|
+
logger.active&&logger.sendErrorAndStackDump("failed for "+node.id,ex)
|
|
168
|
+
}
|
|
169
|
+
return p;
|
|
170
|
+
},{})
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let runtimeTimer
|
|
174
|
+
function runtimeStop() {
|
|
175
|
+
if(runtimeTimer) {
|
|
176
|
+
clearTimeout(runtimeTimer)
|
|
177
|
+
runtimeTimer=null
|
|
178
|
+
}
|
|
179
|
+
nodes.forEach(node=>{
|
|
180
|
+
node.status({fill:"red",shape:"ring",text:"Stopped"})
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
const reset=()=>{
|
|
184
|
+
metrics=[];
|
|
185
|
+
nodes.forEach(node=>node.status({fill:"yellow",shape:"ring",text:"Metrics Reset"}))
|
|
186
|
+
}
|
|
187
|
+
const runtimeStart=(node)=>{
|
|
188
|
+
logger.active&&logger.send({ label: 'start system monitoring'})
|
|
189
|
+
if(runtimeTimer) clearTimeout(runtimeTimer)
|
|
190
|
+
runtimeTimer=setInterval(function(){checkLoop();},1000);
|
|
191
|
+
nodes.forEach(node=>{
|
|
192
|
+
node.status({fill:"green",shape:"ring",text:"Collecting"})
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
function checkLoop() {
|
|
196
|
+
logger.active&&logger.send({ label: 'checkloop collect state'})
|
|
197
|
+
const currentState=systemMetrics();
|
|
198
|
+
metrics.unshift(currentState);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = function (RED) {
|
|
202
|
+
function nodeFunction(n) {
|
|
203
|
+
RED.nodes.createNode(this, n);
|
|
204
|
+
let node=Object.assign(this,n);
|
|
205
|
+
if(runtimeTimer) node.status({fill:"green",shape:"ring",text:"Collecting"})
|
|
206
|
+
else node.status({fill:"yellow",shape:"ring",text:"Initial state"});
|
|
207
|
+
const selectedMetrics=node.metrics.split(',')
|
|
208
|
+
const sendData=selectedMetrics.reduce((p,c,i)=>{
|
|
209
|
+
p.push("metricFunction."+c+"('"+c+"',metricSample."+c+")")
|
|
210
|
+
return p
|
|
211
|
+
},[]).join(",")
|
|
212
|
+
const sendDataUpdate=selectedMetrics.reduce((p,c,i)=>{
|
|
213
|
+
p.push("metricFunctionUpdate."+c+"('"+c+"',metricSample."+c+")")
|
|
214
|
+
return p
|
|
215
|
+
},[]).join(",")
|
|
216
|
+
try{
|
|
217
|
+
logger.active&&logger.info({label:"sendData",function:sendData})
|
|
218
|
+
node.getSendData=eval("(metricSample)=>svgObject.calculatePositionVertically(["+sendData+"],60)")
|
|
219
|
+
const evalText="(metricSample)=>["+sendDataUpdate+"]"
|
|
220
|
+
logger.active&&logger.debug({ label: 'eval', node:{id: node.id, name: node.name},eval:evalText})
|
|
221
|
+
node.getSendDataUpdate=eval(evalText)
|
|
222
|
+
} catch(ex) {
|
|
223
|
+
node.getSendData=()=>{return [{action:"text",x:0 ,y:60 ,children:["failed: "+ex.message]}]}
|
|
224
|
+
logger.active&&logger.error({ label: 'eval', node:{id: node.id, name: node.name},error:ex.message,display:display,sendData:sendData,stack:ex.stack })
|
|
225
|
+
node.status({fill:"red",shape:"ring",text:"error: "+ex.message});
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
node.on('close', function (removed, done) {
|
|
229
|
+
nodes=[]
|
|
230
|
+
done()
|
|
231
|
+
})
|
|
232
|
+
nodes.push(node)
|
|
233
|
+
if(!runtimeTimer) {
|
|
234
|
+
runtimeStart(node)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const url='/' + logger.label.replace(" ","") + '/'
|
|
238
|
+
RED.httpAdmin.get(url + ':id/:action/', RED.auth.needsPermission(logger.label + '.write'), function (req, res) {
|
|
239
|
+
const node = RED.nodes.getNode(req.params.id);
|
|
240
|
+
if (node && node.type===logger.label) {
|
|
241
|
+
try {
|
|
242
|
+
switch (req.params.action) {
|
|
243
|
+
case "getBase":
|
|
244
|
+
res.status(200).json(node.getBaseSVG())
|
|
245
|
+
break
|
|
246
|
+
case "Start":
|
|
247
|
+
runtimeStart()
|
|
248
|
+
node.warn("Request to start monitor system");
|
|
249
|
+
break
|
|
250
|
+
case "Stop":
|
|
251
|
+
runtimeStop()
|
|
252
|
+
node.warn("Request to stop monitor system");
|
|
253
|
+
break
|
|
254
|
+
case "observeGC":
|
|
255
|
+
obs.observe({ entryTypes: ['gc'] });
|
|
256
|
+
node.warn("Request to observe gc");
|
|
257
|
+
break
|
|
258
|
+
case "observeDisconnect":
|
|
259
|
+
obs.disconnect();
|
|
260
|
+
node.warn("Request to observe disconnect")
|
|
261
|
+
break
|
|
262
|
+
case "trace gc on":
|
|
263
|
+
v8.setFlagsFromString('--trace-gc')
|
|
264
|
+
node.warn("Request to --trace-gc");
|
|
265
|
+
break
|
|
266
|
+
case "trace gc off":
|
|
267
|
+
v8.setFlagsFromString('--notrace-gc')
|
|
268
|
+
node.warn("Request to --notrace-gc");
|
|
269
|
+
break
|
|
270
|
+
default:
|
|
271
|
+
throw Error("invalid action "+req.params.action)
|
|
272
|
+
}
|
|
273
|
+
res.sendStatus(200);
|
|
274
|
+
} catch(ex) {
|
|
275
|
+
var reason1='Internal Server Error, monitor system failed '+ex.toString();
|
|
276
|
+
logger.error(reason1);
|
|
277
|
+
res.status(500).send(reason1);
|
|
278
|
+
logger.active&&logger.error(ex.stack);
|
|
279
|
+
}
|
|
280
|
+
} else {
|
|
281
|
+
var reason2="request to reset monitor flow failed for id:" +req.params.id;
|
|
282
|
+
node.error(reason1);
|
|
283
|
+
res.status(404).send(reason2);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
RED.httpAdmin.get(url + ':action/', RED.auth.needsPermission( logger.label + '.read'), function (req, res) {
|
|
287
|
+
try {
|
|
288
|
+
logger.active&&logger.info({ label: ' httpAdmin '+req.params.action})
|
|
289
|
+
switch (req.params.action) {
|
|
290
|
+
case "svgHTML":
|
|
291
|
+
if(svgHTMLCache) {
|
|
292
|
+
res.type('json').status(200).send(svgHTMLCache)
|
|
293
|
+
} else {
|
|
294
|
+
fs.readFile(path.join(__dirname,"svgHTML.js"), "utf8", (err, data) => {
|
|
295
|
+
if(err) {
|
|
296
|
+
logger.error("svgHTML get error: "+err)
|
|
297
|
+
res.status(500).send("NOT FOUND")
|
|
298
|
+
} else {
|
|
299
|
+
svgHTMLCache=data
|
|
300
|
+
res.type('json').status(200).send(data);
|
|
301
|
+
}
|
|
302
|
+
})
|
|
303
|
+
}
|
|
304
|
+
break
|
|
305
|
+
case "getDataSVG":
|
|
306
|
+
res.status(200).json(getJson())
|
|
307
|
+
break;
|
|
308
|
+
case "getDataUpdateSVG":
|
|
309
|
+
res.status(200).json(updateJson())
|
|
310
|
+
break;
|
|
311
|
+
default:
|
|
312
|
+
throw Error("invalid action "+req.params.action)
|
|
313
|
+
}
|
|
314
|
+
} catch(ex) {
|
|
315
|
+
var reason1='Internal Server Error, monitor system failed '+ex.toString();
|
|
316
|
+
logger.error(reason1);
|
|
317
|
+
res.status(500).send(reason1);
|
|
318
|
+
logger.active&&logger.error(ex.stack);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
RED.nodes.registerType(logger.label,nodeFunction);
|
|
322
|
+
};
|