@sfdxy/anypoint-connect 0.14.0 → 0.16.2
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/README.md +39 -16
- package/dist/analysis/LogAnalyzer.d.ts +10 -4
- package/dist/analysis/LogAnalyzer.d.ts.map +1 -1
- package/dist/analysis/LogAnalyzer.js +29 -18
- package/dist/analysis/LogAnalyzer.js.map +1 -1
- package/dist/analysis/error-context.d.ts +2 -0
- package/dist/analysis/error-context.d.ts.map +1 -1
- package/dist/analysis/error-context.js +2 -1
- package/dist/analysis/error-context.js.map +1 -1
- package/dist/analysis/error-grouper.js +1 -1
- package/dist/analysis/error-grouper.js.map +1 -1
- package/dist/analysis/filter.d.ts +54 -0
- package/dist/analysis/filter.d.ts.map +1 -0
- package/dist/analysis/filter.js +100 -0
- package/dist/analysis/filter.js.map +1 -0
- package/dist/analysis/{utils.d.ts → normalize.d.ts} +1 -1
- package/dist/analysis/normalize.d.ts.map +1 -0
- package/dist/analysis/{utils.js → normalize.js} +2 -2
- package/dist/analysis/normalize.js.map +1 -0
- package/dist/analysis/parser.d.ts.map +1 -1
- package/dist/analysis/parser.js +45 -17
- package/dist/analysis/parser.js.map +1 -1
- package/dist/analysis/pattern-detector.js +1 -1
- package/dist/analysis/pattern-detector.js.map +1 -1
- package/dist/analysis/stats.d.ts +25 -0
- package/dist/analysis/stats.d.ts.map +1 -1
- package/dist/analysis/stats.js +35 -1
- package/dist/analysis/stats.js.map +1 -1
- package/dist/analysis/summary.d.ts +51 -0
- package/dist/analysis/summary.d.ts.map +1 -0
- package/dist/analysis/summary.js +47 -0
- package/dist/analysis/summary.js.map +1 -0
- package/dist/analysis/types.d.ts +14 -0
- package/dist/analysis/types.d.ts.map +1 -1
- package/dist/api/ApiManagerApi.d.ts +53 -0
- package/dist/api/ApiManagerApi.d.ts.map +1 -1
- package/dist/api/ApiManagerApi.js +53 -2
- package/dist/api/ApiManagerApi.js.map +1 -1
- package/dist/api/CloudHub2Api.d.ts +24 -4
- package/dist/api/CloudHub2Api.d.ts.map +1 -1
- package/dist/api/CloudHub2Api.js +10 -22
- package/dist/api/CloudHub2Api.js.map +1 -1
- package/dist/api/DesignCenterApi.d.ts +0 -20
- package/dist/api/DesignCenterApi.d.ts.map +1 -1
- package/dist/api/DesignCenterApi.js +0 -22
- package/dist/api/DesignCenterApi.js.map +1 -1
- package/dist/api/ExchangeApi.d.ts +15 -0
- package/dist/api/ExchangeApi.d.ts.map +1 -1
- package/dist/api/ExchangeApi.js +51 -0
- package/dist/api/ExchangeApi.js.map +1 -1
- package/dist/api/LogsApi.d.ts +50 -18
- package/dist/api/LogsApi.d.ts.map +1 -1
- package/dist/api/LogsApi.js +69 -98
- package/dist/api/LogsApi.js.map +1 -1
- package/dist/api/MonitoringApi.d.ts +137 -93
- package/dist/api/MonitoringApi.d.ts.map +1 -1
- package/dist/api/MonitoringApi.js +471 -204
- package/dist/api/MonitoringApi.js.map +1 -1
- package/dist/api/RuntimesApi.d.ts +56 -0
- package/dist/api/RuntimesApi.d.ts.map +1 -0
- package/dist/api/RuntimesApi.js +58 -0
- package/dist/api/RuntimesApi.js.map +1 -0
- package/dist/auth/FileStore.js +1 -1
- package/dist/auth/FileStore.js.map +1 -1
- package/dist/cli.js +4 -2
- package/dist/cli.js.map +1 -1
- package/dist/client/AnypointClient.d.ts +3 -1
- package/dist/client/AnypointClient.d.ts.map +1 -1
- package/dist/client/AnypointClient.js +5 -2
- package/dist/client/AnypointClient.js.map +1 -1
- package/dist/client/smoke-test.d.ts +111 -0
- package/dist/client/smoke-test.d.ts.map +1 -0
- package/dist/client/smoke-test.js +275 -0
- package/dist/client/smoke-test.js.map +1 -0
- package/dist/commands/api.d.ts +1 -1
- package/dist/commands/api.d.ts.map +1 -1
- package/dist/commands/api.js +175 -30
- package/dist/commands/api.js.map +1 -1
- package/dist/commands/app-change-cli.d.ts +52 -0
- package/dist/commands/app-change-cli.d.ts.map +1 -0
- package/dist/commands/app-change-cli.js +149 -0
- package/dist/commands/app-change-cli.js.map +1 -0
- package/dist/commands/apps.d.ts +1 -1
- package/dist/commands/apps.d.ts.map +1 -1
- package/dist/commands/apps.js +199 -18
- package/dist/commands/apps.js.map +1 -1
- package/dist/commands/auth.js +1 -1
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/compare.d.ts +8 -0
- package/dist/commands/compare.d.ts.map +1 -0
- package/dist/commands/compare.js +104 -0
- package/dist/commands/compare.js.map +1 -0
- package/dist/commands/config.js +1 -1
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/deploy.d.ts +6 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +89 -91
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/design-center.d.ts +1 -1
- package/dist/commands/design-center.d.ts.map +1 -1
- package/dist/commands/design-center.js +36 -25
- package/dist/commands/design-center.js.map +1 -1
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +1 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/logs.d.ts +3 -2
- package/dist/commands/logs.d.ts.map +1 -1
- package/dist/commands/logs.js +142 -17
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/monitor.d.ts +5 -6
- package/dist/commands/monitor.d.ts.map +1 -1
- package/dist/commands/monitor.js +210 -230
- package/dist/commands/monitor.js.map +1 -1
- package/dist/commands/runtimes.d.ts +7 -0
- package/dist/commands/runtimes.d.ts.map +1 -0
- package/dist/commands/runtimes.js +66 -0
- package/dist/commands/runtimes.js.map +1 -0
- package/dist/commands/shared.d.ts +2 -0
- package/dist/commands/shared.d.ts.map +1 -1
- package/dist/commands/shared.js +16 -1
- package/dist/commands/shared.js.map +1 -1
- package/dist/{utils/config.d.ts → config/profiles.d.ts} +9 -1
- package/dist/config/profiles.d.ts.map +1 -0
- package/dist/{utils/config.js → config/profiles.js} +1 -1
- package/dist/config/profiles.js.map +1 -0
- package/dist/index.d.ts +12 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp/prompts.js +7 -7
- package/dist/mcp/prompts.js.map +1 -1
- package/dist/mcp/server.d.ts +14 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +61 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/analysis.d.ts.map +1 -1
- package/dist/mcp/tools/analysis.js +74 -46
- package/dist/mcp/tools/analysis.js.map +1 -1
- package/dist/mcp/tools/anypoint-mq.d.ts.map +1 -1
- package/dist/mcp/tools/anypoint-mq.js +25 -54
- package/dist/mcp/tools/anypoint-mq.js.map +1 -1
- package/dist/mcp/tools/api-manager.d.ts +2 -1
- package/dist/mcp/tools/api-manager.d.ts.map +1 -1
- package/dist/mcp/tools/api-manager.js +154 -89
- package/dist/mcp/tools/api-manager.js.map +1 -1
- package/dist/mcp/tools/app-inputs.d.ts +92 -0
- package/dist/mcp/tools/app-inputs.d.ts.map +1 -0
- package/dist/mcp/tools/app-inputs.js +142 -0
- package/dist/mcp/tools/app-inputs.js.map +1 -0
- package/dist/mcp/tools/apps-deploy.d.ts +13 -0
- package/dist/mcp/tools/apps-deploy.d.ts.map +1 -0
- package/dist/mcp/tools/apps-deploy.js +358 -0
- package/dist/mcp/tools/apps-deploy.js.map +1 -0
- package/dist/mcp/tools/apps-lifecycle.d.ts +11 -0
- package/dist/mcp/tools/apps-lifecycle.d.ts.map +1 -0
- package/dist/mcp/tools/apps-lifecycle.js +244 -0
- package/dist/mcp/tools/apps-lifecycle.js.map +1 -0
- package/dist/mcp/tools/apps-read.d.ts +9 -0
- package/dist/mcp/tools/apps-read.d.ts.map +1 -0
- package/dist/mcp/tools/apps-read.js +414 -0
- package/dist/mcp/tools/apps-read.js.map +1 -0
- package/dist/mcp/tools/artifacts.d.ts +8 -0
- package/dist/mcp/tools/artifacts.d.ts.map +1 -0
- package/dist/mcp/tools/artifacts.js +81 -0
- package/dist/mcp/tools/artifacts.js.map +1 -0
- package/dist/mcp/tools/audit.d.ts.map +1 -1
- package/dist/mcp/tools/audit.js +9 -17
- package/dist/mcp/tools/audit.js.map +1 -1
- package/dist/mcp/tools/design-center.d.ts +2 -3
- package/dist/mcp/tools/design-center.d.ts.map +1 -1
- package/dist/mcp/tools/design-center.js +41 -185
- package/dist/mcp/tools/design-center.js.map +1 -1
- package/dist/mcp/tools/exchange.d.ts +1 -2
- package/dist/mcp/tools/exchange.d.ts.map +1 -1
- package/dist/mcp/tools/exchange.js +51 -312
- package/dist/mcp/tools/exchange.js.map +1 -1
- package/dist/mcp/tools/governance.d.ts +8 -0
- package/dist/mcp/tools/governance.d.ts.map +1 -0
- package/dist/mcp/tools/governance.js +59 -0
- package/dist/mcp/tools/governance.js.map +1 -0
- package/dist/mcp/tools/identity.d.ts.map +1 -1
- package/dist/mcp/tools/identity.js +15 -36
- package/dist/mcp/tools/identity.js.map +1 -1
- package/dist/mcp/tools/index.d.ts +5 -1
- package/dist/mcp/tools/index.d.ts.map +1 -1
- package/dist/mcp/tools/index.js +5 -1
- package/dist/mcp/tools/index.js.map +1 -1
- package/dist/mcp/tools/logs.d.ts +48 -2
- package/dist/mcp/tools/logs.d.ts.map +1 -1
- package/dist/mcp/tools/logs.js +204 -54
- package/dist/mcp/tools/logs.js.map +1 -1
- package/dist/mcp/tools/monitoring.d.ts +1 -2
- package/dist/mcp/tools/monitoring.d.ts.map +1 -1
- package/dist/mcp/tools/monitoring.js +78 -285
- package/dist/mcp/tools/monitoring.js.map +1 -1
- package/dist/mcp/tools/object-store.d.ts +1 -1
- package/dist/mcp/tools/object-store.d.ts.map +1 -1
- package/dist/mcp/tools/object-store.js +19 -59
- package/dist/mcp/tools/object-store.js.map +1 -1
- package/dist/mcp/tools/profile.d.ts.map +1 -1
- package/dist/mcp/tools/profile.js +15 -35
- package/dist/mcp/tools/profile.js.map +1 -1
- package/dist/mcp/tools/shared.d.ts +43 -0
- package/dist/mcp/tools/shared.d.ts.map +1 -1
- package/dist/mcp/tools/shared.js +54 -0
- package/dist/mcp/tools/shared.js.map +1 -1
- package/dist/mcp.d.ts +3 -11
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +4 -56
- package/dist/mcp.js.map +1 -1
- package/dist/safety/deployment.d.ts +47 -2
- package/dist/safety/deployment.d.ts.map +1 -1
- package/dist/safety/deployment.js +139 -6
- package/dist/safety/deployment.js.map +1 -1
- package/dist/safety/guards.d.ts +2 -2
- package/dist/safety/guards.d.ts.map +1 -1
- package/dist/safety/guards.js +4 -4
- package/dist/safety/guards.js.map +1 -1
- package/dist/safety/paths.d.ts +29 -0
- package/dist/safety/paths.d.ts.map +1 -0
- package/dist/safety/paths.js +76 -0
- package/dist/safety/paths.js.map +1 -0
- package/dist/safety/runtime.d.ts +24 -0
- package/dist/safety/runtime.d.ts.map +1 -0
- package/dist/safety/runtime.js +59 -0
- package/dist/safety/runtime.js.map +1 -0
- package/dist/safety/secret-sources.d.ts +51 -0
- package/dist/safety/secret-sources.d.ts.map +1 -0
- package/dist/safety/secret-sources.js +124 -0
- package/dist/safety/secret-sources.js.map +1 -0
- package/dist/safety/secrets.d.ts +32 -0
- package/dist/safety/secrets.d.ts.map +1 -0
- package/dist/safety/secrets.js +91 -0
- package/dist/safety/secrets.js.map +1 -0
- package/dist/utils/errors.d.ts +14 -1
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +31 -3
- package/dist/utils/errors.js.map +1 -1
- package/dist/workflows/app-change.d.ts +124 -0
- package/dist/workflows/app-change.d.ts.map +1 -0
- package/dist/workflows/app-change.js +305 -0
- package/dist/workflows/app-change.js.map +1 -0
- package/dist/workflows/app-state.d.ts +60 -0
- package/dist/workflows/app-state.d.ts.map +1 -0
- package/dist/workflows/app-state.js +95 -0
- package/dist/workflows/app-state.js.map +1 -0
- package/dist/workflows/artifact-compare.d.ts +134 -0
- package/dist/workflows/artifact-compare.d.ts.map +1 -0
- package/dist/workflows/artifact-compare.js +419 -0
- package/dist/workflows/artifact-compare.js.map +1 -0
- package/dist/workflows/asset-naming.d.ts +72 -0
- package/dist/workflows/asset-naming.d.ts.map +1 -0
- package/dist/workflows/asset-naming.js +116 -0
- package/dist/workflows/asset-naming.js.map +1 -0
- package/dist/{api/DesignCenterWorkflow.d.ts → workflows/design-center.d.ts} +4 -4
- package/dist/workflows/design-center.d.ts.map +1 -0
- package/dist/{api/DesignCenterWorkflow.js → workflows/design-center.js} +1 -1
- package/dist/workflows/design-center.js.map +1 -0
- package/dist/workflows/jar-deployment.d.ts +156 -0
- package/dist/workflows/jar-deployment.d.ts.map +1 -0
- package/dist/workflows/jar-deployment.js +178 -0
- package/dist/workflows/jar-deployment.js.map +1 -0
- package/package.json +12 -4
- package/dist/analysis/utils.d.ts.map +0 -1
- package/dist/analysis/utils.js.map +0 -1
- package/dist/api/DesignCenterWorkflow.d.ts.map +0 -1
- package/dist/api/DesignCenterWorkflow.js.map +0 -1
- package/dist/api/index.d.ts +0 -13
- package/dist/api/index.d.ts.map +0 -1
- package/dist/api/index.js +0 -13
- package/dist/api/index.js.map +0 -1
- package/dist/mcp/tools/applications.d.ts +0 -4
- package/dist/mcp/tools/applications.d.ts.map +0 -1
- package/dist/mcp/tools/applications.js +0 -815
- package/dist/mcp/tools/applications.js.map +0 -1
- package/dist/utils/config.d.ts.map +0 -1
- package/dist/utils/config.js.map +0 -1
|
@@ -1,7 +1,63 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Monitoring API
|
|
3
|
-
* AMQL
|
|
3
|
+
* AMQL queries against the Anypoint Monitoring metrics search endpoint.
|
|
4
|
+
*
|
|
5
|
+
* Datasource notes (behaviour of the platform, not of this client):
|
|
6
|
+
* - Traffic: "mulesoft.app.inbound" / "mulesoft.app.outbound" (requests, response_time,
|
|
7
|
+
* response.status = 'FAILED', http.route on outbound), "mulesoft.message" (total_count, error_count).
|
|
8
|
+
* - JVM memory: "mulesoft.app.jvm.memory" is per pool ("total-heap", an old-generation pool such as
|
|
9
|
+
* "tenured_gen", eden, survivor, "metaspace", …) with usage / committed / "limit" (-1 = unbounded).
|
|
10
|
+
* - GC: "mulesoft.app.jvm.gc" count and duration (ms) are cumulative counters per worker and collector,
|
|
11
|
+
* so the activity in a window is MAX − MIN, never SUM.
|
|
12
|
+
* - Host: "mulesoft.app.jvm.cpu" (available_processors, total_physical_memory_size) and
|
|
13
|
+
* "mulesoft.app.memory" (system_cpu_load, process_cpu_load, free_physical_memory_size). MIN/MAX over
|
|
14
|
+
* the physical-memory size fields fail server-side (HTTP 500 after a long wait); only AVG is used.
|
|
4
15
|
*/
|
|
16
|
+
import { AmqlQueryError } from '../utils/errors.js';
|
|
17
|
+
/** Largest page the metrics search endpoint accepts. */
|
|
18
|
+
export const AMQL_MAX_LIMIT = 2000;
|
|
19
|
+
/** Hard cap on rows collected across pages by {@link MonitoringApi.searchAll}. */
|
|
20
|
+
const SEARCH_ALL_ROW_CAP = 20000;
|
|
21
|
+
/** Large windows on some datasources take well over the default HTTP timeout. */
|
|
22
|
+
const AMQL_TIMEOUT_MS = 90_000;
|
|
23
|
+
/** User-facing bucket sizes mapped to AMQL TIMESERIES durations. */
|
|
24
|
+
export const GRANULARITY = {
|
|
25
|
+
'1m': 'PT1M',
|
|
26
|
+
'5m': 'PT5M',
|
|
27
|
+
'15m': 'PT15M',
|
|
28
|
+
'30m': 'PT30M',
|
|
29
|
+
'1h': 'PT1H',
|
|
30
|
+
'1d': 'P1D',
|
|
31
|
+
};
|
|
32
|
+
export const GRANULARITIES = Object.keys(GRANULARITY);
|
|
33
|
+
export function isGranularity(value) {
|
|
34
|
+
return value in GRANULARITY;
|
|
35
|
+
}
|
|
36
|
+
/** Quote a value as an AMQL string literal. */
|
|
37
|
+
export function amqlString(value) {
|
|
38
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
39
|
+
}
|
|
40
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
41
|
+
function assertUuid(value, label) {
|
|
42
|
+
if (!UUID_PATTERN.test(value))
|
|
43
|
+
throw new Error(`Invalid ${label}: expected a UUID`);
|
|
44
|
+
}
|
|
45
|
+
export const TIME_SERIES_SIGNALS = ['traffic', 'latency', 'memory', 'cpu', 'gc'];
|
|
46
|
+
const OLD_GENERATION_POOL = /old|tenured/i;
|
|
47
|
+
const OLD_GENERATION_COLLECTOR = /old|marksweep|mark sweep|full|major|tenured/i;
|
|
48
|
+
function num(value) {
|
|
49
|
+
const n = Number(value);
|
|
50
|
+
return Number.isFinite(n) ? n : 0;
|
|
51
|
+
}
|
|
52
|
+
function numOrNull(value) {
|
|
53
|
+
if (value === null || value === undefined || value === '')
|
|
54
|
+
return null;
|
|
55
|
+
const n = Number(value);
|
|
56
|
+
return Number.isFinite(n) ? n : null;
|
|
57
|
+
}
|
|
58
|
+
function str(value, fallback = 'Unknown') {
|
|
59
|
+
return value === null || value === undefined || value === '' ? fallback : String(value);
|
|
60
|
+
}
|
|
5
61
|
export class MonitoringApi {
|
|
6
62
|
http;
|
|
7
63
|
cache;
|
|
@@ -11,249 +67,460 @@ export class MonitoringApi {
|
|
|
11
67
|
this.cache = cache;
|
|
12
68
|
}
|
|
13
69
|
/**
|
|
14
|
-
* Execute an AMQL query
|
|
70
|
+
* Execute one page of an AMQL query. Platform errors (syntax, unknown datasource or
|
|
71
|
+
* attribute, oversized limit) are raised as {@link AmqlQueryError}, never hidden.
|
|
15
72
|
*/
|
|
16
|
-
async search(query,
|
|
73
|
+
async search(query, options = {}) {
|
|
74
|
+
return (await this.searchPage(query, options)).data ?? [];
|
|
75
|
+
}
|
|
76
|
+
/** Execute an AMQL query and follow pagination until exhausted (capped). */
|
|
77
|
+
async searchAll(query) {
|
|
78
|
+
const rows = [];
|
|
79
|
+
let offset = 0;
|
|
80
|
+
for (;;) {
|
|
81
|
+
const page = await this.searchPage(query, { limit: AMQL_MAX_LIMIT, offset });
|
|
82
|
+
const data = page.data ?? [];
|
|
83
|
+
rows.push(...data);
|
|
84
|
+
offset += data.length;
|
|
85
|
+
if (!page.metadata?.pagination?.next || data.length === 0 || rows.length >= SEARCH_ALL_ROW_CAP) {
|
|
86
|
+
return rows;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async searchPage(query, { limit = 200, offset = 0 }) {
|
|
91
|
+
const pageSize = Math.min(Math.max(Math.trunc(limit) || 1, 1), AMQL_MAX_LIMIT);
|
|
92
|
+
const pageOffset = Math.max(Math.trunc(offset) || 0, 0);
|
|
17
93
|
try {
|
|
18
|
-
|
|
19
|
-
return response.data || [];
|
|
94
|
+
return await this.http.post(`${this.baseUrl}/metrics:search?limit=${pageSize}&offset=${pageOffset}`, { query }, { timeout: AMQL_TIMEOUT_MS });
|
|
20
95
|
}
|
|
21
|
-
catch {
|
|
22
|
-
|
|
96
|
+
catch (error) {
|
|
97
|
+
throw AmqlQueryError.from(error, query);
|
|
23
98
|
}
|
|
24
99
|
}
|
|
25
100
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* Anypoint Platform API — not user input — so string interpolation is safe here.
|
|
29
|
-
*/
|
|
30
|
-
async getInboundMetrics(orgId, envId, from, to) {
|
|
31
|
-
const cacheKey = `mon:inbound:${orgId}:${envId}:${from}:${to}`;
|
|
32
|
-
return this.cache.getOrCompute(cacheKey, async () => {
|
|
33
|
-
const query = `SELECT COUNT(requests) AS "request_count", AVG(response_time) AS "avg_response_time", "app.name" FROM "mulesoft.app.inbound" WHERE "sub_org.id" = '${orgId}' AND "env.id" = '${envId}' AND timestamp BETWEEN ${from} AND ${to} GROUP BY "app.name"`;
|
|
34
|
-
const data = await this.search(query);
|
|
35
|
-
return data.map((row) => ({
|
|
36
|
-
appName: String(row['app.name'] || 'Unknown'),
|
|
37
|
-
requestCount: Number(row['request_count'] || 0),
|
|
38
|
-
avgResponseTime: Number(row['avg_response_time'] || 0),
|
|
39
|
-
}));
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Get outbound metrics for a time range
|
|
101
|
+
* Traffic and latency per app (or per worker), with failures, outbound calls and message
|
|
102
|
+
* volume merged in. Omitting `envId` compares every environment.
|
|
44
103
|
*/
|
|
45
|
-
async
|
|
46
|
-
const cacheKey = `mon:
|
|
104
|
+
async getMetrics(scope, groupBy = 'app') {
|
|
105
|
+
const cacheKey = `mon:metrics:${this.scopeKey(scope)}:${groupBy}`;
|
|
47
106
|
return this.cache.getOrCompute(cacheKey, async () => {
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
107
|
+
const keys = this.groupKeys(scope, groupBy === 'worker');
|
|
108
|
+
const where = this.where(scope);
|
|
109
|
+
const failed = `${where} AND "response.status" = 'FAILED'`;
|
|
110
|
+
const groupBySql = `GROUP BY ${keys.join(', ')}`;
|
|
111
|
+
const select = keys.join(', ');
|
|
112
|
+
const [inbound, inboundFailed, outbound, outboundFailed, messages] = await Promise.all([
|
|
113
|
+
this.searchAll(`SELECT COUNT(requests) AS "request_count", AVG(response_time) AS "avg_response_time", MIN(response_time) AS "min_response_time", MAX(response_time) AS "max_response_time", PERCENTILE(response_time, 0.5) AS "p50", PERCENTILE(response_time, 0.75) AS "p75", PERCENTILE(response_time, 0.9) AS "p90", PERCENTILE(response_time, 0.95) AS "p95", PERCENTILE(response_time, 0.99) AS "p99", ${select} FROM "mulesoft.app.inbound" WHERE ${where} ${groupBySql}`),
|
|
114
|
+
this.searchAll(`SELECT COUNT(requests) AS "failed_count", ${select} FROM "mulesoft.app.inbound" WHERE ${failed} ${groupBySql}`),
|
|
115
|
+
this.searchAll(`SELECT COUNT(requests) AS "request_count", AVG(response_time) AS "avg_response_time", ${select} FROM "mulesoft.app.outbound" WHERE ${where} ${groupBySql}`),
|
|
116
|
+
this.searchAll(`SELECT COUNT(requests) AS "failed_count", ${select} FROM "mulesoft.app.outbound" WHERE ${failed} ${groupBySql}`),
|
|
117
|
+
this.searchAll(`SELECT SUM(total_count) AS "message_count", SUM(error_count) AS "message_error_count", ${select} FROM "mulesoft.message" WHERE ${where} ${groupBySql}`),
|
|
118
|
+
]);
|
|
119
|
+
const rows = new Map();
|
|
120
|
+
const rowFor = (row) => {
|
|
121
|
+
const key = keys.map((k) => str(row[k.replace(/"/g, '')], '')).join('\u0000');
|
|
122
|
+
let entry = rows.get(key);
|
|
123
|
+
if (!entry) {
|
|
124
|
+
entry = {
|
|
125
|
+
appName: str(row['app.name']),
|
|
126
|
+
...(scope.envId ? {} : { envName: str(row['env.name']) }),
|
|
127
|
+
...(groupBy === 'worker' ? { workerId: str(row['worker.id']) } : {}),
|
|
128
|
+
requestCount: 0,
|
|
129
|
+
failedCount: 0,
|
|
130
|
+
failureRate: 0,
|
|
131
|
+
avgResponseTime: 0,
|
|
132
|
+
minResponseTime: 0,
|
|
133
|
+
maxResponseTime: 0,
|
|
134
|
+
p50: 0,
|
|
135
|
+
p75: 0,
|
|
136
|
+
p90: 0,
|
|
137
|
+
p95: 0,
|
|
138
|
+
p99: 0,
|
|
139
|
+
outboundCount: 0,
|
|
140
|
+
outboundFailedCount: 0,
|
|
141
|
+
outboundAvgResponseTime: 0,
|
|
142
|
+
messageCount: 0,
|
|
143
|
+
messageErrorCount: 0,
|
|
144
|
+
};
|
|
145
|
+
rows.set(key, entry);
|
|
146
|
+
}
|
|
147
|
+
return entry;
|
|
74
148
|
};
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
149
|
+
for (const row of inbound) {
|
|
150
|
+
Object.assign(rowFor(row), {
|
|
151
|
+
requestCount: num(row['request_count']),
|
|
152
|
+
avgResponseTime: num(row['avg_response_time']),
|
|
153
|
+
minResponseTime: num(row['min_response_time']),
|
|
154
|
+
maxResponseTime: num(row['max_response_time']),
|
|
155
|
+
p50: num(row['p50']),
|
|
156
|
+
p75: num(row['p75']),
|
|
157
|
+
p90: num(row['p90']),
|
|
158
|
+
p95: num(row['p95']),
|
|
159
|
+
p99: num(row['p99']),
|
|
85
160
|
});
|
|
86
161
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Get percentile-based performance metrics per app
|
|
95
|
-
*/
|
|
96
|
-
async getPerformanceMetrics(orgId, envId, from, to, appName) {
|
|
97
|
-
const cacheKey = `mon:perf:${orgId}:${envId}:${from}:${to}:${appName || ''}`;
|
|
98
|
-
return this.cache.getOrCompute(cacheKey, async () => {
|
|
99
|
-
let query = `SELECT COUNT(requests) AS "request_count", AVG(response_time) AS "avg_response_time", MAX(response_time) AS "max_response_time", MIN(response_time) AS "min_response_time", PERCENTILE(response_time, 0.5) AS "p50", PERCENTILE(response_time, 0.95) AS "p95", PERCENTILE(response_time, 0.99) AS "p99", "app.name" FROM "mulesoft.app.inbound" WHERE "sub_org.id" = '${orgId}' AND "env.id" = '${envId}' AND timestamp BETWEEN ${from} AND ${to}`;
|
|
100
|
-
if (appName) {
|
|
101
|
-
query += ` AND "app.name" = '${appName}'`;
|
|
162
|
+
for (const row of inboundFailed)
|
|
163
|
+
rowFor(row).failedCount = num(row['failed_count']);
|
|
164
|
+
for (const row of outbound) {
|
|
165
|
+
const entry = rowFor(row);
|
|
166
|
+
entry.outboundCount = num(row['request_count']);
|
|
167
|
+
entry.outboundAvgResponseTime = num(row['avg_response_time']);
|
|
102
168
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
maxResponseTime: Number(row['max_response_time'] || 0),
|
|
110
|
-
minResponseTime: Number(row['min_response_time'] || 0),
|
|
111
|
-
p50: Number(row['p50'] || 0),
|
|
112
|
-
p95: Number(row['p95'] || 0),
|
|
113
|
-
p99: Number(row['p99'] || 0),
|
|
114
|
-
}));
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Get time-series metrics for trending analysis
|
|
119
|
-
*/
|
|
120
|
-
async getTimeSeries(orgId, envId, from, to, granularity = 'PT1H', appName) {
|
|
121
|
-
const cacheKey = `mon:ts:${orgId}:${envId}:${from}:${to}:${granularity}:${appName || ''}`;
|
|
122
|
-
return this.cache.getOrCompute(cacheKey, async () => {
|
|
123
|
-
let query = `SELECT timestamp, COUNT(requests) AS "request_count", AVG(response_time) AS "avg_response_time", PERCENTILE(response_time, 0.95) AS "p95", "app.name" FROM "mulesoft.app.inbound" WHERE "sub_org.id" = '${orgId}' AND "env.id" = '${envId}' AND timestamp BETWEEN ${from} AND ${to}`;
|
|
124
|
-
if (appName) {
|
|
125
|
-
query += ` AND "app.name" = '${appName}'`;
|
|
169
|
+
for (const row of outboundFailed)
|
|
170
|
+
rowFor(row).outboundFailedCount = num(row['failed_count']);
|
|
171
|
+
for (const row of messages) {
|
|
172
|
+
const entry = rowFor(row);
|
|
173
|
+
entry.messageCount = num(row['message_count']);
|
|
174
|
+
entry.messageErrorCount = num(row['message_error_count']);
|
|
126
175
|
}
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
timestamp: Number(row['timestamp'] || 0),
|
|
131
|
-
appName: String(row['app.name'] || 'Unknown'),
|
|
132
|
-
requestCount: Number(row['request_count'] || 0),
|
|
133
|
-
avgResponseTime: Number(row['avg_response_time'] || 0),
|
|
134
|
-
p95: Number(row['p95'] || 0),
|
|
135
|
-
}));
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Get per-worker/replica performance metrics
|
|
140
|
-
*/
|
|
141
|
-
async getWorkerMetrics(orgId, envId, from, to, appName) {
|
|
142
|
-
const cacheKey = `mon:workers:${orgId}:${envId}:${from}:${to}:${appName || ''}`;
|
|
143
|
-
return this.cache.getOrCompute(cacheKey, async () => {
|
|
144
|
-
let query = `SELECT COUNT(requests) AS "request_count", AVG(response_time) AS "avg_response_time", MAX(response_time) AS "max_response_time", PERCENTILE(response_time, 0.95) AS "p95", "app.name", "worker.id" FROM "mulesoft.app.inbound" WHERE "sub_org.id" = '${orgId}' AND "env.id" = '${envId}' AND timestamp BETWEEN ${from} AND ${to}`;
|
|
145
|
-
if (appName) {
|
|
146
|
-
query += ` AND "app.name" = '${appName}'`;
|
|
176
|
+
const result = [...rows.values()];
|
|
177
|
+
for (const entry of result) {
|
|
178
|
+
entry.failureRate = entry.requestCount > 0 ? entry.failedCount / entry.requestCount : 0;
|
|
147
179
|
}
|
|
148
|
-
|
|
149
|
-
const data = await this.search(query, 500);
|
|
150
|
-
return data.map((row) => ({
|
|
151
|
-
appName: String(row['app.name'] || 'Unknown'),
|
|
152
|
-
workerId: String(row['worker.id'] || 'Unknown'),
|
|
153
|
-
requestCount: Number(row['request_count'] || 0),
|
|
154
|
-
avgResponseTime: Number(row['avg_response_time'] || 0),
|
|
155
|
-
maxResponseTime: Number(row['max_response_time'] || 0),
|
|
156
|
-
p95: Number(row['p95'] || 0),
|
|
157
|
-
}));
|
|
180
|
+
return result.sort((a, b) => b.requestCount - a.requestCount || a.appName.localeCompare(b.appName));
|
|
158
181
|
});
|
|
159
182
|
}
|
|
160
|
-
/**
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
*/
|
|
164
|
-
async getMemoryMetrics(orgId, envId, from, to, appName) {
|
|
165
|
-
const cacheKey = `mon:memory:${orgId}:${envId}:${from}:${to}:${appName || ''}`;
|
|
183
|
+
/** Request and failure counts per HTTP route, inbound and outbound. */
|
|
184
|
+
async getRouteMetrics(scope) {
|
|
185
|
+
const cacheKey = `mon:routes:${this.scopeKey(scope)}`;
|
|
166
186
|
return this.cache.getOrCompute(cacheKey, async () => {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
187
|
+
const keys = [...this.groupKeys(scope, false), '"http.route"'];
|
|
188
|
+
const where = this.where(scope);
|
|
189
|
+
const failed = `${where} AND "response.status" = 'FAILED'`;
|
|
190
|
+
const select = keys.join(', ');
|
|
191
|
+
const groupBySql = `GROUP BY ${select}`;
|
|
192
|
+
const directions = ['inbound', 'outbound'];
|
|
193
|
+
const results = await Promise.all(directions.flatMap((direction) => [
|
|
194
|
+
this.searchAll(`SELECT COUNT(requests) AS "request_count", AVG(response_time) AS "avg_response_time", PERCENTILE(response_time, 0.95) AS "p95", ${select} FROM "mulesoft.app.${direction}" WHERE ${where} ${groupBySql}`),
|
|
195
|
+
this.searchAll(`SELECT COUNT(requests) AS "failed_count", ${select} FROM "mulesoft.app.${direction}" WHERE ${failed} ${groupBySql}`),
|
|
196
|
+
]));
|
|
197
|
+
const rows = new Map();
|
|
198
|
+
directions.forEach((direction, i) => {
|
|
199
|
+
const [totals, failures] = [results[i * 2], results[i * 2 + 1]];
|
|
200
|
+
const rowFor = (row) => {
|
|
201
|
+
const key = [direction, ...keys.map((k) => str(row[k.replace(/"/g, '')], ''))].join('\u0000');
|
|
202
|
+
let entry = rows.get(key);
|
|
203
|
+
if (!entry) {
|
|
204
|
+
const route = row['http.route'];
|
|
205
|
+
entry = {
|
|
206
|
+
appName: str(row['app.name']),
|
|
207
|
+
...(scope.envId ? {} : { envName: str(row['env.name']) }),
|
|
208
|
+
direction,
|
|
209
|
+
route: route === null || route === undefined || route === '' ? null : String(route),
|
|
210
|
+
requestCount: 0,
|
|
211
|
+
failedCount: 0,
|
|
212
|
+
avgResponseTime: 0,
|
|
213
|
+
p95: 0,
|
|
214
|
+
};
|
|
215
|
+
rows.set(key, entry);
|
|
216
|
+
}
|
|
217
|
+
return entry;
|
|
218
|
+
};
|
|
219
|
+
for (const row of totals) {
|
|
220
|
+
Object.assign(rowFor(row), {
|
|
221
|
+
requestCount: num(row['request_count']),
|
|
222
|
+
avgResponseTime: num(row['avg_response_time']),
|
|
223
|
+
p95: num(row['p95']),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
for (const row of failures)
|
|
227
|
+
rowFor(row).failedCount = num(row['failed_count']);
|
|
228
|
+
});
|
|
229
|
+
return [...rows.values()].sort((a, b) => b.failedCount - a.failedCount || b.requestCount - a.requestCount);
|
|
182
230
|
});
|
|
183
231
|
}
|
|
184
|
-
/**
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
*/
|
|
188
|
-
async getMemoryTimeSeries(orgId, envId, from, to, granularity = 'PT1H', appName) {
|
|
189
|
-
const cacheKey = `mon:memts:${orgId}:${envId}:${from}:${to}:${granularity}:${appName || ''}`;
|
|
232
|
+
/** JVM heap, old-generation pressure, GC activity and host CPU/RAM per worker. */
|
|
233
|
+
async getRuntimeMetrics(scope) {
|
|
234
|
+
const cacheKey = `mon:runtime:${this.scopeKey(scope)}`;
|
|
190
235
|
return this.cache.getOrCompute(cacheKey, async () => {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
236
|
+
const where = this.where(scope);
|
|
237
|
+
const workerKeys = `"app.name", "worker.id"`;
|
|
238
|
+
const [pools, gc, capacity, host] = await Promise.all([
|
|
239
|
+
this.searchAll(`SELECT AVG(usage) AS "used_avg", MAX(usage) AS "used_peak", AVG(committed) AS "committed_avg", MAX("limit") AS "limit_max", ${workerKeys}, "type", "pool" FROM "mulesoft.app.jvm.memory" WHERE ${where} GROUP BY ${workerKeys}, "type", "pool"`),
|
|
240
|
+
this.searchAll(`SELECT MAX(count) AS "count_max", MIN(count) AS "count_min", MAX(duration) AS "duration_max", MIN(duration) AS "duration_min", ${workerKeys}, name FROM "mulesoft.app.jvm.gc" WHERE ${where} GROUP BY ${workerKeys}, name`),
|
|
241
|
+
this.searchAll(`SELECT MAX(available_processors) AS "available_processors", AVG(total_physical_memory_size) AS "total_physical_memory", ${workerKeys} FROM "mulesoft.app.jvm.cpu" WHERE ${where} GROUP BY ${workerKeys}`),
|
|
242
|
+
this.searchAll(`SELECT AVG(system_cpu_load) AS "system_cpu_avg", MAX(system_cpu_load) AS "system_cpu_max", AVG(process_cpu_load) AS "process_cpu_avg", MAX(process_cpu_load) AS "process_cpu_max", AVG(free_physical_memory_size) AS "free_memory_avg", ${workerKeys} FROM "mulesoft.app.memory" WHERE ${where} GROUP BY ${workerKeys}`),
|
|
243
|
+
]);
|
|
244
|
+
const workers = new Map();
|
|
245
|
+
const workerFor = (row) => {
|
|
246
|
+
const appName = str(row['app.name']);
|
|
247
|
+
const workerId = str(row['worker.id']);
|
|
248
|
+
const key = `${appName}\u0000${workerId}`;
|
|
249
|
+
let entry = workers.get(key);
|
|
250
|
+
if (!entry) {
|
|
251
|
+
entry = {
|
|
252
|
+
appName,
|
|
253
|
+
workerId,
|
|
254
|
+
heapUsedAvg: 0,
|
|
255
|
+
heapUsedPeak: 0,
|
|
256
|
+
heapCommittedAvg: 0,
|
|
257
|
+
oldGenPool: null,
|
|
258
|
+
oldGenUsedAvg: 0,
|
|
259
|
+
oldGenUsedPeak: 0,
|
|
260
|
+
oldGenLimit: null,
|
|
261
|
+
oldGenPeakRatio: null,
|
|
262
|
+
metaspaceUsedAvg: 0,
|
|
263
|
+
oldGenGcCount: 0,
|
|
264
|
+
oldGenGcTimeMs: 0,
|
|
265
|
+
gcCollectors: [],
|
|
266
|
+
pools: [],
|
|
267
|
+
availableProcessors: null,
|
|
268
|
+
totalPhysicalMemory: null,
|
|
269
|
+
freePhysicalMemoryAvg: null,
|
|
270
|
+
systemCpuLoadAvg: null,
|
|
271
|
+
systemCpuLoadMax: null,
|
|
272
|
+
processCpuLoadAvg: null,
|
|
273
|
+
processCpuLoadMax: null,
|
|
274
|
+
};
|
|
275
|
+
workers.set(key, entry);
|
|
276
|
+
}
|
|
277
|
+
return entry;
|
|
278
|
+
};
|
|
279
|
+
for (const row of pools) {
|
|
280
|
+
if (row['pool'] === null || row['pool'] === undefined)
|
|
281
|
+
continue;
|
|
282
|
+
const limit = numOrNull(row['limit_max']);
|
|
283
|
+
workerFor(row).pools.push({
|
|
284
|
+
pool: str(row['pool']),
|
|
285
|
+
type: str(row['type']),
|
|
286
|
+
usedAvg: num(row['used_avg']),
|
|
287
|
+
usedPeak: num(row['used_peak']),
|
|
288
|
+
committedAvg: num(row['committed_avg']),
|
|
289
|
+
limit: limit !== null && limit > 0 ? limit : null,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
for (const row of gc) {
|
|
293
|
+
const countMax = numOrNull(row['count_max']);
|
|
294
|
+
if (countMax === null)
|
|
295
|
+
continue; // collector registered but never reported
|
|
296
|
+
const collector = str(row['name']);
|
|
297
|
+
workerFor(row).gcCollectors.push({
|
|
298
|
+
collector,
|
|
299
|
+
oldGeneration: OLD_GENERATION_COLLECTOR.test(collector),
|
|
300
|
+
collections: Math.max(countMax - num(row['count_min']), 0),
|
|
301
|
+
timeMs: Math.max(num(row['duration_max']) - num(row['duration_min']), 0),
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
for (const row of capacity) {
|
|
305
|
+
const entry = workerFor(row);
|
|
306
|
+
entry.availableProcessors = numOrNull(row['available_processors']);
|
|
307
|
+
entry.totalPhysicalMemory = numOrNull(row['total_physical_memory']);
|
|
194
308
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
309
|
+
for (const row of host) {
|
|
310
|
+
Object.assign(workerFor(row), {
|
|
311
|
+
systemCpuLoadAvg: numOrNull(row['system_cpu_avg']),
|
|
312
|
+
systemCpuLoadMax: numOrNull(row['system_cpu_max']),
|
|
313
|
+
processCpuLoadAvg: numOrNull(row['process_cpu_avg']),
|
|
314
|
+
processCpuLoadMax: numOrNull(row['process_cpu_max']),
|
|
315
|
+
freePhysicalMemoryAvg: numOrNull(row['free_memory_avg']),
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
for (const entry of workers.values()) {
|
|
319
|
+
const heapPools = entry.pools.filter((p) => p.type === 'heap');
|
|
320
|
+
const totalHeap = heapPools.find((p) => p.pool === 'total-heap');
|
|
321
|
+
const partitions = heapPools.filter((p) => p.pool !== 'total-heap');
|
|
322
|
+
entry.heapUsedAvg = totalHeap?.usedAvg ?? partitions.reduce((s, p) => s + p.usedAvg, 0);
|
|
323
|
+
entry.heapUsedPeak = totalHeap?.usedPeak ?? partitions.reduce((s, p) => s + p.usedPeak, 0);
|
|
324
|
+
entry.heapCommittedAvg = totalHeap?.committedAvg ?? partitions.reduce((s, p) => s + p.committedAvg, 0);
|
|
325
|
+
const oldGen = partitions.find((p) => OLD_GENERATION_POOL.test(p.pool));
|
|
326
|
+
if (oldGen) {
|
|
327
|
+
entry.oldGenPool = oldGen.pool;
|
|
328
|
+
entry.oldGenUsedAvg = oldGen.usedAvg;
|
|
329
|
+
entry.oldGenUsedPeak = oldGen.usedPeak;
|
|
330
|
+
entry.oldGenLimit = oldGen.limit;
|
|
331
|
+
entry.oldGenPeakRatio = oldGen.limit ? oldGen.usedPeak / oldGen.limit : null;
|
|
332
|
+
}
|
|
333
|
+
entry.metaspaceUsedAvg = entry.pools.find((p) => p.pool === 'metaspace')?.usedAvg ?? 0;
|
|
334
|
+
for (const collector of entry.gcCollectors.filter((c) => c.oldGeneration)) {
|
|
335
|
+
entry.oldGenGcCount += collector.collections;
|
|
336
|
+
entry.oldGenGcTimeMs += collector.timeMs;
|
|
337
|
+
}
|
|
338
|
+
entry.pools.sort((a, b) => a.type.localeCompare(b.type) || a.pool.localeCompare(b.pool));
|
|
339
|
+
}
|
|
340
|
+
return [...workers.values()].sort((a, b) => a.appName.localeCompare(b.appName) || a.workerId.localeCompare(b.workerId));
|
|
204
341
|
});
|
|
205
342
|
}
|
|
206
343
|
/**
|
|
207
|
-
*
|
|
344
|
+
* Bucketed metrics for one signal:
|
|
345
|
+
* - traffic: requests, failed, avg and p95 latency per app
|
|
346
|
+
* - latency: p50 / p75 / p90 / p95 / p99 per app
|
|
347
|
+
* - memory: heap, old-generation and metaspace usage per worker
|
|
348
|
+
* - cpu: system and process CPU load and average free physical memory per worker
|
|
349
|
+
* - gc: old-generation collections and GC time per worker (per-bucket deltas)
|
|
208
350
|
*/
|
|
209
|
-
async
|
|
210
|
-
const cacheKey = `mon:
|
|
351
|
+
async getTimeSeries(scope, signal, granularity = '1h') {
|
|
352
|
+
const cacheKey = `mon:ts:${this.scopeKey(scope)}:${signal}:${granularity}`;
|
|
211
353
|
return this.cache.getOrCompute(cacheKey, async () => {
|
|
212
|
-
const
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
354
|
+
const where = this.where(scope);
|
|
355
|
+
const ts = `TIMESERIES ${GRANULARITY[granularity]}`;
|
|
356
|
+
const app = `"app.name"`;
|
|
357
|
+
const worker = `"app.name", "worker.id"`;
|
|
358
|
+
switch (signal) {
|
|
359
|
+
case 'traffic': {
|
|
360
|
+
const [totals, failures] = await Promise.all([
|
|
361
|
+
this.searchAll(`SELECT timestamp, COUNT(requests) AS "request_count", AVG(response_time) AS "avg_response_time", PERCENTILE(response_time, 0.95) AS "p95", ${app} FROM "mulesoft.app.inbound" WHERE ${where} GROUP BY ${app} ${ts}`),
|
|
362
|
+
this.searchAll(`SELECT timestamp, COUNT(requests) AS "failed_count", ${app} FROM "mulesoft.app.inbound" WHERE ${where} AND "response.status" = 'FAILED' GROUP BY ${app} ${ts}`),
|
|
363
|
+
]);
|
|
364
|
+
return this.mergeSeries([
|
|
365
|
+
[
|
|
366
|
+
totals,
|
|
367
|
+
{ requestCount: 'request_count', avgResponseTime: 'avg_response_time', p95: 'p95' },
|
|
368
|
+
],
|
|
369
|
+
[failures, { failedCount: 'failed_count' }],
|
|
370
|
+
], false, { requestCount: 0, failedCount: 0 });
|
|
371
|
+
}
|
|
372
|
+
case 'latency': {
|
|
373
|
+
const rows = await this.searchAll(`SELECT timestamp, PERCENTILE(response_time, 0.5) AS "p50", PERCENTILE(response_time, 0.75) AS "p75", PERCENTILE(response_time, 0.9) AS "p90", PERCENTILE(response_time, 0.95) AS "p95", PERCENTILE(response_time, 0.99) AS "p99", ${app} FROM "mulesoft.app.inbound" WHERE ${where} GROUP BY ${app} ${ts}`);
|
|
374
|
+
return this.mergeSeries([[rows, { p50: 'p50', p75: 'p75', p90: 'p90', p95: 'p95', p99: 'p99' }]], false);
|
|
375
|
+
}
|
|
376
|
+
case 'memory': {
|
|
377
|
+
const rows = await this.searchAll(`SELECT timestamp, AVG(usage) AS "used", ${worker}, "pool" FROM "mulesoft.app.jvm.memory" WHERE ${where} GROUP BY ${worker}, "pool" ${ts}`);
|
|
378
|
+
const heap = rows.filter((r) => r['pool'] === 'total-heap');
|
|
379
|
+
const oldGen = rows.filter((r) => OLD_GENERATION_POOL.test(str(r['pool'], '')));
|
|
380
|
+
const metaspace = rows.filter((r) => r['pool'] === 'metaspace');
|
|
381
|
+
return this.mergeSeries([
|
|
382
|
+
[heap, { heapUsed: 'used' }],
|
|
383
|
+
[oldGen, { oldGenUsed: 'used' }],
|
|
384
|
+
[metaspace, { metaspaceUsed: 'used' }],
|
|
385
|
+
], true);
|
|
386
|
+
}
|
|
387
|
+
case 'cpu': {
|
|
388
|
+
const rows = await this.searchAll(`SELECT timestamp, AVG(system_cpu_load) AS "system_cpu", AVG(process_cpu_load) AS "process_cpu", AVG(free_physical_memory_size) AS "free_memory", ${worker} FROM "mulesoft.app.memory" WHERE ${where} GROUP BY ${worker} ${ts}`);
|
|
389
|
+
return this.mergeSeries([
|
|
390
|
+
[
|
|
391
|
+
rows,
|
|
392
|
+
{
|
|
393
|
+
systemCpuLoad: 'system_cpu',
|
|
394
|
+
processCpuLoad: 'process_cpu',
|
|
395
|
+
freePhysicalMemory: 'free_memory',
|
|
396
|
+
},
|
|
397
|
+
],
|
|
398
|
+
], true);
|
|
399
|
+
}
|
|
400
|
+
case 'gc': {
|
|
401
|
+
const rows = await this.searchAll(`SELECT timestamp, MAX(count) AS "count_max", MIN(count) AS "count_min", MAX(duration) AS "duration_max", MIN(duration) AS "duration_min", ${worker}, name FROM "mulesoft.app.jvm.gc" WHERE ${where} GROUP BY ${worker}, name ${ts}`);
|
|
402
|
+
return this.gcDeltas(rows);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
222
405
|
});
|
|
223
406
|
}
|
|
224
|
-
/**
|
|
225
|
-
* Build a full metrics export for a period
|
|
226
|
-
*/
|
|
407
|
+
/** Build a metrics export for a period (one environment, every app). */
|
|
227
408
|
async exportMetrics(orgId, envId, envName, from, to) {
|
|
228
|
-
const apps = await this.
|
|
409
|
+
const apps = await this.getMetrics({ orgId, envId, from, to });
|
|
229
410
|
const totalRequests = apps.reduce((sum, m) => sum + m.requestCount, 0);
|
|
230
|
-
const
|
|
231
|
-
|
|
411
|
+
const totalFailed = apps.reduce((sum, m) => sum + m.failedCount, 0);
|
|
412
|
+
const avgResponseTime = totalRequests > 0
|
|
413
|
+
? apps.reduce((sum, m) => sum + m.avgResponseTime * m.requestCount, 0) / totalRequests
|
|
232
414
|
: 0;
|
|
233
415
|
return {
|
|
234
416
|
environment: envName,
|
|
235
|
-
period: {
|
|
236
|
-
from: new Date(from).toISOString(),
|
|
237
|
-
to: new Date(to).toISOString(),
|
|
238
|
-
},
|
|
417
|
+
period: { from: new Date(from).toISOString(), to: new Date(to).toISOString() },
|
|
239
418
|
apps,
|
|
240
|
-
summary: {
|
|
241
|
-
totalRequests,
|
|
242
|
-
avgResponseTime,
|
|
243
|
-
},
|
|
419
|
+
summary: { totalRequests, totalFailed, avgResponseTime },
|
|
244
420
|
};
|
|
245
421
|
}
|
|
422
|
+
where(scope) {
|
|
423
|
+
assertUuid(scope.orgId, 'organization ID');
|
|
424
|
+
const clauses = [`"sub_org.id" = ${amqlString(scope.orgId)}`];
|
|
425
|
+
if (scope.envId) {
|
|
426
|
+
assertUuid(scope.envId, 'environment ID');
|
|
427
|
+
clauses.push(`"env.id" = ${amqlString(scope.envId)}`);
|
|
428
|
+
}
|
|
429
|
+
clauses.push(`timestamp BETWEEN ${Math.trunc(scope.from)} AND ${Math.trunc(scope.to)}`);
|
|
430
|
+
if (scope.appName)
|
|
431
|
+
clauses.push(`"app.name" = ${amqlString(scope.appName)}`);
|
|
432
|
+
return clauses.join(' AND ');
|
|
433
|
+
}
|
|
434
|
+
groupKeys(scope, byWorker) {
|
|
435
|
+
return ['"app.name"', ...(scope.envId ? [] : ['"env.name"']), ...(byWorker ? ['"worker.id"'] : [])];
|
|
436
|
+
}
|
|
437
|
+
scopeKey(scope) {
|
|
438
|
+
return [scope.orgId, scope.envId ?? '*', scope.from, scope.to, scope.appName ?? ''].join(':');
|
|
439
|
+
}
|
|
440
|
+
/** Merge several bucketed result sets into one point per timestamp + app (+ worker). */
|
|
441
|
+
mergeSeries(sources, byWorker, defaults = {}) {
|
|
442
|
+
const points = new Map();
|
|
443
|
+
for (const [rows, fields] of sources) {
|
|
444
|
+
for (const row of rows) {
|
|
445
|
+
const timestamp = num(row['timestamp']);
|
|
446
|
+
const appName = str(row['app.name']);
|
|
447
|
+
const workerId = byWorker ? str(row['worker.id']) : undefined;
|
|
448
|
+
const key = `${timestamp}\u0000${appName}\u0000${workerId ?? ''}`;
|
|
449
|
+
let point = points.get(key);
|
|
450
|
+
if (!point) {
|
|
451
|
+
point = { timestamp, appName, ...(byWorker ? { workerId } : {}), ...defaults };
|
|
452
|
+
points.set(key, point);
|
|
453
|
+
}
|
|
454
|
+
for (const [target, source] of Object.entries(fields))
|
|
455
|
+
point[target] = numOrNull(row[source]);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return [...points.values()].sort((a, b) => a.timestamp - b.timestamp ||
|
|
459
|
+
a.appName.localeCompare(b.appName) ||
|
|
460
|
+
String(a.workerId ?? '').localeCompare(String(b.workerId ?? '')));
|
|
461
|
+
}
|
|
246
462
|
/**
|
|
247
|
-
*
|
|
463
|
+
* Turn cumulative GC counters into per-bucket activity. Each bucket's delta is measured from the
|
|
464
|
+
* previous bucket's maximum, so collections that straddle a boundary are still counted; the first
|
|
465
|
+
* bucket of a worker falls back to its own MAX − MIN.
|
|
248
466
|
*/
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
467
|
+
gcDeltas(rows) {
|
|
468
|
+
const series = new Map();
|
|
469
|
+
for (const row of rows) {
|
|
470
|
+
if (numOrNull(row['count_max']) === null)
|
|
471
|
+
continue;
|
|
472
|
+
const key = `${str(row['app.name'])}\u0000${str(row['worker.id'])}\u0000${str(row['name'])}`;
|
|
473
|
+
const list = series.get(key) ?? [];
|
|
474
|
+
list.push(row);
|
|
475
|
+
series.set(key, list);
|
|
253
476
|
}
|
|
254
|
-
|
|
255
|
-
|
|
477
|
+
const points = new Map();
|
|
478
|
+
for (const list of series.values()) {
|
|
479
|
+
list.sort((a, b) => num(a['timestamp']) - num(b['timestamp']));
|
|
480
|
+
let previousCount = null;
|
|
481
|
+
let previousDuration = null;
|
|
482
|
+
for (const row of list) {
|
|
483
|
+
const countMax = num(row['count_max']);
|
|
484
|
+
const durationMax = num(row['duration_max']);
|
|
485
|
+
// A counter that goes backwards means the JVM restarted inside the worker.
|
|
486
|
+
const countBase = previousCount !== null && previousCount <= countMax ? previousCount : num(row['count_min']);
|
|
487
|
+
const durationBase = previousDuration !== null && previousDuration <= durationMax
|
|
488
|
+
? previousDuration
|
|
489
|
+
: num(row['duration_min']);
|
|
490
|
+
previousCount = countMax;
|
|
491
|
+
previousDuration = durationMax;
|
|
492
|
+
const collector = str(row['name']);
|
|
493
|
+
const oldGeneration = OLD_GENERATION_COLLECTOR.test(collector);
|
|
494
|
+
const timestamp = num(row['timestamp']);
|
|
495
|
+
const appName = str(row['app.name']);
|
|
496
|
+
const workerId = str(row['worker.id']);
|
|
497
|
+
const key = `${timestamp}\u0000${appName}\u0000${workerId}`;
|
|
498
|
+
let point = points.get(key);
|
|
499
|
+
if (!point) {
|
|
500
|
+
point = {
|
|
501
|
+
timestamp,
|
|
502
|
+
appName,
|
|
503
|
+
workerId,
|
|
504
|
+
oldGenGcCount: 0,
|
|
505
|
+
oldGenGcTimeMs: 0,
|
|
506
|
+
gcCount: 0,
|
|
507
|
+
gcTimeMs: 0,
|
|
508
|
+
};
|
|
509
|
+
points.set(key, point);
|
|
510
|
+
}
|
|
511
|
+
const collections = Math.max(countMax - countBase, 0);
|
|
512
|
+
const timeMs = Math.max(durationMax - durationBase, 0);
|
|
513
|
+
point.gcCount = num(point.gcCount) + collections;
|
|
514
|
+
point.gcTimeMs = num(point.gcTimeMs) + timeMs;
|
|
515
|
+
if (oldGeneration) {
|
|
516
|
+
point.oldGenGcCount = num(point.oldGenGcCount) + collections;
|
|
517
|
+
point.oldGenGcTimeMs = num(point.oldGenGcTimeMs) + timeMs;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
256
520
|
}
|
|
521
|
+
return [...points.values()].sort((a, b) => a.timestamp - b.timestamp ||
|
|
522
|
+
a.appName.localeCompare(b.appName) ||
|
|
523
|
+
String(a.workerId).localeCompare(String(b.workerId)));
|
|
257
524
|
}
|
|
258
525
|
}
|
|
259
526
|
//# sourceMappingURL=MonitoringApi.js.map
|