@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.
Files changed (278) hide show
  1. package/README.md +39 -16
  2. package/dist/analysis/LogAnalyzer.d.ts +10 -4
  3. package/dist/analysis/LogAnalyzer.d.ts.map +1 -1
  4. package/dist/analysis/LogAnalyzer.js +29 -18
  5. package/dist/analysis/LogAnalyzer.js.map +1 -1
  6. package/dist/analysis/error-context.d.ts +2 -0
  7. package/dist/analysis/error-context.d.ts.map +1 -1
  8. package/dist/analysis/error-context.js +2 -1
  9. package/dist/analysis/error-context.js.map +1 -1
  10. package/dist/analysis/error-grouper.js +1 -1
  11. package/dist/analysis/error-grouper.js.map +1 -1
  12. package/dist/analysis/filter.d.ts +54 -0
  13. package/dist/analysis/filter.d.ts.map +1 -0
  14. package/dist/analysis/filter.js +100 -0
  15. package/dist/analysis/filter.js.map +1 -0
  16. package/dist/analysis/{utils.d.ts → normalize.d.ts} +1 -1
  17. package/dist/analysis/normalize.d.ts.map +1 -0
  18. package/dist/analysis/{utils.js → normalize.js} +2 -2
  19. package/dist/analysis/normalize.js.map +1 -0
  20. package/dist/analysis/parser.d.ts.map +1 -1
  21. package/dist/analysis/parser.js +45 -17
  22. package/dist/analysis/parser.js.map +1 -1
  23. package/dist/analysis/pattern-detector.js +1 -1
  24. package/dist/analysis/pattern-detector.js.map +1 -1
  25. package/dist/analysis/stats.d.ts +25 -0
  26. package/dist/analysis/stats.d.ts.map +1 -1
  27. package/dist/analysis/stats.js +35 -1
  28. package/dist/analysis/stats.js.map +1 -1
  29. package/dist/analysis/summary.d.ts +51 -0
  30. package/dist/analysis/summary.d.ts.map +1 -0
  31. package/dist/analysis/summary.js +47 -0
  32. package/dist/analysis/summary.js.map +1 -0
  33. package/dist/analysis/types.d.ts +14 -0
  34. package/dist/analysis/types.d.ts.map +1 -1
  35. package/dist/api/ApiManagerApi.d.ts +53 -0
  36. package/dist/api/ApiManagerApi.d.ts.map +1 -1
  37. package/dist/api/ApiManagerApi.js +53 -2
  38. package/dist/api/ApiManagerApi.js.map +1 -1
  39. package/dist/api/CloudHub2Api.d.ts +24 -4
  40. package/dist/api/CloudHub2Api.d.ts.map +1 -1
  41. package/dist/api/CloudHub2Api.js +10 -22
  42. package/dist/api/CloudHub2Api.js.map +1 -1
  43. package/dist/api/DesignCenterApi.d.ts +0 -20
  44. package/dist/api/DesignCenterApi.d.ts.map +1 -1
  45. package/dist/api/DesignCenterApi.js +0 -22
  46. package/dist/api/DesignCenterApi.js.map +1 -1
  47. package/dist/api/ExchangeApi.d.ts +15 -0
  48. package/dist/api/ExchangeApi.d.ts.map +1 -1
  49. package/dist/api/ExchangeApi.js +51 -0
  50. package/dist/api/ExchangeApi.js.map +1 -1
  51. package/dist/api/LogsApi.d.ts +50 -18
  52. package/dist/api/LogsApi.d.ts.map +1 -1
  53. package/dist/api/LogsApi.js +69 -98
  54. package/dist/api/LogsApi.js.map +1 -1
  55. package/dist/api/MonitoringApi.d.ts +137 -93
  56. package/dist/api/MonitoringApi.d.ts.map +1 -1
  57. package/dist/api/MonitoringApi.js +471 -204
  58. package/dist/api/MonitoringApi.js.map +1 -1
  59. package/dist/api/RuntimesApi.d.ts +56 -0
  60. package/dist/api/RuntimesApi.d.ts.map +1 -0
  61. package/dist/api/RuntimesApi.js +58 -0
  62. package/dist/api/RuntimesApi.js.map +1 -0
  63. package/dist/auth/FileStore.js +1 -1
  64. package/dist/auth/FileStore.js.map +1 -1
  65. package/dist/cli.js +4 -2
  66. package/dist/cli.js.map +1 -1
  67. package/dist/client/AnypointClient.d.ts +3 -1
  68. package/dist/client/AnypointClient.d.ts.map +1 -1
  69. package/dist/client/AnypointClient.js +5 -2
  70. package/dist/client/AnypointClient.js.map +1 -1
  71. package/dist/client/smoke-test.d.ts +111 -0
  72. package/dist/client/smoke-test.d.ts.map +1 -0
  73. package/dist/client/smoke-test.js +275 -0
  74. package/dist/client/smoke-test.js.map +1 -0
  75. package/dist/commands/api.d.ts +1 -1
  76. package/dist/commands/api.d.ts.map +1 -1
  77. package/dist/commands/api.js +175 -30
  78. package/dist/commands/api.js.map +1 -1
  79. package/dist/commands/app-change-cli.d.ts +52 -0
  80. package/dist/commands/app-change-cli.d.ts.map +1 -0
  81. package/dist/commands/app-change-cli.js +149 -0
  82. package/dist/commands/app-change-cli.js.map +1 -0
  83. package/dist/commands/apps.d.ts +1 -1
  84. package/dist/commands/apps.d.ts.map +1 -1
  85. package/dist/commands/apps.js +199 -18
  86. package/dist/commands/apps.js.map +1 -1
  87. package/dist/commands/auth.js +1 -1
  88. package/dist/commands/auth.js.map +1 -1
  89. package/dist/commands/compare.d.ts +8 -0
  90. package/dist/commands/compare.d.ts.map +1 -0
  91. package/dist/commands/compare.js +104 -0
  92. package/dist/commands/compare.js.map +1 -0
  93. package/dist/commands/config.js +1 -1
  94. package/dist/commands/config.js.map +1 -1
  95. package/dist/commands/deploy.d.ts +6 -1
  96. package/dist/commands/deploy.d.ts.map +1 -1
  97. package/dist/commands/deploy.js +89 -91
  98. package/dist/commands/deploy.js.map +1 -1
  99. package/dist/commands/design-center.d.ts +1 -1
  100. package/dist/commands/design-center.d.ts.map +1 -1
  101. package/dist/commands/design-center.js +36 -25
  102. package/dist/commands/design-center.js.map +1 -1
  103. package/dist/commands/index.d.ts +1 -0
  104. package/dist/commands/index.d.ts.map +1 -1
  105. package/dist/commands/index.js +1 -0
  106. package/dist/commands/index.js.map +1 -1
  107. package/dist/commands/logs.d.ts +3 -2
  108. package/dist/commands/logs.d.ts.map +1 -1
  109. package/dist/commands/logs.js +142 -17
  110. package/dist/commands/logs.js.map +1 -1
  111. package/dist/commands/monitor.d.ts +5 -6
  112. package/dist/commands/monitor.d.ts.map +1 -1
  113. package/dist/commands/monitor.js +210 -230
  114. package/dist/commands/monitor.js.map +1 -1
  115. package/dist/commands/runtimes.d.ts +7 -0
  116. package/dist/commands/runtimes.d.ts.map +1 -0
  117. package/dist/commands/runtimes.js +66 -0
  118. package/dist/commands/runtimes.js.map +1 -0
  119. package/dist/commands/shared.d.ts +2 -0
  120. package/dist/commands/shared.d.ts.map +1 -1
  121. package/dist/commands/shared.js +16 -1
  122. package/dist/commands/shared.js.map +1 -1
  123. package/dist/{utils/config.d.ts → config/profiles.d.ts} +9 -1
  124. package/dist/config/profiles.d.ts.map +1 -0
  125. package/dist/{utils/config.js → config/profiles.js} +1 -1
  126. package/dist/config/profiles.js.map +1 -0
  127. package/dist/index.d.ts +12 -3
  128. package/dist/index.d.ts.map +1 -1
  129. package/dist/index.js +12 -2
  130. package/dist/index.js.map +1 -1
  131. package/dist/mcp/prompts.js +7 -7
  132. package/dist/mcp/prompts.js.map +1 -1
  133. package/dist/mcp/server.d.ts +14 -0
  134. package/dist/mcp/server.d.ts.map +1 -0
  135. package/dist/mcp/server.js +61 -0
  136. package/dist/mcp/server.js.map +1 -0
  137. package/dist/mcp/tools/analysis.d.ts.map +1 -1
  138. package/dist/mcp/tools/analysis.js +74 -46
  139. package/dist/mcp/tools/analysis.js.map +1 -1
  140. package/dist/mcp/tools/anypoint-mq.d.ts.map +1 -1
  141. package/dist/mcp/tools/anypoint-mq.js +25 -54
  142. package/dist/mcp/tools/anypoint-mq.js.map +1 -1
  143. package/dist/mcp/tools/api-manager.d.ts +2 -1
  144. package/dist/mcp/tools/api-manager.d.ts.map +1 -1
  145. package/dist/mcp/tools/api-manager.js +154 -89
  146. package/dist/mcp/tools/api-manager.js.map +1 -1
  147. package/dist/mcp/tools/app-inputs.d.ts +92 -0
  148. package/dist/mcp/tools/app-inputs.d.ts.map +1 -0
  149. package/dist/mcp/tools/app-inputs.js +142 -0
  150. package/dist/mcp/tools/app-inputs.js.map +1 -0
  151. package/dist/mcp/tools/apps-deploy.d.ts +13 -0
  152. package/dist/mcp/tools/apps-deploy.d.ts.map +1 -0
  153. package/dist/mcp/tools/apps-deploy.js +358 -0
  154. package/dist/mcp/tools/apps-deploy.js.map +1 -0
  155. package/dist/mcp/tools/apps-lifecycle.d.ts +11 -0
  156. package/dist/mcp/tools/apps-lifecycle.d.ts.map +1 -0
  157. package/dist/mcp/tools/apps-lifecycle.js +244 -0
  158. package/dist/mcp/tools/apps-lifecycle.js.map +1 -0
  159. package/dist/mcp/tools/apps-read.d.ts +9 -0
  160. package/dist/mcp/tools/apps-read.d.ts.map +1 -0
  161. package/dist/mcp/tools/apps-read.js +414 -0
  162. package/dist/mcp/tools/apps-read.js.map +1 -0
  163. package/dist/mcp/tools/artifacts.d.ts +8 -0
  164. package/dist/mcp/tools/artifacts.d.ts.map +1 -0
  165. package/dist/mcp/tools/artifacts.js +81 -0
  166. package/dist/mcp/tools/artifacts.js.map +1 -0
  167. package/dist/mcp/tools/audit.d.ts.map +1 -1
  168. package/dist/mcp/tools/audit.js +9 -17
  169. package/dist/mcp/tools/audit.js.map +1 -1
  170. package/dist/mcp/tools/design-center.d.ts +2 -3
  171. package/dist/mcp/tools/design-center.d.ts.map +1 -1
  172. package/dist/mcp/tools/design-center.js +41 -185
  173. package/dist/mcp/tools/design-center.js.map +1 -1
  174. package/dist/mcp/tools/exchange.d.ts +1 -2
  175. package/dist/mcp/tools/exchange.d.ts.map +1 -1
  176. package/dist/mcp/tools/exchange.js +51 -312
  177. package/dist/mcp/tools/exchange.js.map +1 -1
  178. package/dist/mcp/tools/governance.d.ts +8 -0
  179. package/dist/mcp/tools/governance.d.ts.map +1 -0
  180. package/dist/mcp/tools/governance.js +59 -0
  181. package/dist/mcp/tools/governance.js.map +1 -0
  182. package/dist/mcp/tools/identity.d.ts.map +1 -1
  183. package/dist/mcp/tools/identity.js +15 -36
  184. package/dist/mcp/tools/identity.js.map +1 -1
  185. package/dist/mcp/tools/index.d.ts +5 -1
  186. package/dist/mcp/tools/index.d.ts.map +1 -1
  187. package/dist/mcp/tools/index.js +5 -1
  188. package/dist/mcp/tools/index.js.map +1 -1
  189. package/dist/mcp/tools/logs.d.ts +48 -2
  190. package/dist/mcp/tools/logs.d.ts.map +1 -1
  191. package/dist/mcp/tools/logs.js +204 -54
  192. package/dist/mcp/tools/logs.js.map +1 -1
  193. package/dist/mcp/tools/monitoring.d.ts +1 -2
  194. package/dist/mcp/tools/monitoring.d.ts.map +1 -1
  195. package/dist/mcp/tools/monitoring.js +78 -285
  196. package/dist/mcp/tools/monitoring.js.map +1 -1
  197. package/dist/mcp/tools/object-store.d.ts +1 -1
  198. package/dist/mcp/tools/object-store.d.ts.map +1 -1
  199. package/dist/mcp/tools/object-store.js +19 -59
  200. package/dist/mcp/tools/object-store.js.map +1 -1
  201. package/dist/mcp/tools/profile.d.ts.map +1 -1
  202. package/dist/mcp/tools/profile.js +15 -35
  203. package/dist/mcp/tools/profile.js.map +1 -1
  204. package/dist/mcp/tools/shared.d.ts +43 -0
  205. package/dist/mcp/tools/shared.d.ts.map +1 -1
  206. package/dist/mcp/tools/shared.js +54 -0
  207. package/dist/mcp/tools/shared.js.map +1 -1
  208. package/dist/mcp.d.ts +3 -11
  209. package/dist/mcp.d.ts.map +1 -1
  210. package/dist/mcp.js +4 -56
  211. package/dist/mcp.js.map +1 -1
  212. package/dist/safety/deployment.d.ts +47 -2
  213. package/dist/safety/deployment.d.ts.map +1 -1
  214. package/dist/safety/deployment.js +139 -6
  215. package/dist/safety/deployment.js.map +1 -1
  216. package/dist/safety/guards.d.ts +2 -2
  217. package/dist/safety/guards.d.ts.map +1 -1
  218. package/dist/safety/guards.js +4 -4
  219. package/dist/safety/guards.js.map +1 -1
  220. package/dist/safety/paths.d.ts +29 -0
  221. package/dist/safety/paths.d.ts.map +1 -0
  222. package/dist/safety/paths.js +76 -0
  223. package/dist/safety/paths.js.map +1 -0
  224. package/dist/safety/runtime.d.ts +24 -0
  225. package/dist/safety/runtime.d.ts.map +1 -0
  226. package/dist/safety/runtime.js +59 -0
  227. package/dist/safety/runtime.js.map +1 -0
  228. package/dist/safety/secret-sources.d.ts +51 -0
  229. package/dist/safety/secret-sources.d.ts.map +1 -0
  230. package/dist/safety/secret-sources.js +124 -0
  231. package/dist/safety/secret-sources.js.map +1 -0
  232. package/dist/safety/secrets.d.ts +32 -0
  233. package/dist/safety/secrets.d.ts.map +1 -0
  234. package/dist/safety/secrets.js +91 -0
  235. package/dist/safety/secrets.js.map +1 -0
  236. package/dist/utils/errors.d.ts +14 -1
  237. package/dist/utils/errors.d.ts.map +1 -1
  238. package/dist/utils/errors.js +31 -3
  239. package/dist/utils/errors.js.map +1 -1
  240. package/dist/workflows/app-change.d.ts +124 -0
  241. package/dist/workflows/app-change.d.ts.map +1 -0
  242. package/dist/workflows/app-change.js +305 -0
  243. package/dist/workflows/app-change.js.map +1 -0
  244. package/dist/workflows/app-state.d.ts +60 -0
  245. package/dist/workflows/app-state.d.ts.map +1 -0
  246. package/dist/workflows/app-state.js +95 -0
  247. package/dist/workflows/app-state.js.map +1 -0
  248. package/dist/workflows/artifact-compare.d.ts +134 -0
  249. package/dist/workflows/artifact-compare.d.ts.map +1 -0
  250. package/dist/workflows/artifact-compare.js +419 -0
  251. package/dist/workflows/artifact-compare.js.map +1 -0
  252. package/dist/workflows/asset-naming.d.ts +72 -0
  253. package/dist/workflows/asset-naming.d.ts.map +1 -0
  254. package/dist/workflows/asset-naming.js +116 -0
  255. package/dist/workflows/asset-naming.js.map +1 -0
  256. package/dist/{api/DesignCenterWorkflow.d.ts → workflows/design-center.d.ts} +4 -4
  257. package/dist/workflows/design-center.d.ts.map +1 -0
  258. package/dist/{api/DesignCenterWorkflow.js → workflows/design-center.js} +1 -1
  259. package/dist/workflows/design-center.js.map +1 -0
  260. package/dist/workflows/jar-deployment.d.ts +156 -0
  261. package/dist/workflows/jar-deployment.d.ts.map +1 -0
  262. package/dist/workflows/jar-deployment.js +178 -0
  263. package/dist/workflows/jar-deployment.js.map +1 -0
  264. package/package.json +12 -4
  265. package/dist/analysis/utils.d.ts.map +0 -1
  266. package/dist/analysis/utils.js.map +0 -1
  267. package/dist/api/DesignCenterWorkflow.d.ts.map +0 -1
  268. package/dist/api/DesignCenterWorkflow.js.map +0 -1
  269. package/dist/api/index.d.ts +0 -13
  270. package/dist/api/index.d.ts.map +0 -1
  271. package/dist/api/index.js +0 -13
  272. package/dist/api/index.js.map +0 -1
  273. package/dist/mcp/tools/applications.d.ts +0 -4
  274. package/dist/mcp/tools/applications.d.ts.map +0 -1
  275. package/dist/mcp/tools/applications.js +0 -815
  276. package/dist/mcp/tools/applications.js.map +0 -1
  277. package/dist/utils/config.d.ts.map +0 -1
  278. package/dist/utils/config.js.map +0 -1
@@ -1,7 +1,63 @@
1
1
  /**
2
2
  * Monitoring API
3
- * AMQL-based metrics queries with configurable time ranges
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, limit = 200) {
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
- const response = await this.http.post(`${this.baseUrl}/metrics:search?limit=${limit}&offset=0`, { query });
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
- return [];
96
+ catch (error) {
97
+ throw AmqlQueryError.from(error, query);
23
98
  }
24
99
  }
25
100
  /**
26
- * Get inbound metrics for a time range.
27
- * Note: orgId, envId, and timestamps are system-controlled values from the
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 getOutboundMetrics(orgId, envId, from, to) {
46
- const cacheKey = `mon:outbound:${orgId}:${envId}:${from}:${to}`;
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 query = `SELECT COUNT(requests) AS "request_count", AVG(response_time) AS "avg_response_time", "app.name" FROM "mulesoft.app.outbound" WHERE "sub_org.id" = '${orgId}' AND "env.id" = '${envId}' AND timestamp BETWEEN ${from} AND ${to} GROUP BY "app.name"`;
49
- const data = await this.search(query);
50
- return data.map((row) => ({
51
- appName: String(row['app.name'] || 'Unknown'),
52
- requestCount: Number(row['request_count'] || 0),
53
- avgResponseTime: Number(row['avg_response_time'] || 0),
54
- }));
55
- });
56
- }
57
- /**
58
- * Get combined app metrics for a time range
59
- */
60
- async getAppMetrics(orgId, envId, from, to, appName) {
61
- const [inbound, outbound] = await Promise.all([
62
- this.getInboundMetrics(orgId, envId, from, to),
63
- this.getOutboundMetrics(orgId, envId, from, to),
64
- ]);
65
- const outboundByApp = new Map(outbound.map((o) => [o.appName, o]));
66
- let results = inbound.map((row) => {
67
- const ob = outboundByApp.get(row.appName);
68
- return {
69
- appName: row.appName,
70
- requestCount: row.requestCount,
71
- avgResponseTime: row.avgResponseTime,
72
- outboundCount: ob?.requestCount || 0,
73
- outboundAvgResponseTime: ob?.avgResponseTime || 0,
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
- // Add outbound-only apps
77
- for (const ob of outbound) {
78
- if (!inbound.some((ib) => ib.appName === ob.appName)) {
79
- results.push({
80
- appName: ob.appName,
81
- requestCount: 0,
82
- avgResponseTime: 0,
83
- outboundCount: ob.requestCount,
84
- outboundAvgResponseTime: ob.avgResponseTime,
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
- if (appName) {
89
- results = results.filter((r) => r.appName.toLowerCase() === appName.toLowerCase());
90
- }
91
- return results;
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
- query += ` GROUP BY "app.name"`;
104
- const data = await this.search(query);
105
- return data.map((row) => ({
106
- appName: String(row['app.name'] || 'Unknown'),
107
- requestCount: Number(row['request_count'] || 0),
108
- avgResponseTime: Number(row['avg_response_time'] || 0),
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
- query += ` GROUP BY "app.name" TIMESERIES ${granularity}`;
128
- const data = await this.search(query, 1000);
129
- return data.map((row) => ({
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
- query += ` GROUP BY "app.name", "worker.id"`;
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
- * Get JVM memory metrics per app (heap, GC, threads)
162
- * Queries the mulesoft.jvm datasource
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
- let query = `SELECT AVG(heap_used) AS "heap_used", AVG(heap_committed) AS "heap_committed", MAX(heap_total) AS "heap_max", SUM("gc.count") AS "gc_count", AVG("gc.time") AS "gc_time", AVG(thread_count) AS "thread_count", "app.name" FROM "mulesoft.jvm" WHERE "sub_org.id" = '${orgId}' AND "env.id" = '${envId}' AND timestamp BETWEEN ${from} AND ${to}`;
168
- if (appName) {
169
- query += ` AND "app.name" = '${appName}'`;
170
- }
171
- query += ` GROUP BY "app.name"`;
172
- const data = await this.search(query);
173
- return data.map((row) => ({
174
- appName: String(row['app.name'] || 'Unknown'),
175
- heapUsed: Number(row['heap_used'] || 0),
176
- heapCommitted: Number(row['heap_committed'] || 0),
177
- heapMax: Number(row['heap_max'] || 0),
178
- gcCount: Number(row['gc_count'] || 0),
179
- gcTime: Number(row['gc_time'] || 0),
180
- threadCount: Number(row['thread_count'] || 0),
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
- * Get JVM memory time-series for trending analysis
186
- * Queries the mulesoft.jvm datasource with TIMESERIES
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
- let query = `SELECT timestamp, AVG(heap_used) AS "heap_used", AVG(heap_committed) AS "heap_committed", SUM("gc.count") AS "gc_count", "app.name" FROM "mulesoft.jvm" WHERE "sub_org.id" = '${orgId}' AND "env.id" = '${envId}' AND timestamp BETWEEN ${from} AND ${to}`;
192
- if (appName) {
193
- query += ` AND "app.name" = '${appName}'`;
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
- query += ` GROUP BY "app.name" TIMESERIES ${granularity}`;
196
- const data = await this.search(query, 1000);
197
- return data.map((row) => ({
198
- timestamp: Number(row['timestamp'] || 0),
199
- appName: String(row['app.name'] || 'Unknown'),
200
- heapUsed: Number(row['heap_used'] || 0),
201
- heapCommitted: Number(row['heap_committed'] || 0),
202
- gcCount: Number(row['gc_count'] || 0),
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
- * Get cross-environment metrics comparison (no env filter)
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 getCrossEnvMetrics(orgId, from, to) {
210
- const cacheKey = `mon:crossenv:${orgId}:${from}:${to}`;
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 query = `SELECT COUNT(requests) AS "request_count", AVG(response_time) AS "avg_response_time", PERCENTILE(response_time, 0.95) AS "p95", PERCENTILE(response_time, 0.99) AS "p99", "app.name", "env.name" FROM "mulesoft.app.inbound" WHERE "sub_org.id" = '${orgId}' AND timestamp BETWEEN ${from} AND ${to} GROUP BY "app.name", "env.name"`;
213
- const data = await this.search(query, 500);
214
- return data.map((row) => ({
215
- appName: String(row['app.name'] || 'Unknown'),
216
- envName: String(row['env.name'] || 'Unknown'),
217
- requestCount: Number(row['request_count'] || 0),
218
- avgResponseTime: Number(row['avg_response_time'] || 0),
219
- p95: Number(row['p95'] || 0),
220
- p99: Number(row['p99'] || 0),
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.getAppMetrics(orgId, envId, from, to);
409
+ const apps = await this.getMetrics({ orgId, envId, from, to });
229
410
  const totalRequests = apps.reduce((sum, m) => sum + m.requestCount, 0);
230
- const avgResponseTime = apps.length > 0
231
- ? apps.reduce((sum, m) => sum + m.avgResponseTime * m.requestCount, 0) / (totalRequests || 1)
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
- * Check if monitoring API is available
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
- async isAvailable() {
250
- try {
251
- await this.search('SELECT COUNT(requests) FROM "mulesoft.app.inbound" LIMIT 1');
252
- return true;
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
- catch {
255
- return false;
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