@theotherwillembotha/node-red-statsd 0.4.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/LICENSE +15 -0
- package/README.md +0 -0
- package/build/GenerateNodes.d.ts +2 -0
- package/build/GenerateNodes.d.ts.map +1 -0
- package/build/GenerateNodes.js +13 -0
- package/build/Nodes.html +104 -0
- package/build/Nodes.js +36056 -0
- package/build/Plugins.html +11 -0
- package/build/Plugins.js +34360 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +18 -0
- package/build/runtime/NodeManagerRuntime.js +140 -0
- package/build/statsd/fragments/StatsdCounterFragment.html +34 -0
- package/build/statsd/fragments/StatsdGaugeFragment.html +34 -0
- package/build/statsd/fragments/StatsdTimerFragment.html +41 -0
- package/build/statsd/node/StatsdMetricsConfigNode.d.ts +15 -0
- package/build/statsd/node/StatsdMetricsConfigNode.d.ts.map +1 -0
- package/build/statsd/node/StatsdMetricsConfigNode.js +50 -0
- package/build/statsd/service/StatsdMetricsContainer.d.ts +86 -0
- package/build/statsd/service/StatsdMetricsContainer.d.ts.map +1 -0
- package/build/statsd/service/StatsdMetricsContainer.js +293 -0
- package/package.json +59 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StatsdMetricsContainer = exports.StatsdSummaryMetric = exports.StatsdHistogramMetric = exports.StatsdGaugeMetric = exports.StatsdCounterMetric = void 0;
|
|
4
|
+
const node_red_plugincore_1 = require("@theotherwillembotha/node-red-plugincore");
|
|
5
|
+
function getHotShots() { return require('hot-shots'); }
|
|
6
|
+
// ******************************************************* //
|
|
7
|
+
// Counter //
|
|
8
|
+
// ******************************************************* //
|
|
9
|
+
class StatsdCounterMetric extends node_red_plugincore_1.Metric {
|
|
10
|
+
constructor(client, metricKey, config) {
|
|
11
|
+
super(config);
|
|
12
|
+
this.value = 0;
|
|
13
|
+
this.subscribers = {};
|
|
14
|
+
this.client = client;
|
|
15
|
+
this.metricKey = metricKey;
|
|
16
|
+
}
|
|
17
|
+
inc() {
|
|
18
|
+
this.value++;
|
|
19
|
+
this.client.increment(this.metricKey);
|
|
20
|
+
const state = { value: this.value, labels: {} };
|
|
21
|
+
Object.values(this.subscribers).forEach(cb => cb(state));
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
24
|
+
reset() { this.value = 0; }
|
|
25
|
+
get() {
|
|
26
|
+
return Promise.resolve({ value: this.value, labels: {} });
|
|
27
|
+
}
|
|
28
|
+
subscribe(node, callback) {
|
|
29
|
+
this.subscribers[node.id()] = callback;
|
|
30
|
+
}
|
|
31
|
+
unsubscribe(node) {
|
|
32
|
+
delete this.subscribers[node.id()];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.StatsdCounterMetric = StatsdCounterMetric;
|
|
36
|
+
// ******************************************************* //
|
|
37
|
+
// Gauge //
|
|
38
|
+
// ******************************************************* //
|
|
39
|
+
class StatsdGaugeMetric extends node_red_plugincore_1.Metric {
|
|
40
|
+
constructor(client, metricKey, config) {
|
|
41
|
+
super(config);
|
|
42
|
+
this.value = 0;
|
|
43
|
+
this.subscribers = {};
|
|
44
|
+
this.client = client;
|
|
45
|
+
this.metricKey = metricKey;
|
|
46
|
+
}
|
|
47
|
+
inc() {
|
|
48
|
+
this.value++;
|
|
49
|
+
this.client.gauge(this.metricKey, this.value);
|
|
50
|
+
this.notifySubscribers();
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
dec() {
|
|
54
|
+
this.value--;
|
|
55
|
+
this.client.gauge(this.metricKey, this.value);
|
|
56
|
+
this.notifySubscribers();
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
set(value) {
|
|
60
|
+
this.value = value;
|
|
61
|
+
this.client.gauge(this.metricKey, this.value);
|
|
62
|
+
this.notifySubscribers();
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
reset() {
|
|
66
|
+
this.value = 0;
|
|
67
|
+
this.client.gauge(this.metricKey, 0);
|
|
68
|
+
}
|
|
69
|
+
get() { return { value: this.value }; }
|
|
70
|
+
subscribe(node, callback) {
|
|
71
|
+
this.subscribers[node.id()] = callback;
|
|
72
|
+
}
|
|
73
|
+
unsubscribe(node) {
|
|
74
|
+
delete this.subscribers[node.id()];
|
|
75
|
+
}
|
|
76
|
+
notifySubscribers() {
|
|
77
|
+
const state = { value: this.value };
|
|
78
|
+
Object.values(this.subscribers).forEach(cb => cb(state));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.StatsdGaugeMetric = StatsdGaugeMetric;
|
|
82
|
+
// ******************************************************* //
|
|
83
|
+
// Histogram //
|
|
84
|
+
// ******************************************************* //
|
|
85
|
+
class StatsdHistogramMetric extends node_red_plugincore_1.Metric {
|
|
86
|
+
constructor(client, metricKey, config) {
|
|
87
|
+
super(config);
|
|
88
|
+
this.sum = 0;
|
|
89
|
+
this.count = 0;
|
|
90
|
+
this.subscribers = {};
|
|
91
|
+
this.client = client;
|
|
92
|
+
this.metricKey = metricKey;
|
|
93
|
+
}
|
|
94
|
+
observe(value) {
|
|
95
|
+
this.sum += value;
|
|
96
|
+
this.count++;
|
|
97
|
+
this.client.timing(this.metricKey, value);
|
|
98
|
+
const state = { average: () => this.count > 0 ? this.sum / this.count : undefined };
|
|
99
|
+
Object.values(this.subscribers).forEach(cb => cb(state));
|
|
100
|
+
}
|
|
101
|
+
reset() {
|
|
102
|
+
this.sum = 0;
|
|
103
|
+
this.count = 0;
|
|
104
|
+
}
|
|
105
|
+
subscribe(node, callback) {
|
|
106
|
+
this.subscribers[node.id()] = callback;
|
|
107
|
+
}
|
|
108
|
+
unsubscribe(node) {
|
|
109
|
+
delete this.subscribers[node.id()];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
exports.StatsdHistogramMetric = StatsdHistogramMetric;
|
|
113
|
+
// ******************************************************* //
|
|
114
|
+
// Summary //
|
|
115
|
+
// ******************************************************* //
|
|
116
|
+
class StatsdSummaryMetric extends node_red_plugincore_1.Metric {
|
|
117
|
+
constructor(client, metricKey, config) {
|
|
118
|
+
super(config);
|
|
119
|
+
this.sum = 0;
|
|
120
|
+
this.count = 0;
|
|
121
|
+
this.subscribers = {};
|
|
122
|
+
this.client = client;
|
|
123
|
+
this.metricKey = metricKey;
|
|
124
|
+
}
|
|
125
|
+
observe(value) {
|
|
126
|
+
this.sum += value;
|
|
127
|
+
this.count++;
|
|
128
|
+
// StatsD has no native summary type; use timing as the closest equivalent
|
|
129
|
+
this.client.timing(this.metricKey, value);
|
|
130
|
+
const state = { average: () => this.count > 0 ? this.sum / this.count : undefined };
|
|
131
|
+
Object.values(this.subscribers).forEach(cb => cb(state));
|
|
132
|
+
}
|
|
133
|
+
reset() {
|
|
134
|
+
this.sum = 0;
|
|
135
|
+
this.count = 0;
|
|
136
|
+
}
|
|
137
|
+
subscribe(node, callback) {
|
|
138
|
+
this.subscribers[node.id()] = callback;
|
|
139
|
+
}
|
|
140
|
+
unsubscribe(node) {
|
|
141
|
+
delete this.subscribers[node.id()];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
exports.StatsdSummaryMetric = StatsdSummaryMetric;
|
|
145
|
+
class StatsdMetricsContainer extends node_red_plugincore_1.MetricsContainer {
|
|
146
|
+
constructor(config) {
|
|
147
|
+
super();
|
|
148
|
+
this.counters = {};
|
|
149
|
+
this.gauges = {};
|
|
150
|
+
this.histograms = {};
|
|
151
|
+
this.summaries = {};
|
|
152
|
+
this._config = config;
|
|
153
|
+
const StatsD = getHotShots().StatsD;
|
|
154
|
+
this._client = new StatsD({
|
|
155
|
+
host: config.host,
|
|
156
|
+
port: config.port,
|
|
157
|
+
prefix: config.prefix || undefined,
|
|
158
|
+
protocol: config.protocol || "udp",
|
|
159
|
+
errorHandler: (err) => {
|
|
160
|
+
console.error("StatsD client error:", String(err));
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
supports(capability) { return true; }
|
|
165
|
+
hasChanged(config) {
|
|
166
|
+
const c = config;
|
|
167
|
+
return !(this._config.host === c.host &&
|
|
168
|
+
this._config.port === c.port &&
|
|
169
|
+
this._config.prefix === c.prefix &&
|
|
170
|
+
this._config.protocol === c.protocol);
|
|
171
|
+
}
|
|
172
|
+
close() {
|
|
173
|
+
if (this._client) {
|
|
174
|
+
this._client.close();
|
|
175
|
+
this._client = null;
|
|
176
|
+
}
|
|
177
|
+
this.counters = {};
|
|
178
|
+
this.gauges = {};
|
|
179
|
+
this.histograms = {};
|
|
180
|
+
this.summaries = {};
|
|
181
|
+
}
|
|
182
|
+
client() { return this._client; }
|
|
183
|
+
counter(config) {
|
|
184
|
+
const id = `counter_${config.id}`;
|
|
185
|
+
if (!this.counters[id]) {
|
|
186
|
+
this.counters[id] = new StatsdCounterMetric(this._client, id, config);
|
|
187
|
+
}
|
|
188
|
+
return this.counters[id];
|
|
189
|
+
}
|
|
190
|
+
gauge(config) {
|
|
191
|
+
const id = `gauge_${config.id}`;
|
|
192
|
+
if (!this.gauges[id]) {
|
|
193
|
+
this.gauges[id] = new StatsdGaugeMetric(this._client, id, config);
|
|
194
|
+
}
|
|
195
|
+
return this.gauges[id];
|
|
196
|
+
}
|
|
197
|
+
histogram(config) {
|
|
198
|
+
const id = `histogram_${config.id}`;
|
|
199
|
+
if (!this.histograms[id]) {
|
|
200
|
+
this.histograms[id] = new StatsdHistogramMetric(this._client, id, config);
|
|
201
|
+
}
|
|
202
|
+
return this.histograms[id];
|
|
203
|
+
}
|
|
204
|
+
summary(config) {
|
|
205
|
+
const id = `summary_${config.id}`;
|
|
206
|
+
if (!this.summaries[id]) {
|
|
207
|
+
this.summaries[id] = new StatsdSummaryMetric(this._client, id, config);
|
|
208
|
+
}
|
|
209
|
+
return this.summaries[id];
|
|
210
|
+
}
|
|
211
|
+
createTimer(metricConfig, fragmentData) {
|
|
212
|
+
const pattern = fragmentData.metricKey || "{{metric}}.{{name}}";
|
|
213
|
+
const metricKey = StatsdMetricsContainer.resolvePattern(pattern, metricConfig);
|
|
214
|
+
const histogramConfig = {
|
|
215
|
+
...metricConfig,
|
|
216
|
+
type: "Histogram",
|
|
217
|
+
buckettype: node_red_plugincore_1.BucketType.default,
|
|
218
|
+
bucketconfig: {},
|
|
219
|
+
};
|
|
220
|
+
const id = `timer_${metricConfig.id}`;
|
|
221
|
+
if (!this.histograms[id]) {
|
|
222
|
+
this.histograms[id] = new StatsdHistogramMetric(this._client, metricKey, histogramConfig);
|
|
223
|
+
}
|
|
224
|
+
return this.histograms[id];
|
|
225
|
+
}
|
|
226
|
+
createCounter(metricConfig, fragmentData) {
|
|
227
|
+
const pattern = fragmentData.metricKey || "{{metric}}.{{name}}";
|
|
228
|
+
const metricKey = StatsdMetricsContainer.resolvePattern(pattern, metricConfig);
|
|
229
|
+
const counterConfig = {
|
|
230
|
+
...metricConfig,
|
|
231
|
+
type: "Counter",
|
|
232
|
+
};
|
|
233
|
+
const id = `counter_${metricConfig.id}`;
|
|
234
|
+
if (!this.counters[id]) {
|
|
235
|
+
this.counters[id] = new StatsdCounterMetric(this._client, metricKey, counterConfig);
|
|
236
|
+
}
|
|
237
|
+
return this.counters[id];
|
|
238
|
+
}
|
|
239
|
+
createGauge(metricConfig, fragmentData) {
|
|
240
|
+
const pattern = fragmentData.metricKey || "{{metric}}.{{name}}";
|
|
241
|
+
const metricKey = StatsdMetricsContainer.resolvePattern(pattern, metricConfig);
|
|
242
|
+
const gaugeConfig = {
|
|
243
|
+
...metricConfig,
|
|
244
|
+
type: "Gauge",
|
|
245
|
+
};
|
|
246
|
+
const id = `gauge_${metricConfig.id}`;
|
|
247
|
+
if (!this.gauges[id]) {
|
|
248
|
+
this.gauges[id] = new StatsdGaugeMetric(this._client, metricKey, gaugeConfig);
|
|
249
|
+
}
|
|
250
|
+
return this.gauges[id];
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Resolves a metric key pattern using flat MetricConfig fields.
|
|
254
|
+
* All labels are fixed at creation time — no dynamic resolution needed.
|
|
255
|
+
*/
|
|
256
|
+
static resolvePattern(pattern, config) {
|
|
257
|
+
return pattern
|
|
258
|
+
.replace(/\{\{type\}\}/g, config.type)
|
|
259
|
+
.replace(/\{\{id\}\}/g, config.id)
|
|
260
|
+
.replace(/\{\{metric\}\}/g, config.metric)
|
|
261
|
+
.replace(/\{\{flow\}\}/g, config.flow)
|
|
262
|
+
.replace(/\{\{name\}\}/g, config.name);
|
|
263
|
+
}
|
|
264
|
+
static registerFragments() {
|
|
265
|
+
const timerHtml = require('../fragments/StatsdTimerFragment.html');
|
|
266
|
+
node_red_plugincore_1.ConfigFragmentService.registerFragment({
|
|
267
|
+
section: 'TimerMetricConfig',
|
|
268
|
+
providerType: 'StatsdMetricsConfigNode',
|
|
269
|
+
html: timerHtml,
|
|
270
|
+
});
|
|
271
|
+
const counterHtml = require('../fragments/StatsdCounterFragment.html');
|
|
272
|
+
node_red_plugincore_1.ConfigFragmentService.registerFragment({
|
|
273
|
+
section: 'CounterMetricConfig',
|
|
274
|
+
providerType: 'StatsdMetricsConfigNode',
|
|
275
|
+
html: counterHtml,
|
|
276
|
+
});
|
|
277
|
+
const gaugeHtml = require('../fragments/StatsdGaugeFragment.html');
|
|
278
|
+
node_red_plugincore_1.ConfigFragmentService.registerFragment({
|
|
279
|
+
section: 'GaugeMetricConfig',
|
|
280
|
+
providerType: 'StatsdMetricsConfigNode',
|
|
281
|
+
html: gaugeHtml,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
exports.StatsdMetricsContainer = StatsdMetricsContainer;
|
|
286
|
+
// Register at module load time. Wrapped in try-catch because during
|
|
287
|
+
// generate-nodes (pre-esbuild), the .html require fails.
|
|
288
|
+
try {
|
|
289
|
+
StatsdMetricsContainer.registerFragments();
|
|
290
|
+
}
|
|
291
|
+
catch (_e) {
|
|
292
|
+
// Expected during build time
|
|
293
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@theotherwillembotha/node-red-statsd",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "StatsD metrics provider plugin for node-red-plugincore.",
|
|
5
|
+
"author": "Willem Botha (@theotherwillembotha)",
|
|
6
|
+
"license": "ISC",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"node-red",
|
|
9
|
+
"metrics",
|
|
10
|
+
"statsd"
|
|
11
|
+
],
|
|
12
|
+
"main": "./build/index.js",
|
|
13
|
+
"exports": "./build/index.js",
|
|
14
|
+
"types": "./build/index.d.ts",
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
17
|
+
"prepare": "npm run build",
|
|
18
|
+
"clean": "rm -rf ./build && rm -rf ./resources",
|
|
19
|
+
"build": "npm run clean && tsc && npm run copy-fragments && npm run generate-nodes && npm run bundle && npm run copy-files",
|
|
20
|
+
"generate-nodes": "node build/GenerateNodes.js",
|
|
21
|
+
"copy-fragments": "find src -name '*.html' -path '*/fragments/*' | while read f; do mkdir -p \"build/$(dirname \"${f#src/}\")\" && cp \"$f\" \"build/${f#src/}\"; done",
|
|
22
|
+
"bundle": "node esbuild.js",
|
|
23
|
+
"copy-files": "cp -r ./icons ./build 2>/dev/null || true && mkdir -p ./resources && cp -r ./build/resources/. ./resources/ 2>/dev/null || true"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/theotherwillembotha/nodered_statsd.git"
|
|
28
|
+
},
|
|
29
|
+
"files": ["build/", "icons/", "documentation/", "LICENSE", "README.md"],
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18",
|
|
35
|
+
"nodered": ">=4.0.0"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@theotherwillembotha/node-red-plugincore": "^0.4.2"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@theotherwillembotha/node-red-plugincore": "../nodered_plugincore",
|
|
42
|
+
"@types/deep-equal": "^1.0.4",
|
|
43
|
+
"@types/node": "^26.1.1",
|
|
44
|
+
"@types/node-red": "~1.3.5",
|
|
45
|
+
"deep-equal": "^2.2.3",
|
|
46
|
+
"esbuild": "^0.24.0",
|
|
47
|
+
"hot-shots": "^10.2.1",
|
|
48
|
+
"typescript": "^5.8.3"
|
|
49
|
+
},
|
|
50
|
+
"node-red": {
|
|
51
|
+
"version": ">=4.0.0",
|
|
52
|
+
"nodes": {
|
|
53
|
+
"statsd": "./build/Nodes.js"
|
|
54
|
+
},
|
|
55
|
+
"plugins": {
|
|
56
|
+
"statsd": "./build/Plugins.js"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|