@arcblock/pm2-prom-module 2.6.4 → 2.6.6

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/core/app.js CHANGED
@@ -57,6 +57,8 @@ class App {
57
57
  restartCount: pidData.restartCount,
58
58
  metrics: this.fillMetricsData(pidData.metrics),
59
59
  status: pidData.status,
60
+ appUrl: pidData.appUrl,
61
+ appName: pidData.appName,
60
62
  };
61
63
  }
62
64
  else {
package/core/pm2.js CHANGED
@@ -17,10 +17,13 @@ const pm2_1 = __importDefault(require("pm2"));
17
17
  const app_1 = require("./app");
18
18
  const utils_1 = require("../utils");
19
19
  const cpu_1 = require("../utils/cpu");
20
+ const keyBy_1 = __importDefault(require("lodash/keyBy"));
20
21
  const metrics_1 = require("../metrics");
21
22
  const app_2 = require("../metrics/app");
22
23
  const logger_1 = require("../utils/logger");
23
24
  const docker_1 = require("../utils/docker");
25
+ const domain_1 = require("../utils/domain");
26
+ const p_all_1 = __importDefault(require("p-all"));
24
27
  const WORKER_CHECK_INTERVAL = 1000;
25
28
  const SHOW_STAT_INTERVAL = 10000;
26
29
  const APPS = {};
@@ -44,6 +47,8 @@ const updateAppPidsData = (workingApp, pidData) => {
44
47
  createdAt: pidData.createdAt,
45
48
  metrics: pidData.metrics,
46
49
  status: pidData.status,
50
+ appUrl: pidData.appUrl,
51
+ appName: pidData.appName,
47
52
  });
48
53
  };
49
54
  const detectActiveApps = () => {
@@ -88,6 +93,8 @@ const detectActiveApps = () => {
88
93
  createdAt: pm2_env.created_at || 0,
89
94
  metrics: pm2_env.axm_monitor,
90
95
  status: pm2_env.status,
96
+ appUrl: pm2_env.BLOCKLET_APP_URL,
97
+ appName: pm2_env.BLOCKLET_APP_NAME,
91
98
  };
92
99
  }
93
100
  });
@@ -142,11 +149,6 @@ const detectActiveApps = () => {
142
149
  workingApp.updateStatus(entry.status);
143
150
  }
144
151
  }
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
152
  // Update metric with available apps
151
153
  metrics_1.metricAvailableApps === null || metrics_1.metricAvailableApps === void 0 ? void 0 : metrics_1.metricAvailableApps.set(Object.keys(APPS).length);
152
154
  // Get all pids to monit
@@ -202,10 +204,35 @@ const detectActiveApps = () => {
202
204
  .catch((err) => {
203
205
  console.error(err.stack || err);
204
206
  });
207
+ const uniqAppMaps = (0, keyBy_1.default)(
208
+ // @ts-expect-error
209
+ apps.filter((x) => { var _a; return (_a = x.pm2_env) === null || _a === void 0 ? void 0 : _a.BLOCKLET_APP_PID; }), (x) => { var _a; return (_a = x.pm2_env) === null || _a === void 0 ? void 0 : _a.BLOCKLET_APP_PID; });
210
+ (0, p_all_1.default)(Object.values(uniqAppMaps)
211
+ .map((x) => x.pid)
212
+ .map((pid) => {
213
+ const app = pidsMonit[pid];
214
+ if (!app) {
215
+ throw new Error(`App ${pid} does not have active PIDs. Clear app metrics`);
216
+ }
217
+ return app;
218
+ })
219
+ .map((app) => {
220
+ return () => __awaiter(void 0, void 0, void 0, function* () {
221
+ return {
222
+ appName: app.appName,
223
+ urls: yield (0, domain_1.getAppDomainList)(app.appUrl),
224
+ };
225
+ });
226
+ }), { concurrency: 8 }).then((apps) => {
227
+ for (const app of apps) {
228
+ for (const url of app.urls) {
229
+ metrics_1.metricAppDomainList === null || metrics_1.metricAppDomainList === void 0 ? void 0 : metrics_1.metricAppDomainList.set({ appName: app.appName, domain: url }, app.urls.length);
230
+ }
231
+ }
232
+ }).catch(error => console.error(error));
205
233
  });
206
234
  };
207
235
  const startPm2Connect = (conf) => {
208
- const logger = (0, logger_1.getLogger)();
209
236
  pm2_1.default.connect((err) => {
210
237
  var _a;
211
238
  if (err)
@@ -220,32 +247,6 @@ const startPm2Connect = (conf) => {
220
247
  (0, metrics_1.initDynamicGaugeMetricClients)(additionalMetrics);
221
248
  }
222
249
  detectActiveApps();
223
- // Collect statistic from running apps
224
- pm2_1.default.launchBus((err, bus) => {
225
- if (err)
226
- return console.error(err.stack || err);
227
- logger.debug('Start bus listener');
228
- bus.on('process:msg', (packet) => {
229
- if (packet.process &&
230
- packet.raw &&
231
- packet.raw.topic === 'pm2-prom-module:metrics' &&
232
- packet.raw.data) {
233
- const { name, pm_id } = packet.process;
234
- /*logger.debug(
235
- `Got message from app=${name} and pid=${pm_id}. Message=${JSON.stringify(
236
- packet.raw.data
237
- )}`
238
- );*/
239
- if (name && APPS[name] && packet.raw.data.metrics) {
240
- (0, app_2.processAppMetrics)(conf, {
241
- pmId: pm_id,
242
- appName: name,
243
- appResponse: packet.raw.data,
244
- });
245
- }
246
- }
247
- });
248
- });
249
250
  // Start timer to update available apps
250
251
  setInterval(() => {
251
252
  detectActiveApps();
@@ -296,17 +297,3 @@ function processWorkingApp(workingApp) {
296
297
  });
297
298
  });
298
299
  }
299
- function sendCollectStaticticBusEvent(pm2Ids) {
300
- // Request available metrics from all running apps
301
- pm2Ids.forEach((pm2id) => {
302
- pm2_1.default.sendDataToProcessId(pm2id, {
303
- topic: 'pm2-prom-module:collect',
304
- data: {},
305
- // Required fields by pm2 but we do not use them
306
- id: pm2id,
307
- }, (err) => {
308
- if (err)
309
- return console.error(`pm2-prom-module: sendDataToProcessId ${err.stack || err}`);
310
- });
311
- });
312
- }
package/metrics/index.js CHANGED
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.deletePromAppInstancesMetrics = exports.deletePromAppMetrics = exports.combineAllRegistries = exports.initDynamicGaugeMetricClients = exports.initMetrics = exports.dynamicGaugeMetricClients = exports.metricAppStatus = exports.metricAppPidsMemory = exports.metricAppUptime = exports.metricAppRestartCount = exports.metricAppPidsCpuThreshold = exports.metricAppPidsCpuLast = exports.metricAppAverageCpu = exports.metricAppTotalMemory = exports.metricAppAverageMemory = exports.metricAppInstances = exports.metricAvailableApps = exports.registry = void 0;
15
+ exports.deletePromAppInstancesMetrics = exports.deletePromAppMetrics = exports.combineAllRegistries = exports.initDynamicGaugeMetricClients = exports.initMetrics = exports.dynamicGaugeMetricClients = exports.metricAppStatus = exports.metricAppPidsMemory = exports.metricAppUptime = exports.metricAppRestartCount = exports.metricAppPidsCpuThreshold = exports.metricAppPidsCpuLast = exports.metricAppAverageCpu = exports.metricAppTotalMemory = exports.metricAppAverageMemory = exports.metricAppDomainList = exports.metricAppInstances = exports.metricAvailableApps = exports.registry = void 0;
16
16
  const prom_client_1 = __importDefault(require("prom-client"));
17
17
  const node_os_1 = __importDefault(require("node:os"));
18
18
  const cpu_1 = require("../utils/cpu");
@@ -23,6 +23,7 @@ const METRIC_FREE_MEMORY = 'free_memory';
23
23
  const METRIC_AVAILABLE_CPU = 'cpu_count';
24
24
  const METRIC_AVAILABLE_APPS = 'available_apps';
25
25
  const METRIC_APP_INSTANCES = 'app_instances';
26
+ const METRIC_APP_DOMAIN_LIST = 'app_domain_list';
26
27
  const METRIC_APP_AVERAGE_MEMORY = 'app_average_memory';
27
28
  const METRIC_APP_PIDS_MEMORY = 'app_pids_memory';
28
29
  const METRIC_APP_TOTAL_MEMORY = 'app_total_memory';
@@ -146,6 +147,12 @@ const initMetrics = (prefix) => {
146
147
  registers: [exports.registry],
147
148
  labelNames: ['app'],
148
149
  });
150
+ exports.metricAppDomainList = new prom_client_1.default.Gauge({
151
+ name: `${prefix}_${METRIC_APP_DOMAIN_LIST}`,
152
+ help: 'Show app domain list',
153
+ registers: [exports.registry],
154
+ labelNames: ['appName', 'domain'],
155
+ });
149
156
  exports.metricAppAverageMemory = new prom_client_1.default.Gauge({
150
157
  name: `${prefix}_${METRIC_APP_AVERAGE_MEMORY}`,
151
158
  help: 'Show average using memory of an app',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcblock/pm2-prom-module",
3
- "version": "2.6.4",
3
+ "version": "2.6.6",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -8,13 +8,21 @@
8
8
  "main": "index.js",
9
9
  "dependencies": {
10
10
  "@abtnode/util": "^1.16.44",
11
+ "@keyv/sqlite": "^4.0.6",
12
+ "@types/dockerode": "^3.3.42",
13
+ "dockerode": "^4.0.7",
11
14
  "internal-ip": "^6.2.0",
15
+ "is-url": "^1.2.4",
16
+ "keyv": "^5.5.3",
17
+ "lodash": "^4.17.21",
18
+ "p-all": "^3.0.0",
12
19
  "pidusage": "^3.0.2",
13
- "pm2": "^5.3.0",
20
+ "pm2": "^6.0.8",
14
21
  "pmx": "beta",
15
22
  "prom-client": "^15.1.3",
23
+ "ufo": "^1.6.1",
16
24
  "xbytes": "^1.9.1",
17
- "zx": "^4.3.0"
25
+ "zx": "^8.8.0"
18
26
  },
19
27
  "scripts": {
20
28
  "predev": "export PM2_HOME=~/.arcblock/abtnode && pm2 delete @arcblock/pm2-prom-module && pm2 uninstall @arcblock/pm2-prom-module || true && npm run build",
@@ -46,6 +54,7 @@
46
54
  "prefix": "pm2"
47
55
  },
48
56
  "devDependencies": {
57
+ "@types/fs-extra": "^11.0.4",
49
58
  "@types/node": "^18.19.50",
50
59
  "@types/pidusage": "^2.0.5",
51
60
  "bumpp": "^10.1.0",
package/utils/docker.js CHANGED
@@ -12,16 +12,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.getDockerStats = exports.getCPULimit = exports.getFreeMemory = exports.getUsedMemory = exports.getBlockletServerInfo = exports.getAvailableMemory = exports.hasDockerLimitFiles = void 0;
15
+ exports.getCPULimit = exports.getFreeMemory = exports.getUsedMemory = exports.getBlockletServerInfo = exports.getAvailableMemory = exports.hasDockerLimitFiles = void 0;
16
+ exports.getDockerStats = getDockerStats;
16
17
  const promises_1 = require("node:fs/promises");
17
18
  const node_os_1 = __importDefault(require("node:os"));
18
19
  const cpu_1 = require("./cpu");
19
- const zx_1 = require("zx");
20
- const xbytes_1 = __importDefault(require("xbytes"));
20
+ const dockerode_1 = __importDefault(require("dockerode"));
21
21
  const get_ip_1 = __importDefault(require("@abtnode/util/lib/get-ip"));
22
22
  const internal_ip_1 = require("internal-ip");
23
- // 禁用命令和结果的自动输出
24
- zx_1.$.verbose = false;
23
+ const uniq_1 = __importDefault(require("lodash/uniq"));
24
+ // 初始化 Docker 客户端
25
+ const docker = new dockerode_1.default();
25
26
  //const MEMORY_AVAILABLE = '/sys/fs/cgroup/memory.limit_in_bytes';
26
27
  //const MEMORY_USED = '/sys/fs/cgroup/memory.usage_in_bytes';
27
28
  const MEMORY_AVAILABLE = '/sys/fs/cgroup/memory.max';
@@ -97,7 +98,7 @@ const getUsedMemory = () => __awaiter(void 0, void 0, void 0, function* () {
97
98
  return usedMemory;
98
99
  }
99
100
  }
100
- catch (_b) {
101
+ catch (_a) {
101
102
  return 0;
102
103
  }
103
104
  });
@@ -126,7 +127,7 @@ const getFreeMemory = () => __awaiter(void 0, void 0, void 0, function* () {
126
127
  return systemFreeMem;
127
128
  }
128
129
  }
129
- catch (_c) {
130
+ catch (_a) {
130
131
  return 0;
131
132
  }
132
133
  });
@@ -146,41 +147,74 @@ const getCPULimit = () => __awaiter(void 0, void 0, void 0, function* () {
146
147
  }
147
148
  }
148
149
  }
149
- catch (_d) { }
150
+ catch (_a) { }
150
151
  return count;
151
152
  });
152
153
  exports.getCPULimit = getCPULimit;
154
+ /**
155
+ * 获取单个容器的统计信息
156
+ */
157
+ function getContainerStats(containerId) {
158
+ return __awaiter(this, void 0, void 0, function* () {
159
+ var _a, _b, _c, _d, _e, _f, _g;
160
+ try {
161
+ const container = docker.getContainer(containerId);
162
+ const stats = yield container.stats({ stream: false });
163
+ // 内存使用信息
164
+ const memoryUsage = ((_a = stats.memory_stats) === null || _a === void 0 ? void 0 : _a.usage) || 0;
165
+ const memoryLimit = ((_b = stats.memory_stats) === null || _b === void 0 ? void 0 : _b.limit) || 0;
166
+ // CPU使用信息
167
+ const cpuStats = (_c = stats.cpu_stats) === null || _c === void 0 ? void 0 : _c.cpu_usage;
168
+ const preCpuStats = (_d = stats.precpu_stats) === null || _d === void 0 ? void 0 : _d.cpu_usage;
169
+ let cpuPercent = 0;
170
+ if (cpuStats && preCpuStats && ((_e = stats.cpu_stats) === null || _e === void 0 ? void 0 : _e.system_cpu_usage) && ((_f = stats.precpu_stats) === null || _f === void 0 ? void 0 : _f.system_cpu_usage)) {
171
+ // 计算容器CPU使用时间的增量(纳秒)
172
+ const cpuDelta = cpuStats.total_usage - preCpuStats.total_usage;
173
+ // 计算系统CPU时间的增量(纳秒)
174
+ const systemDelta = stats.cpu_stats.system_cpu_usage - stats.precpu_stats.system_cpu_usage;
175
+ if (systemDelta > 0 && cpuDelta >= 0) {
176
+ // 获取系统可用的CPU核心数
177
+ const onlineCpus = ((_g = stats.cpu_stats) === null || _g === void 0 ? void 0 : _g.online_cpus) || node_os_1.default.cpus().length;
178
+ // Docker CPU使用率计算公式:
179
+ // CPU% = (容器CPU增量 / 系统CPU增量) * CPU核心数 * 100%
180
+ //
181
+ // 解释:
182
+ // - cpuDelta: 容器在两次采样间隔内使用的CPU时间
183
+ // - systemDelta: 系统在两次采样间隔内所有CPU核心的总时间
184
+ // - 比值 (cpuDelta/systemDelta) 表示容器占用系统总CPU时间的比例
185
+ // - 乘以核心数是因为Docker的统计方式:单核100%使用时,在N核系统上显示为N*100%
186
+ cpuPercent = (cpuDelta / systemDelta) * onlineCpus * 100;
187
+ // 确保结果为非负数且不超过理论最大值
188
+ cpuPercent = Math.max(0, Math.min(cpuPercent, onlineCpus * 100));
189
+ }
190
+ }
191
+ return {
192
+ name: containerId,
193
+ cpuUsage: Math.round(cpuPercent * 100) / 100, // 保留2位小数
194
+ memoryUsage: memoryUsage,
195
+ totalMemory: memoryLimit,
196
+ };
197
+ }
198
+ catch (error) {
199
+ console.error(`Failed to get stats for container ${containerId}:`, error.message);
200
+ return null;
201
+ }
202
+ });
203
+ }
153
204
  function getDockerStats(ids) {
154
205
  return __awaiter(this, void 0, void 0, function* () {
155
206
  try {
156
207
  if (!ids.length) {
157
208
  return [];
158
209
  }
159
- const result = yield (0, zx_1.$) `docker stats --no-stream --format "{{json .}}" -a`;
160
- if (result.exitCode || !result.stdout) {
161
- return [];
162
- }
163
- const statsRows = result.stdout
164
- .split('\n')
165
- .filter(Boolean)
166
- .map((x) => JSON.parse(x));
167
- const stats = statsRows.map((x) => {
168
- const [memoryUsage, totalMemory] = x.MemUsage.split('/').map((x) => xbytes_1.default.parseSize(x.trim()));
169
- return {
170
- name: x.Name,
171
- cpuUsage: +x.CPUPerc.replace('%', ''),
172
- memoryUsage: memoryUsage,
173
- totalMemory: totalMemory,
174
- };
175
- });
176
- return ids.map((id) => {
177
- return stats.find((x) => x.name === id);
178
- });
210
+ // 并行获取所有容器的统计信息
211
+ const results = yield Promise.all((0, uniq_1.default)(ids).map(id => getContainerStats(id)));
212
+ // 过滤出成功的结果
213
+ return results.filter((stat) => stat !== null);
179
214
  }
180
215
  catch (error) {
181
- console.error(error);
216
+ console.error('Error getting Docker stats:', error);
182
217
  return [];
183
218
  }
184
219
  });
185
220
  }
186
- exports.getDockerStats = getDockerStats;
@@ -0,0 +1,56 @@
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.getAppDomainList = getAppDomainList;
16
+ const is_url_1 = __importDefault(require("is-url"));
17
+ const isEmpty_1 = __importDefault(require("lodash/isEmpty"));
18
+ const ufo_1 = require("ufo");
19
+ const keyv_1 = __importDefault(require("keyv"));
20
+ const sqlite_1 = __importDefault(require("@keyv/sqlite"));
21
+ const appDomainListCache = new keyv_1.default({
22
+ store: new sqlite_1.default({
23
+ // 这里只能相对路径才能工作
24
+ uri: 'sqlite://./cache.db',
25
+ table: 'app_domain_list_cache',
26
+ busyTimeout: 10000,
27
+ }),
28
+ // 默认缓存一个小时
29
+ ttl: 1000 * 60 * 60,
30
+ });
31
+ function getAppDomainList(url) {
32
+ return __awaiter(this, void 0, void 0, function* () {
33
+ var _a;
34
+ try {
35
+ if (!url) {
36
+ return [];
37
+ }
38
+ if (!(0, is_url_1.default)(url)) {
39
+ return [url];
40
+ }
41
+ if (yield appDomainListCache.has(url)) {
42
+ return yield appDomainListCache.get(url);
43
+ }
44
+ const response = yield fetch((0, ufo_1.joinURL)(url, '__blocklet__.js?type=json'));
45
+ const domainAliases = ((_a = (yield response.json())) === null || _a === void 0 ? void 0 : _a.domainAliases) || [];
46
+ if (!(0, isEmpty_1.default)(domainAliases)) {
47
+ yield appDomainListCache.set(url, domainAliases);
48
+ }
49
+ return domainAliases || [url];
50
+ }
51
+ catch (error) {
52
+ console.error(error);
53
+ return [url];
54
+ }
55
+ });
56
+ }
package/utils/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toUndescore = void 0;
3
+ exports.toUndescore = toUndescore;
4
4
  function toUndescore(str) {
5
5
  return str.toLowerCase().replace(/\s+/g, '_');
6
6
  }
7
- exports.toUndescore = toUndescore;