@arcblock/pm2-prom-module 2.6.1
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/.prettierrc.js +8 -0
- package/README.md +220 -0
- package/core/app.js +236 -0
- package/core/pm2.js +311 -0
- package/index.js +106 -0
- package/metrics/app.js +193 -0
- package/metrics/index.js +234 -0
- package/metrics/prom/histogram.js +62 -0
- package/metrics/prom/summary.js +51 -0
- package/package.json +71 -0
- package/types.js +2 -0
- package/utils/cpu.js +46 -0
- package/utils/docker.js +150 -0
- package/utils/index.js +7 -0
- package/utils/logger.js +44 -0
package/core/pm2.js
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.startPm2Connect = void 0;
|
|
16
|
+
const pm2_1 = __importDefault(require("pm2"));
|
|
17
|
+
const app_1 = require("./app");
|
|
18
|
+
const utils_1 = require("../utils");
|
|
19
|
+
const cpu_1 = require("../utils/cpu");
|
|
20
|
+
const metrics_1 = require("../metrics");
|
|
21
|
+
const app_2 = require("../metrics/app");
|
|
22
|
+
const logger_1 = require("../utils/logger");
|
|
23
|
+
const docker_1 = require("../utils/docker");
|
|
24
|
+
const WORKER_CHECK_INTERVAL = 1000;
|
|
25
|
+
const SHOW_STAT_INTERVAL = 10000;
|
|
26
|
+
const APPS = {};
|
|
27
|
+
const isMonitoringApp = (app) => {
|
|
28
|
+
const pm2_env = app.pm2_env;
|
|
29
|
+
if (pm2_env.axm_options.isModule ||
|
|
30
|
+
!app.name ||
|
|
31
|
+
app.pm_id === undefined // pm_id might be zero
|
|
32
|
+
) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
};
|
|
37
|
+
const updateAppPidsData = (workingApp, pidData) => {
|
|
38
|
+
workingApp.updatePid({
|
|
39
|
+
id: pidData.id,
|
|
40
|
+
memory: pidData.memory,
|
|
41
|
+
cpu: pidData.cpu || 0,
|
|
42
|
+
pmId: pidData.pmId,
|
|
43
|
+
restartCount: pidData.restartCount,
|
|
44
|
+
createdAt: pidData.createdAt,
|
|
45
|
+
metrics: pidData.metrics,
|
|
46
|
+
status: pidData.status,
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
const detectActiveApps = () => {
|
|
50
|
+
const logger = (0, logger_1.getLogger)();
|
|
51
|
+
pm2_1.default.list((err, apps) => {
|
|
52
|
+
if (err)
|
|
53
|
+
return console.error(err.stack || err);
|
|
54
|
+
const pidsMonit = {};
|
|
55
|
+
const mapAppPids = {};
|
|
56
|
+
const activePM2Ids = new Set();
|
|
57
|
+
apps.forEach((appInstance) => {
|
|
58
|
+
var _a;
|
|
59
|
+
const pm2_env = appInstance.pm2_env;
|
|
60
|
+
const appName = appInstance.name;
|
|
61
|
+
if (!isMonitoringApp(appInstance) || !appName || appInstance.pm_id === undefined) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// Fill all apps pids
|
|
65
|
+
if (!mapAppPids[appName]) {
|
|
66
|
+
mapAppPids[appName] = {
|
|
67
|
+
pids: [],
|
|
68
|
+
restartsSum: 0,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
mapAppPids[appName].restartsSum =
|
|
72
|
+
mapAppPids[appName].restartsSum + Number(pm2_env.restart_time || 0);
|
|
73
|
+
// Get the last app instance status
|
|
74
|
+
mapAppPids[appName].status = (_a = appInstance.pm2_env) === null || _a === void 0 ? void 0 : _a.status;
|
|
75
|
+
if (appInstance.pid && appInstance.pm_id !== undefined) {
|
|
76
|
+
mapAppPids[appName].pids.push(appInstance.pid);
|
|
77
|
+
// Fill active pm2 apps id to collect internal statistic
|
|
78
|
+
if (pm2_env.status === 'online') {
|
|
79
|
+
activePM2Ids.add(appInstance.pm_id);
|
|
80
|
+
}
|
|
81
|
+
// Fill monitoring data
|
|
82
|
+
pidsMonit[appInstance.pid] = {
|
|
83
|
+
cpu: 0,
|
|
84
|
+
memory: 0,
|
|
85
|
+
pmId: appInstance.pm_id,
|
|
86
|
+
id: appInstance.pid,
|
|
87
|
+
restartCount: pm2_env.restart_time || 0,
|
|
88
|
+
createdAt: pm2_env.created_at || 0,
|
|
89
|
+
metrics: pm2_env.axm_monitor,
|
|
90
|
+
status: pm2_env.status,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
Object.keys(APPS).forEach((appName) => {
|
|
95
|
+
const processingApp = mapAppPids[appName];
|
|
96
|
+
// Filters apps which do not have active pids
|
|
97
|
+
if (!processingApp) {
|
|
98
|
+
logger.debug(`Delete ${appName} because it not longer exists`);
|
|
99
|
+
const workingApp = APPS[appName];
|
|
100
|
+
const instances = workingApp.getActivePm2Ids();
|
|
101
|
+
// Clear app metrics
|
|
102
|
+
(0, app_2.deleteAppMetrics)(appName);
|
|
103
|
+
// Clear all metrics in prom-client because an app is not exists anymore
|
|
104
|
+
(0, metrics_1.deletePromAppMetrics)(appName, instances);
|
|
105
|
+
delete APPS[appName];
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
const workingApp = APPS[appName];
|
|
109
|
+
if (workingApp) {
|
|
110
|
+
const activePids = processingApp.pids;
|
|
111
|
+
const removedPids = workingApp.removeNotActivePids(activePids);
|
|
112
|
+
if (removedPids.length) {
|
|
113
|
+
const removedIntances = removedPids.map((entry) => entry.pmId);
|
|
114
|
+
logger.debug(`App ${appName} clear metrics. Removed PIDs ${removedIntances.toString()}`);
|
|
115
|
+
(0, metrics_1.deletePromAppInstancesMetrics)(appName, removedIntances);
|
|
116
|
+
if (!activePids.length) {
|
|
117
|
+
// Delete app metrics because it does not have active PIDs anymore
|
|
118
|
+
logger.debug(`App ${appName} does not have active PIDs. Clear app metrics`);
|
|
119
|
+
(0, app_2.deleteAppMetrics)(appName);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const pidsRestartsSum = workingApp
|
|
123
|
+
.getRestartCount()
|
|
124
|
+
.reduce((accum, item) => accum + item.value, 0);
|
|
125
|
+
if (processingApp.restartsSum > pidsRestartsSum) {
|
|
126
|
+
// Reset metrics when active restart app bigger then active app
|
|
127
|
+
// This logic exist to prevent autoscaling problems if we use only !==
|
|
128
|
+
logger.debug(`App ${appName} has been restarted. Clear app metrics`);
|
|
129
|
+
(0, app_2.deleteAppMetrics)(appName);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
// Create instances for new apps
|
|
135
|
+
for (const [appName, entry] of Object.entries(mapAppPids)) {
|
|
136
|
+
if (!APPS[appName]) {
|
|
137
|
+
APPS[appName] = new app_1.App(appName);
|
|
138
|
+
}
|
|
139
|
+
const workingApp = APPS[appName];
|
|
140
|
+
if (workingApp) {
|
|
141
|
+
// Update status
|
|
142
|
+
workingApp.updateStatus(entry.status);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Collect statistic from apps. Do it after all APPS created
|
|
146
|
+
if (activePM2Ids.size > 0) {
|
|
147
|
+
// logger.debug(`Collect app metrics from PIDs ${Array.from(activePM2Ids)}`);
|
|
148
|
+
sendCollectStaticticBusEvent(Array.from(activePM2Ids));
|
|
149
|
+
}
|
|
150
|
+
// Update metric with available apps
|
|
151
|
+
metrics_1.metricAvailableApps === null || metrics_1.metricAvailableApps === void 0 ? void 0 : metrics_1.metricAvailableApps.set(Object.keys(APPS).length);
|
|
152
|
+
// Get all pids to monit
|
|
153
|
+
const pids = Object.keys(pidsMonit);
|
|
154
|
+
// Get real pids data.
|
|
155
|
+
// !ATTENTION! Can not use PM2 app.monit because of incorrect values of CPU usage
|
|
156
|
+
(0, cpu_1.getPidsUsage)(pids)
|
|
157
|
+
.then((stats) => __awaiter(void 0, void 0, void 0, function* () {
|
|
158
|
+
// Fill data for all pids
|
|
159
|
+
if (stats && Object.keys(stats).length) {
|
|
160
|
+
for (const [pid, stat] of Object.entries(stats)) {
|
|
161
|
+
const pidId = Number(pid);
|
|
162
|
+
if (pidId && pidsMonit[pidId]) {
|
|
163
|
+
pidsMonit[pidId].cpu = Math.round(stat.cpu * 10) / 10;
|
|
164
|
+
pidsMonit[pidId].memory = stat.memory;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Get docker stats
|
|
169
|
+
// @ts-expect-error
|
|
170
|
+
const dockerApps = apps.filter((app) => { var _a; return (_a = app.pm2_env) === null || _a === void 0 ? void 0 : _a.BLOCKLET_DOCKER_NAME; });
|
|
171
|
+
yield (0, docker_1.getDockerStats)(
|
|
172
|
+
// @ts-expect-error
|
|
173
|
+
dockerApps.map((x) => { var _a; return (_a = x.pm2_env) === null || _a === void 0 ? void 0 : _a.BLOCKLET_DOCKER_NAME; })).then((stats) => {
|
|
174
|
+
stats.map((stat, i) => {
|
|
175
|
+
const entry = mapAppPids[dockerApps[i].name];
|
|
176
|
+
if (entry) {
|
|
177
|
+
entry.pids.forEach((pid) => {
|
|
178
|
+
pidsMonit[pid].cpu = stat.cpuUsage;
|
|
179
|
+
pidsMonit[pid].memory = stat.memoryUsage;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
for (const [appName, entry] of Object.entries(mapAppPids)) {
|
|
185
|
+
const workingApp = APPS[appName];
|
|
186
|
+
if (workingApp) {
|
|
187
|
+
// Update pids data
|
|
188
|
+
entry.pids.forEach((pidId) => {
|
|
189
|
+
const monit = pidsMonit[pidId];
|
|
190
|
+
if (monit) {
|
|
191
|
+
updateAppPidsData(workingApp, monit);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
// Collect metrics
|
|
195
|
+
processWorkingApp(workingApp);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}))
|
|
199
|
+
.catch((err) => {
|
|
200
|
+
console.error(err.stack || err);
|
|
201
|
+
});
|
|
202
|
+
// get docker stats
|
|
203
|
+
// @ts-ignore
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
const startPm2Connect = (conf) => {
|
|
207
|
+
const logger = (0, logger_1.getLogger)();
|
|
208
|
+
pm2_1.default.connect((err) => {
|
|
209
|
+
var _a;
|
|
210
|
+
if (err)
|
|
211
|
+
return console.error(err.stack || err);
|
|
212
|
+
const additionalMetrics = app_1.PM2_METRICS.map((entry) => {
|
|
213
|
+
return {
|
|
214
|
+
key: (0, utils_1.toUndescore)(entry.name),
|
|
215
|
+
description: `${entry.name}. Unit "${entry.unit}"`,
|
|
216
|
+
};
|
|
217
|
+
});
|
|
218
|
+
if (additionalMetrics.length) {
|
|
219
|
+
(0, metrics_1.initDynamicGaugeMetricClients)(additionalMetrics);
|
|
220
|
+
}
|
|
221
|
+
detectActiveApps();
|
|
222
|
+
// Collect statistic from running apps
|
|
223
|
+
pm2_1.default.launchBus((err, bus) => {
|
|
224
|
+
if (err)
|
|
225
|
+
return console.error(err.stack || err);
|
|
226
|
+
logger.debug('Start bus listener');
|
|
227
|
+
bus.on('process:msg', (packet) => {
|
|
228
|
+
if (packet.process &&
|
|
229
|
+
packet.raw &&
|
|
230
|
+
packet.raw.topic === 'pm2-prom-module:metrics' &&
|
|
231
|
+
packet.raw.data) {
|
|
232
|
+
const { name, pm_id } = packet.process;
|
|
233
|
+
/*logger.debug(
|
|
234
|
+
`Got message from app=${name} and pid=${pm_id}. Message=${JSON.stringify(
|
|
235
|
+
packet.raw.data
|
|
236
|
+
)}`
|
|
237
|
+
);*/
|
|
238
|
+
if (name && APPS[name] && packet.raw.data.metrics) {
|
|
239
|
+
(0, app_2.processAppMetrics)(conf, {
|
|
240
|
+
pmId: pm_id,
|
|
241
|
+
appName: name,
|
|
242
|
+
appResponse: packet.raw.data,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
// Start timer to update available apps
|
|
249
|
+
setInterval(() => {
|
|
250
|
+
detectActiveApps();
|
|
251
|
+
}, (_a = conf.app_check_interval) !== null && _a !== void 0 ? _a : WORKER_CHECK_INTERVAL);
|
|
252
|
+
if (conf.debug) {
|
|
253
|
+
setInterval(() => {
|
|
254
|
+
if (Object.keys(APPS).length) {
|
|
255
|
+
for (const [, app] of Object.entries(APPS)) {
|
|
256
|
+
const cpuValues = app.getCpuThreshold().map((entry) => entry.value);
|
|
257
|
+
const memory = Math.round(app.getTotalUsedMemory() / 1024 / 1024);
|
|
258
|
+
const CPU = cpuValues.length ? cpuValues.toString() : '0';
|
|
259
|
+
(0, logger_1.getLogger)().debug(`App "${app.getName()}" has ${app.getActiveWorkersCount()} worker(s). CPU: ${CPU}, Memory: ${memory}MB`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
(0, logger_1.getLogger)().debug(`No apps available`);
|
|
264
|
+
}
|
|
265
|
+
}, SHOW_STAT_INTERVAL);
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
};
|
|
269
|
+
exports.startPm2Connect = startPm2Connect;
|
|
270
|
+
function processWorkingApp(workingApp) {
|
|
271
|
+
const labels = { app: workingApp.getName() };
|
|
272
|
+
metrics_1.metricAppInstances === null || metrics_1.metricAppInstances === void 0 ? void 0 : metrics_1.metricAppInstances.set(labels, workingApp.getActiveWorkersCount());
|
|
273
|
+
metrics_1.metricAppAverageMemory === null || metrics_1.metricAppAverageMemory === void 0 ? void 0 : metrics_1.metricAppAverageMemory.set(labels, workingApp.getAverageUsedMemory());
|
|
274
|
+
metrics_1.metricAppTotalMemory === null || metrics_1.metricAppTotalMemory === void 0 ? void 0 : metrics_1.metricAppTotalMemory.set(labels, workingApp.getTotalUsedMemory());
|
|
275
|
+
metrics_1.metricAppAverageCpu === null || metrics_1.metricAppAverageCpu === void 0 ? void 0 : metrics_1.metricAppAverageCpu.set(labels, workingApp.getAverageCpu());
|
|
276
|
+
metrics_1.metricAppUptime === null || metrics_1.metricAppUptime === void 0 ? void 0 : metrics_1.metricAppUptime.set(labels, workingApp.getUptime());
|
|
277
|
+
metrics_1.metricAppStatus === null || metrics_1.metricAppStatus === void 0 ? void 0 : metrics_1.metricAppStatus.set(labels, workingApp.getStatus());
|
|
278
|
+
workingApp.getCurrentPidsCpu().forEach((entry) => {
|
|
279
|
+
metrics_1.metricAppPidsCpuLast === null || metrics_1.metricAppPidsCpuLast === void 0 ? void 0 : metrics_1.metricAppPidsCpuLast.set(Object.assign(Object.assign({}, labels), { instance: entry.pmId }), entry.value);
|
|
280
|
+
});
|
|
281
|
+
workingApp.getCpuThreshold().forEach((entry) => {
|
|
282
|
+
metrics_1.metricAppPidsCpuThreshold === null || metrics_1.metricAppPidsCpuThreshold === void 0 ? void 0 : metrics_1.metricAppPidsCpuThreshold.set(Object.assign(Object.assign({}, labels), { instance: entry.pmId }), entry.value);
|
|
283
|
+
});
|
|
284
|
+
workingApp.getCurrentPidsMemory().forEach((entry) => {
|
|
285
|
+
metrics_1.metricAppPidsMemory === null || metrics_1.metricAppPidsMemory === void 0 ? void 0 : metrics_1.metricAppPidsMemory.set(Object.assign(Object.assign({}, labels), { instance: entry.pmId }), entry.value);
|
|
286
|
+
});
|
|
287
|
+
workingApp.getRestartCount().forEach((entry) => {
|
|
288
|
+
metrics_1.metricAppRestartCount === null || metrics_1.metricAppRestartCount === void 0 ? void 0 : metrics_1.metricAppRestartCount.set(Object.assign(Object.assign({}, labels), { instance: entry.pmId }), entry.value);
|
|
289
|
+
});
|
|
290
|
+
workingApp.getPidPm2Metrics().forEach((entry) => {
|
|
291
|
+
Object.keys(entry.metrics).forEach((metricKey) => {
|
|
292
|
+
if (metrics_1.dynamicGaugeMetricClients[metricKey]) {
|
|
293
|
+
metrics_1.dynamicGaugeMetricClients[metricKey].set(Object.assign(Object.assign({}, labels), { instance: entry.pmId }), entry.metrics[metricKey]);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
function sendCollectStaticticBusEvent(pm2Ids) {
|
|
299
|
+
// Request available metrics from all running apps
|
|
300
|
+
pm2Ids.forEach((pm2id) => {
|
|
301
|
+
pm2_1.default.sendDataToProcessId(pm2id, {
|
|
302
|
+
topic: 'pm2-prom-module:collect',
|
|
303
|
+
data: {},
|
|
304
|
+
// Required fields by pm2 but we do not use them
|
|
305
|
+
id: pm2id,
|
|
306
|
+
}, (err) => {
|
|
307
|
+
if (err)
|
|
308
|
+
return console.error(`pm2-prom-module: sendDataToProcessId ${err.stack || err}`);
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
const pmx_1 = __importDefault(require("pmx"));
|
|
17
|
+
const http_1 = require("http");
|
|
18
|
+
const net_1 = __importDefault(require("net"));
|
|
19
|
+
const fs_1 = __importDefault(require("fs"));
|
|
20
|
+
const pm2_1 = require("./core/pm2");
|
|
21
|
+
const logger_1 = require("./utils/logger");
|
|
22
|
+
const metrics_1 = require("./metrics");
|
|
23
|
+
const DEFAULT_PREFIX = 'pm2';
|
|
24
|
+
const startPromServer = (prefix, moduleConfig) => {
|
|
25
|
+
(0, metrics_1.initMetrics)(prefix);
|
|
26
|
+
const serviceName = moduleConfig.service_name;
|
|
27
|
+
const port = Number(moduleConfig.port);
|
|
28
|
+
const hostname = moduleConfig.hostname;
|
|
29
|
+
const unixSocketPath = moduleConfig.unix_socket_path;
|
|
30
|
+
const promServer = (0, http_1.createServer)((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
31
|
+
const mergedRegistry = (0, metrics_1.combineAllRegistries)(Boolean(moduleConfig.aggregate_app_metrics));
|
|
32
|
+
mergedRegistry.setDefaultLabels({ serviceName });
|
|
33
|
+
res.setHeader('Content-Type', mergedRegistry.contentType);
|
|
34
|
+
res.end(yield mergedRegistry.metrics());
|
|
35
|
+
return;
|
|
36
|
+
}));
|
|
37
|
+
const listenCallback = () => {
|
|
38
|
+
const listenValue = promServer.address();
|
|
39
|
+
let listenString = '';
|
|
40
|
+
if (typeof listenValue === 'string') {
|
|
41
|
+
listenString = listenValue;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
listenString = `${listenValue === null || listenValue === void 0 ? void 0 : listenValue.address}:${listenValue === null || listenValue === void 0 ? void 0 : listenValue.port}`;
|
|
45
|
+
}
|
|
46
|
+
console.log(`Metrics server is available on ${listenString}`);
|
|
47
|
+
};
|
|
48
|
+
if (unixSocketPath) {
|
|
49
|
+
promServer.on('error', function (promServerError) {
|
|
50
|
+
if (promServerError.code == 'EADDRINUSE') {
|
|
51
|
+
console.log(`Listen error: "${promServerError.message}". Try to remove socket...`);
|
|
52
|
+
const clientSocket = new net_1.default.Socket();
|
|
53
|
+
clientSocket.on('error', function (clientSocketError) {
|
|
54
|
+
if (clientSocketError.code == 'ECONNREFUSED') {
|
|
55
|
+
console.log(`Remove old socket ${unixSocketPath}`);
|
|
56
|
+
fs_1.default.unlinkSync(unixSocketPath);
|
|
57
|
+
promServer.listen(unixSocketPath);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
clientSocket.connect({ path: unixSocketPath }, function () {
|
|
61
|
+
console.log('Server running, giving up...');
|
|
62
|
+
process.exit();
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
promServer.listen(unixSocketPath, listenCallback);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
promServer.listen(port, hostname, listenCallback);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
pmx_1.default.initModule({
|
|
73
|
+
widget: {
|
|
74
|
+
el: {
|
|
75
|
+
probes: true,
|
|
76
|
+
actions: true,
|
|
77
|
+
},
|
|
78
|
+
block: {
|
|
79
|
+
actions: false,
|
|
80
|
+
issues: true,
|
|
81
|
+
meta: true,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
}, function (err, conf) {
|
|
85
|
+
var _a, _b;
|
|
86
|
+
if (err)
|
|
87
|
+
return console.error(err.stack || err);
|
|
88
|
+
const moduleConfig = conf.module_conf;
|
|
89
|
+
(0, logger_1.initLogger)({ isDebug: moduleConfig.debug });
|
|
90
|
+
(0, pm2_1.startPm2Connect)(moduleConfig);
|
|
91
|
+
startPromServer((_a = moduleConfig.prefix) !== null && _a !== void 0 ? _a : DEFAULT_PREFIX, moduleConfig);
|
|
92
|
+
pmx_1.default.configureModule({
|
|
93
|
+
human_info: [
|
|
94
|
+
['Status', 'Module enabled'],
|
|
95
|
+
['Debug', moduleConfig.debug ? 'Enabled' : 'Disabled'],
|
|
96
|
+
[
|
|
97
|
+
'Aggregate apps metrics',
|
|
98
|
+
moduleConfig.aggregate_app_metrics ? 'Enabled' : 'Disabled',
|
|
99
|
+
],
|
|
100
|
+
['Port', moduleConfig.port],
|
|
101
|
+
['Service name', moduleConfig.service_name ? moduleConfig.service_name : `N/A`],
|
|
102
|
+
['Check interval', `${moduleConfig.app_check_interval} ms`],
|
|
103
|
+
['Prefix', (_b = moduleConfig.prefix) !== null && _b !== void 0 ? _b : DEFAULT_PREFIX],
|
|
104
|
+
],
|
|
105
|
+
});
|
|
106
|
+
});
|
package/metrics/app.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getAppRegistry = exports.processAppMetrics = exports.deleteAppMetrics = void 0;
|
|
7
|
+
const prom_client_1 = __importDefault(require("prom-client"));
|
|
8
|
+
const logger_1 = require("../utils/logger");
|
|
9
|
+
const histogram_1 = require("./prom/histogram");
|
|
10
|
+
const summary_1 = require("./prom/summary");
|
|
11
|
+
const dynamicAppMetrics = {};
|
|
12
|
+
const DEFAULT_LABELS = ['app', 'instance'];
|
|
13
|
+
const parseLabels = (values) => {
|
|
14
|
+
const labels = new Set();
|
|
15
|
+
values.forEach((entry) => {
|
|
16
|
+
Object.keys(entry.labels).forEach((label) => {
|
|
17
|
+
labels.add(String(label));
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
return Array.from(labels);
|
|
21
|
+
};
|
|
22
|
+
const createMetricByType = (metric, labels) => {
|
|
23
|
+
switch (metric.type) {
|
|
24
|
+
case "counter" /* MetricType.Counter */: {
|
|
25
|
+
const metricEntry = new prom_client_1.default.Counter({
|
|
26
|
+
name: metric.name,
|
|
27
|
+
help: metric.help,
|
|
28
|
+
aggregator: metric.aggregator,
|
|
29
|
+
labelNames: [...DEFAULT_LABELS, ...labels],
|
|
30
|
+
registers: [],
|
|
31
|
+
});
|
|
32
|
+
return metricEntry;
|
|
33
|
+
}
|
|
34
|
+
case "gauge" /* MetricType.Gauge */: {
|
|
35
|
+
const metricEntry = new prom_client_1.default.Gauge({
|
|
36
|
+
name: metric.name,
|
|
37
|
+
help: metric.help,
|
|
38
|
+
aggregator: metric.aggregator,
|
|
39
|
+
labelNames: [...DEFAULT_LABELS, ...labels],
|
|
40
|
+
registers: [],
|
|
41
|
+
});
|
|
42
|
+
return metricEntry;
|
|
43
|
+
}
|
|
44
|
+
case "histogram" /* MetricType.Histogram */: {
|
|
45
|
+
const filteredMetrics = labels.filter((entry) => entry !== 'le');
|
|
46
|
+
const metricEntry = new histogram_1.IHistogram({
|
|
47
|
+
name: metric.name,
|
|
48
|
+
help: metric.help,
|
|
49
|
+
aggregator: metric.aggregator,
|
|
50
|
+
labelNames: [...DEFAULT_LABELS, ...filteredMetrics],
|
|
51
|
+
registers: [],
|
|
52
|
+
});
|
|
53
|
+
return metricEntry;
|
|
54
|
+
}
|
|
55
|
+
case "summary" /* MetricType.Summary */: {
|
|
56
|
+
const filteredMetrics = labels.filter((entry) => entry !== 'quantile');
|
|
57
|
+
const metricEntry = new summary_1.ISummary({
|
|
58
|
+
name: metric.name,
|
|
59
|
+
help: metric.help,
|
|
60
|
+
aggregator: metric.aggregator,
|
|
61
|
+
labelNames: [...DEFAULT_LABELS, ...filteredMetrics],
|
|
62
|
+
registers: [],
|
|
63
|
+
});
|
|
64
|
+
return metricEntry;
|
|
65
|
+
}
|
|
66
|
+
default:
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const createRegistryMetrics = (registry) => {
|
|
71
|
+
const logger = (0, logger_1.getLogger)();
|
|
72
|
+
const metrics = {};
|
|
73
|
+
for (const [appName, appEntry] of Object.entries(dynamicAppMetrics)) {
|
|
74
|
+
for (const [metricName, pidEntry] of Object.entries(appEntry)) {
|
|
75
|
+
for (const [pm2id, metric] of Object.entries(pidEntry)) {
|
|
76
|
+
if (!metrics[metricName]) {
|
|
77
|
+
const parsedLabels = parseLabels(metric.values);
|
|
78
|
+
const newMetricStore = createMetricByType(metric, parsedLabels);
|
|
79
|
+
if (newMetricStore) {
|
|
80
|
+
metrics[metricName] = newMetricStore;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const createdMetric = metrics[metricName];
|
|
84
|
+
if (!createdMetric) {
|
|
85
|
+
logger.error(`Unsupported metric type ${metric.type} for ${metricName}`);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
// Register metric
|
|
89
|
+
registry.registerMetric(createdMetric);
|
|
90
|
+
const defaultLabels = {
|
|
91
|
+
app: appName,
|
|
92
|
+
instance: pm2id,
|
|
93
|
+
};
|
|
94
|
+
// Fill data
|
|
95
|
+
switch (metric.type) {
|
|
96
|
+
case "counter" /* MetricType.Counter */: {
|
|
97
|
+
metric.values.forEach((entry) => {
|
|
98
|
+
try {
|
|
99
|
+
createdMetric.inc(Object.assign(Object.assign({}, entry.labels), defaultLabels), entry.value);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
logger.error(error);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case "gauge" /* MetricType.Gauge */: {
|
|
108
|
+
metric.values.forEach((entry) => {
|
|
109
|
+
try {
|
|
110
|
+
createdMetric.inc(Object.assign(Object.assign({}, entry.labels), defaultLabels), entry.value);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
logger.error(error);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case "histogram" /* MetricType.Histogram */: {
|
|
119
|
+
createdMetric.setValues(defaultLabels, metric.values);
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
case "summary" /* MetricType.Summary */: {
|
|
123
|
+
createdMetric.setValues(defaultLabels, metric.values);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
default:
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
const getAggregatedMetrics = () => {
|
|
135
|
+
const metrics = [];
|
|
136
|
+
for (const [appName, appEntry] of Object.entries(dynamicAppMetrics)) {
|
|
137
|
+
for (const [_metricName, pidEntry] of Object.entries(appEntry)) {
|
|
138
|
+
const pidMetrics = [];
|
|
139
|
+
for (const [_pm2id, metric] of Object.entries(pidEntry)) {
|
|
140
|
+
const metricWithApp = Object.assign({}, metric);
|
|
141
|
+
metricWithApp.values = metricWithApp.values.map((entry) => {
|
|
142
|
+
entry.labels['app'] = appName;
|
|
143
|
+
return entry;
|
|
144
|
+
});
|
|
145
|
+
pidMetrics.push(metricWithApp);
|
|
146
|
+
}
|
|
147
|
+
metrics.push(pidMetrics);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return prom_client_1.default.AggregatorRegistry.aggregate(metrics);
|
|
151
|
+
};
|
|
152
|
+
const deleteAppMetrics = (appName) => {
|
|
153
|
+
const logger = (0, logger_1.getLogger)();
|
|
154
|
+
if (dynamicAppMetrics[appName]) {
|
|
155
|
+
logger.debug(`Remove AppMetrics for app ${appName}`);
|
|
156
|
+
delete dynamicAppMetrics[appName];
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
exports.deleteAppMetrics = deleteAppMetrics;
|
|
160
|
+
const processAppMetrics = (_config, data) => {
|
|
161
|
+
if (!Array.isArray(data.appResponse.metrics)) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
data.appResponse.metrics.forEach((entry) => {
|
|
165
|
+
if (Array.isArray(entry.values) && entry.values.length) {
|
|
166
|
+
const metricName = entry.name;
|
|
167
|
+
if (!dynamicAppMetrics[data.appName]) {
|
|
168
|
+
dynamicAppMetrics[data.appName] = {};
|
|
169
|
+
}
|
|
170
|
+
const appKey = dynamicAppMetrics[data.appName][metricName];
|
|
171
|
+
if (!appKey) {
|
|
172
|
+
dynamicAppMetrics[data.appName][metricName] = {};
|
|
173
|
+
}
|
|
174
|
+
const pm2id = String(data.pmId);
|
|
175
|
+
dynamicAppMetrics[data.appName][metricName][pm2id] = entry;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
exports.processAppMetrics = processAppMetrics;
|
|
180
|
+
const getAppRegistry = (needAggregate) => {
|
|
181
|
+
if (Object.keys(dynamicAppMetrics).length) {
|
|
182
|
+
if (needAggregate) {
|
|
183
|
+
return getAggregatedMetrics();
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
const registry = new prom_client_1.default.Registry();
|
|
187
|
+
createRegistryMetrics(registry);
|
|
188
|
+
return registry;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return undefined;
|
|
192
|
+
};
|
|
193
|
+
exports.getAppRegistry = getAppRegistry;
|