@pagopa/dx-savemoney 0.1.1 → 0.1.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 CHANGED
@@ -12,6 +12,52 @@ Full support for Azure resource analysis with intelligent detection and flexible
12
12
 
13
13
  AWS support is planned for future releases. The architecture is designed to support multiple CSPs with provider-specific analyzers.
14
14
 
15
+ ## Architecture
16
+
17
+ The SaveMoney tool follows a modular architecture designed for multi-CSP
18
+ support:
19
+
20
+ ```mermaid
21
+ flowchart LR
22
+ subgraph cli["CLI Layer"]
23
+ CMDs[other cmds]
24
+ CLI[savemoney]
25
+ end
26
+ subgraph pkg["Package Layer - @pagopa/dx-savemoney"]
27
+ Config[Config Loader]
28
+ Azure[Azure Analyzer]
29
+ AWS[AWS Analyzer<br/>Coming Soon...]
30
+ end
31
+ subgraph svc["Azure Services"]
32
+ ARM[Azure Resource Manager]
33
+ Monitor[Azure Monitor Metrics]
34
+ Identity[Azure Identity]
35
+ end
36
+ subgraph rsc["Azure Resources"]
37
+ direction LR
38
+ VM[Virtual Machines]
39
+ Disk[Managed Disks]
40
+ NIC[Network Interfaces]
41
+ IP[Public IPs]
42
+ ASP[App Service Plans]
43
+ PE[Private Endpoints]
44
+ SA[Storage Accounts]
45
+ CA[Container Apps]
46
+ end
47
+ CLI --> Config
48
+ Config --> Azure
49
+ Config -.-> AWS
50
+ Azure --> ARM
51
+ Azure --> Monitor
52
+ Azure --> Identity
53
+ ARM --> rsc
54
+ Monitor --> rsc
55
+ style CLI fill:#0078d4,color:#fff
56
+ style Azure fill:#0078d4,color:#fff
57
+ style AWS fill:#ccc,color:#666
58
+ style Config fill:#107c10,color:#fff
59
+ ```
60
+
15
61
  ## Installation
16
62
 
17
63
  ```bash
@@ -39,15 +85,16 @@ yarn add @pagopa/dx-savemoney
39
85
 
40
86
  The tool analyzes the following Azure resource types with specific detection methods and risk levels:
41
87
 
42
- | Resource Type | Detection Method | Cost Risk | What's Checked |
43
- | :---------------------- | :---------------------- | :-------: | :-------------------------------------------------------------------------------------- |
44
- | **Virtual Machines** | Instance View + Metrics | 🔴 High | Deallocated/stopped state, Low CPU usage (<1%), Low network traffic (<10MB) |
45
- | **App Service Plans** | API Details + Metrics | 🔴 High | No apps deployed, Very low CPU (<5%), Very low memory (<10%), Oversized Premium tier |
46
- | **Managed Disks** | API Details | 🟡 Medium | Unattached state, No `managedBy` property |
47
- | **Public IP Addresses** | API Details + Metrics | 🟡 Medium | Not associated with any resource, Static IP not in use, Very low network traffic (<1MB) |
48
- | **Network Interfaces** | API Details | 🟡 Medium | Not attached to VM or Private Endpoint, No public IP assigned |
49
- | **Private Endpoints** | API Details | 🟡 Medium | No private link connections, Rejected/disconnected connections, No network interfaces |
50
- | **Storage Accounts** | Metrics | 🟡 Medium | Very low transaction count (<100 in timespan) |
88
+ | Resource Type | Detection Method | Cost Risk | What's Checked |
89
+ | :---------------------- | :---------------------- | :-------: | :------------------------------------------------------------------------------------------------------------------ |
90
+ | **Virtual Machines** | Instance View + Metrics | 🔴 High | Deallocated/stopped state, Low CPU usage (<1%), Low network traffic (<3MB per days) |
91
+ | **App Service Plans** | API Details + Metrics | 🔴 High | No apps deployed, Very low CPU (<5%), Very low memory (<10%), Oversized Premium tier |
92
+ | **Container Apps** | API Details + Metrics | 🟡 Medium | Not running state, Zero replicas configured, Low CPU (<0.001 cores), Low memory (<10MB), Low network traffic (<1MB) |
93
+ | **Managed Disks** | API Details | 🟡 Medium | Unattached state, No `managedBy` property |
94
+ | **Public IP Addresses** | API Details + Metrics | 🟡 Medium | Not associated with any resource, Static IP not in use, Very low network traffic (<~340KB per day) |
95
+ | **Network Interfaces** | API Details | 🟡 Medium | Not attached to VM or Private Endpoint, No public IP assigned |
96
+ | **Private Endpoints** | API Details | 🟡 Medium | No private link connections, Rejected/disconnected connections, No network interfaces |
97
+ | **Storage Accounts** | Metrics | 🟡 Medium | Very low transaction count (<10 per days in timespan) |
51
98
 
52
99
  #### Generic Checks
53
100
 
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Azure resource analyzer - Main orchestration logic
3
3
  */
4
+ import { ContainerAppsAPIClient } from "@azure/arm-appcontainers";
4
5
  import { WebSiteManagementClient } from "@azure/arm-appservice";
5
6
  import { ComputeManagementClient } from "@azure/arm-compute";
6
7
  import { MonitorClient } from "@azure/arm-monitor";
@@ -23,10 +24,11 @@ export declare function analyzeAzureResources(config: AzureConfig, format: "deta
23
24
  * @param computeClient - Azure Compute client
24
25
  * @param networkClient - Azure Network client
25
26
  * @param webSiteClient - Azure Web Site client
27
+ * @param containerAppsClient - Azure Container Apps client
26
28
  * @param preferredLocation - Preferred Azure location
27
29
  * @param timespanDays - Number of days to analyze metrics
28
30
  * @param verbose - Whether verbose logging is enabled
29
31
  * @returns Analysis result with cost risk and reason
30
32
  */
31
- export declare function analyzeResource(resource: armResources.GenericResource, monitorClient: MonitorClient, computeClient: ComputeManagementClient, networkClient: NetworkManagementClient, webSiteClient: WebSiteManagementClient, preferredLocation: string, timespanDays: number, verbose?: boolean): Promise<AnalysisResult>;
33
+ export declare function analyzeResource(resource: armResources.GenericResource, monitorClient: MonitorClient, computeClient: ComputeManagementClient, networkClient: NetworkManagementClient, webSiteClient: WebSiteManagementClient, containerAppsClient: ContainerAppsAPIClient, preferredLocation: string, timespanDays: number, verbose?: boolean): Promise<AnalysisResult>;
32
34
  //# sourceMappingURL=analyzer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../src/azure/analyzer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,YAAY,MAAM,sBAAsB,CAAC;AAIrD,OAAO,KAAK,EAAE,WAAW,EAA+B,MAAM,YAAY,CAAC;AAE3E,OAAO,EAAE,KAAK,cAAc,EAAgB,MAAM,aAAa,CAAC;AAYhE;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,eAAe,GAAG,MAAM,GAAG,OAAO,iBA8D3C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,YAAY,CAAC,eAAe,EACtC,aAAa,EAAE,aAAa,EAC5B,aAAa,EAAE,uBAAuB,EACtC,aAAa,EAAE,uBAAuB,EACtC,aAAa,EAAE,uBAAuB,EACtC,iBAAiB,EAAE,MAAM,EACzB,YAAY,EAAE,MAAM,EACpB,OAAO,UAAQ,GACd,OAAO,CAAC,cAAc,CAAC,CA4FzB"}
1
+ {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../src/azure/analyzer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,YAAY,MAAM,sBAAsB,CAAC;AAIrD,OAAO,KAAK,EAAE,WAAW,EAA+B,MAAM,YAAY,CAAC;AAE3E,OAAO,EAAE,KAAK,cAAc,EAAgB,MAAM,aAAa,CAAC;AAahE;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,eAAe,GAAG,MAAM,GAAG,OAAO,iBAmE3C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,YAAY,CAAC,eAAe,EACtC,aAAa,EAAE,aAAa,EAC5B,aAAa,EAAE,uBAAuB,EACtC,aAAa,EAAE,uBAAuB,EACtC,aAAa,EAAE,uBAAuB,EACtC,mBAAmB,EAAE,sBAAsB,EAC3C,iBAAiB,EAAE,MAAM,EACzB,YAAY,EAAE,MAAM,EACpB,OAAO,UAAQ,GACd,OAAO,CAAC,cAAc,CAAC,CAuGzB"}
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Azure resource analyzer - Main orchestration logic
3
3
  */
4
+ import { ContainerAppsAPIClient } from "@azure/arm-appcontainers";
4
5
  import { WebSiteManagementClient } from "@azure/arm-appservice";
5
6
  import { ComputeManagementClient } from "@azure/arm-compute";
6
7
  import { MonitorClient } from "@azure/arm-monitor";
@@ -10,7 +11,7 @@ import { DefaultAzureCredential } from "@azure/identity";
10
11
  import { getLogger } from "@logtape/logtape";
11
12
  import { mergeResults } from "../types.js";
12
13
  import { generateReport } from "./report.js";
13
- import { analyzeAppServicePlan, analyzeDisk, analyzeNic, analyzePrivateEndpoint, analyzePublicIp, analyzeStorageAccount, analyzeVM, } from "./resources/index.js";
14
+ import { analyzeAppServicePlan, analyzeContainerApp, analyzeDisk, analyzeNic, analyzePrivateEndpoint, analyzePublicIp, analyzeStorageAccount, analyzeVM, } from "./resources/index.js";
14
15
  /**
15
16
  * Analyzes resources in multiple Azure subscriptions and generates a report.
16
17
  *
@@ -28,9 +29,10 @@ export async function analyzeAzureResources(config, format) {
28
29
  const computeClient = new ComputeManagementClient(credential, subscriptionId.trim());
29
30
  const networkClient = new NetworkManagementClient(credential, subscriptionId.trim());
30
31
  const webSiteClient = new WebSiteManagementClient(credential, subscriptionId.trim());
32
+ const containerAppsClient = new ContainerAppsAPIClient(credential, subscriptionId.trim());
31
33
  // Use the async iterator to avoid memory explosion for large environments
32
34
  for await (const resource of resourceClient.resources.list()) {
33
- const { costRisk, reason, suspectedUnused } = await analyzeResource(resource, monitorClient, computeClient, networkClient, webSiteClient, config.preferredLocation, config.timespanDays, config.verbose || false);
35
+ const { costRisk, reason, suspectedUnused } = await analyzeResource(resource, monitorClient, computeClient, networkClient, webSiteClient, containerAppsClient, config.preferredLocation, config.timespanDays, config.verbose || false);
34
36
  if (suspectedUnused) {
35
37
  allReports.push({
36
38
  analysis: {
@@ -60,12 +62,13 @@ export async function analyzeAzureResources(config, format) {
60
62
  * @param computeClient - Azure Compute client
61
63
  * @param networkClient - Azure Network client
62
64
  * @param webSiteClient - Azure Web Site client
65
+ * @param containerAppsClient - Azure Container Apps client
63
66
  * @param preferredLocation - Preferred Azure location
64
67
  * @param timespanDays - Number of days to analyze metrics
65
68
  * @param verbose - Whether verbose logging is enabled
66
69
  * @returns Analysis result with cost risk and reason
67
70
  */
68
- export async function analyzeResource(resource, monitorClient, computeClient, networkClient, webSiteClient, preferredLocation, timespanDays, verbose = false) {
71
+ export async function analyzeResource(resource, monitorClient, computeClient, networkClient, webSiteClient, containerAppsClient, preferredLocation, timespanDays, verbose = false) {
69
72
  const type = resource.type?.toLowerCase() || "";
70
73
  let result = {
71
74
  costRisk: "low",
@@ -79,6 +82,11 @@ export async function analyzeResource(resource, monitorClient, computeClient, ne
79
82
  }
80
83
  // Route to type-specific analysis hooks
81
84
  switch (type) {
85
+ case "microsoft.app/containerapps": {
86
+ const containerAppResult = await analyzeContainerApp(resource, containerAppsClient, monitorClient, timespanDays, verbose);
87
+ result = mergeResults(result, containerAppResult);
88
+ break;
89
+ }
82
90
  case "microsoft.compute/disks": {
83
91
  const diskResult = await analyzeDisk(resource, computeClient, verbose);
84
92
  result = mergeResults(result, diskResult);
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer.js","sourceRoot":"","sources":["../../src/azure/analyzer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"analyzer.js","sourceRoot":"","sources":["../../src/azure/analyzer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Azure Container App analysis
3
+ */
4
+ import type { ContainerAppsAPIClient } from "@azure/arm-appcontainers";
5
+ import type { MonitorClient } from "@azure/arm-monitor";
6
+ import * as armResources from "@azure/arm-resources";
7
+ import type { AnalysisResult } from "../../types.js";
8
+ /**
9
+ * Analyzes an Azure Container App for potential cost optimization.
10
+ *
11
+ * @param resource - The Azure resource object
12
+ * @param containerAppsClient - Azure Container Apps client for details
13
+ * @param monitorClient - Azure Monitor client for metrics
14
+ * @param timespanDays - Number of days to analyze metrics
15
+ * @param verbose - Enable verbose logging
16
+ * @returns Analysis result with cost risk and reason
17
+ */
18
+ export declare function analyzeContainerApp(resource: armResources.GenericResource, containerAppsClient: ContainerAppsAPIClient, monitorClient: MonitorClient, timespanDays: number, verbose?: boolean): Promise<AnalysisResult>;
19
+ //# sourceMappingURL=container-app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"container-app.d.ts","sourceRoot":"","sources":["../../../src/azure/resources/container-app.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,KAAK,YAAY,MAAM,sBAAsB,CAAC;AAGrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AASrD;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,YAAY,CAAC,eAAe,EACtC,mBAAmB,EAAE,sBAAsB,EAC3C,aAAa,EAAE,aAAa,EAC5B,YAAY,EAAE,MAAM,EACpB,OAAO,UAAQ,GACd,OAAO,CAAC,cAAc,CAAC,CAoEzB"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Azure Container App analysis
3
+ */
4
+ import { getLogger } from "@logtape/logtape";
5
+ import { getMetric, verboseLog, verboseLogAnalysisResult, verboseLogResourceStart, } from "../utils.js";
6
+ /**
7
+ * Analyzes an Azure Container App for potential cost optimization.
8
+ *
9
+ * @param resource - The Azure resource object
10
+ * @param containerAppsClient - Azure Container Apps client for details
11
+ * @param monitorClient - Azure Monitor client for metrics
12
+ * @param timespanDays - Number of days to analyze metrics
13
+ * @param verbose - Enable verbose logging
14
+ * @returns Analysis result with cost risk and reason
15
+ */
16
+ export async function analyzeContainerApp(resource, containerAppsClient, monitorClient, timespanDays, verbose = false) {
17
+ verboseLogResourceStart(verbose, resource.name || "unknown", "Container App (Microsoft.App/containerApps)");
18
+ verboseLog(verbose, "Resource details:", resource);
19
+ const costRisk = "medium";
20
+ let reason = "";
21
+ if (!resource.id) {
22
+ return {
23
+ costRisk,
24
+ reason: "Resource ID is missing.",
25
+ suspectedUnused: false,
26
+ };
27
+ }
28
+ const resourceParts = resource.id.split("/");
29
+ const resourceGroupName = resourceParts[4];
30
+ const containerAppName = resourceParts[8];
31
+ try {
32
+ const appDetails = await containerAppsClient.containerApps.get(resourceGroupName, containerAppName);
33
+ verboseLog(verbose, "Container App API details:", appDetails);
34
+ reason = checkRunningStatus({
35
+ properties: {
36
+ provisioningState: appDetails.provisioningState,
37
+ runningStatus: appDetails.runningStatus,
38
+ template: appDetails.template,
39
+ },
40
+ }, reason, verbose);
41
+ reason = await checkResourceMetrics(resource, monitorClient, timespanDays, reason, verbose);
42
+ reason = await checkNetworkMetrics(resource, monitorClient, timespanDays, reason, verbose);
43
+ }
44
+ catch (error) {
45
+ const logger = getLogger(["savemoney", "azure"]);
46
+ logger.warn(`Failed to get Container App details for ${containerAppName}: ${error instanceof Error ? error.message : error}`);
47
+ reason += "Could not retrieve detailed Container App information. ";
48
+ }
49
+ const suspectedUnused = reason.length > 0;
50
+ const result = { costRisk, reason: reason.trim(), suspectedUnused };
51
+ verboseLogAnalysisResult(verbose, result);
52
+ return result;
53
+ }
54
+ /**
55
+ * Checks network traffic metrics for a Container App.
56
+ */
57
+ async function checkNetworkMetrics(resource, monitorClient, timespanDays, reason, verbose) {
58
+ let newReason = reason;
59
+ if (!resource.id) {
60
+ return newReason;
61
+ }
62
+ verboseLog(verbose, "Checking network metrics...");
63
+ const networkIn = await getMetric(monitorClient, resource.id, "RxBytes", "Average", timespanDays);
64
+ const networkOut = await getMetric(monitorClient, resource.id, "TxBytes", "Average", timespanDays);
65
+ verboseLog(verbose, `Network In: ${networkIn !== null ? `${(networkIn / 1048576).toFixed(2)} MB/day avg` : "N/A"}`);
66
+ verboseLog(verbose, `Network Out: ${networkOut !== null ? `${(networkOut / 1048576).toFixed(2)} MB/day avg` : "N/A"}`);
67
+ if (networkIn !== null &&
68
+ networkOut !== null &&
69
+ networkIn + networkOut < 34000) {
70
+ newReason += `Very low network traffic (${((networkIn + networkOut) / 1048576).toFixed(2)} MB/day avg). `;
71
+ }
72
+ return newReason;
73
+ }
74
+ /**
75
+ * Checks resource usage metrics for a Container App.
76
+ */
77
+ async function checkResourceMetrics(resource, monitorClient, timespanDays, reason, verbose) {
78
+ let newReason = reason;
79
+ if (!resource.id) {
80
+ return newReason;
81
+ }
82
+ verboseLog(verbose, "Checking resource usage metrics...");
83
+ const cpuUsage = await getMetric(monitorClient, resource.id, "UsageNanoCores", "Average", timespanDays);
84
+ const memoryUsage = await getMetric(monitorClient, resource.id, "WorkingSetBytes", "Average", timespanDays);
85
+ verboseLog(verbose, `CPU Usage: ${cpuUsage !== null ? `${(cpuUsage / 1000000000).toFixed(4)} cores` : "N/A"}`);
86
+ verboseLog(verbose, `Memory Usage: ${memoryUsage !== null ? `${(memoryUsage / 1048576).toFixed(2)} MB` : "N/A"}`);
87
+ if (cpuUsage !== null && cpuUsage < 1000000) {
88
+ newReason += `Very low CPU usage (${(cpuUsage / 1000000000).toFixed(4)} cores). `;
89
+ }
90
+ if (memoryUsage !== null && memoryUsage < 10485760) {
91
+ newReason += `Very low memory usage (${(memoryUsage / 1048576).toFixed(2)} MB). `;
92
+ }
93
+ return newReason;
94
+ }
95
+ /**
96
+ * Checks the running status of a Container App.
97
+ */
98
+ function checkRunningStatus(appDetails, reason, verbose) {
99
+ let newReason = reason;
100
+ const { provisioningState, runningStatus, template } = appDetails.properties || {};
101
+ verboseLog(verbose, `Min Replicas: ${template?.scale?.minReplicas ?? "N/A"}`);
102
+ verboseLog(verbose, `Max Replicas: ${template?.scale?.maxReplicas ?? "N/A"}`);
103
+ if (provisioningState === "Succeeded" && runningStatus !== "Running") {
104
+ newReason += "Container App is not running. ";
105
+ }
106
+ const minReplicas = template?.scale?.minReplicas;
107
+ const maxReplicas = template?.scale?.maxReplicas;
108
+ if (minReplicas === 0 && maxReplicas === 0) {
109
+ newReason += "Container App has 0 replicas configured. ";
110
+ }
111
+ return newReason;
112
+ }
113
+ //# sourceMappingURL=container-app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"container-app.js","sourceRoot":"","sources":["../../../src/azure/resources/container-app.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@
2
2
  * Azure resource-specific analysis functions
3
3
  */
4
4
  export { analyzeAppServicePlan } from "./app-service.js";
5
+ export { analyzeContainerApp } from "./container-app.js";
5
6
  export { analyzeDisk } from "./disk.js";
6
7
  export { analyzeNic } from "./nic.js";
7
8
  export { analyzePrivateEndpoint } from "./private-endpoint.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/azure/resources/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/azure/resources/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC"}
@@ -2,6 +2,7 @@
2
2
  * Azure resource-specific analysis functions
3
3
  */
4
4
  export { analyzeAppServicePlan } from "./app-service.js";
5
+ export { analyzeContainerApp } from "./container-app.js";
5
6
  export { analyzeDisk } from "./disk.js";
6
7
  export { analyzeNic } from "./nic.js";
7
8
  export { analyzePrivateEndpoint } from "./private-endpoint.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/azure/resources/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/azure/resources/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC"}
@@ -42,10 +42,10 @@ export async function analyzePublicIp(resource, networkClient, monitorClient, ti
42
42
  reason += "Static IP not in use. ";
43
43
  }
44
44
  // Check network metrics for low usage
45
- const bytesInDDoS = await getMetric(monitorClient, resource.id, "BytesInDDoS", "Total", timespanDays);
46
- if (bytesInDDoS !== null && bytesInDDoS < 1000000) {
47
- // Less than 1MB total in 30 days
48
- reason += `Very low network traffic (${(bytesInDDoS / 1024 / 1024).toFixed(2)} MB). `;
45
+ const bytesInDDoS = await getMetric(monitorClient, resource.id, "BytesInDDoS", "Average", timespanDays);
46
+ if (bytesInDDoS !== null && bytesInDDoS < 340000) {
47
+ // Less than ~340KB average per day
48
+ reason += `Very low network traffic (${(bytesInDDoS / 1024 / 1024).toFixed(2)} MB/day avg). `;
49
49
  }
50
50
  }
51
51
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"public-ip.js","sourceRoot":"","sources":["../../../src/azure/resources/public-ip.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAI7C,OAAO,EACL,SAAS,EACT,UAAU,EACV,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAErB;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAAsC,EACtC,aAAsC,EACtC,aAA4B,EAC5B,YAAoB,EACpB,OAAO,GAAG,KAAK;IAEf,uBAAuB,CACrB,OAAO,EACP,QAAQ,CAAC,IAAI,IAAI,SAAS,EAC1B,iDAAiD,CAClD,CAAC;IACF,UAAU,CAAC,OAAO,EAAE,mBAAmB,EAAE,QAAQ,CAAC,CAAC;IAEnD,MAAM,QAAQ,GAA8B,QAAQ,CAAC;IACrD,IAAI,MAAM,GAAG,EAAE,CAAC;IAEhB,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,OAAO;YACL,QAAQ;YACR,MAAM,EAAE,yBAAyB;YACjC,eAAe,EAAE,KAAK;SACvB,CAAC;IACJ,CAAC;IAED,6DAA6D;IAC7D,MAAM,aAAa,GAAG,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7C,MAAM,iBAAiB,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAC3C,MAAM,YAAY,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAEtC,IAAI,CAAC;QACH,qCAAqC;QACrC,MAAM,eAAe,GAAG,MAAM,aAAa,CAAC,iBAAiB,CAAC,GAAG,CAC/D,iBAAiB,EACjB,YAAY,CACb,CAAC;QAEF,UAAU,CAAC,OAAO,EAAE,wBAAwB,EAAE,eAAe,CAAC,CAAC;QAE/D,yDAAyD;QACzD,IAAI,CAAC,eAAe,CAAC,eAAe,IAAI,CAAC,eAAe,CAAC,UAAU,EAAE,CAAC;YACpE,MAAM,IAAI,8CAA8C,CAAC;QAC3D,CAAC;QAED,iDAAiD;QACjD,IACE,eAAe,CAAC,wBAAwB,KAAK,QAAQ;YACrD,CAAC,eAAe,CAAC,eAAe,EAChC,CAAC;YACD,MAAM,IAAI,wBAAwB,CAAC;QACrC,CAAC;QAED,sCAAsC;QACtC,MAAM,WAAW,GAAG,MAAM,SAAS,CACjC,aAAa,EACb,QAAQ,CAAC,EAAE,EACX,aAAa,EACb,OAAO,EACP,YAAY,CACb,CAAC;QAEF,IAAI,WAAW,KAAK,IAAI,IAAI,WAAW,GAAG,OAAO,EAAE,CAAC;YAClD,iCAAiC;YACjC,MAAM,IAAI,6BAA6B,CAAC,WAAW,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC;QACxF,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;QACjD,MAAM,CAAC,IAAI,CACT,uCAAuC,YAAY,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,CACzG,CAAC;QACF,MAAM,IAAI,qDAAqD,CAAC;IAClE,CAAC;IAED,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,eAAe,EAAE,CAAC;IACpE,wBAAwB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"public-ip.js","sourceRoot":"","sources":["../../../src/azure/resources/public-ip.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAI7C,OAAO,EACL,SAAS,EACT,UAAU,EACV,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAErB;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAAsC,EACtC,aAAsC,EACtC,aAA4B,EAC5B,YAAoB,EACpB,OAAO,GAAG,KAAK;IAEf,uBAAuB,CACrB,OAAO,EACP,QAAQ,CAAC,IAAI,IAAI,SAAS,EAC1B,iDAAiD,CAClD,CAAC;IACF,UAAU,CAAC,OAAO,EAAE,mBAAmB,EAAE,QAAQ,CAAC,CAAC;IAEnD,MAAM,QAAQ,GAA8B,QAAQ,CAAC;IACrD,IAAI,MAAM,GAAG,EAAE,CAAC;IAEhB,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,OAAO;YACL,QAAQ;YACR,MAAM,EAAE,yBAAyB;YACjC,eAAe,EAAE,KAAK;SACvB,CAAC;IACJ,CAAC;IAED,6DAA6D;IAC7D,MAAM,aAAa,GAAG,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7C,MAAM,iBAAiB,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAC3C,MAAM,YAAY,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAEtC,IAAI,CAAC;QACH,qCAAqC;QACrC,MAAM,eAAe,GAAG,MAAM,aAAa,CAAC,iBAAiB,CAAC,GAAG,CAC/D,iBAAiB,EACjB,YAAY,CACb,CAAC;QAEF,UAAU,CAAC,OAAO,EAAE,wBAAwB,EAAE,eAAe,CAAC,CAAC;QAE/D,yDAAyD;QACzD,IAAI,CAAC,eAAe,CAAC,eAAe,IAAI,CAAC,eAAe,CAAC,UAAU,EAAE,CAAC;YACpE,MAAM,IAAI,8CAA8C,CAAC;QAC3D,CAAC;QAED,iDAAiD;QACjD,IACE,eAAe,CAAC,wBAAwB,KAAK,QAAQ;YACrD,CAAC,eAAe,CAAC,eAAe,EAChC,CAAC;YACD,MAAM,IAAI,wBAAwB,CAAC;QACrC,CAAC;QAED,sCAAsC;QACtC,MAAM,WAAW,GAAG,MAAM,SAAS,CACjC,aAAa,EACb,QAAQ,CAAC,EAAE,EACX,aAAa,EACb,SAAS,EACT,YAAY,CACb,CAAC;QAEF,IAAI,WAAW,KAAK,IAAI,IAAI,WAAW,GAAG,MAAM,EAAE,CAAC;YACjD,mCAAmC;YACnC,MAAM,IAAI,6BAA6B,CAAC,WAAW,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC;QAChG,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;QACjD,MAAM,CAAC,IAAI,CACT,uCAAuC,YAAY,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,CACzG,CAAC;QACF,MAAM,IAAI,qDAAqD,CAAC;IAClE,CAAC;IAED,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,eAAe,EAAE,CAAC;IACpE,wBAAwB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -21,12 +21,12 @@ export async function analyzeStorageAccount(resource, monitorClient, timespanDay
21
21
  suspectedUnused: false,
22
22
  };
23
23
  }
24
- const transactions = await getMetric(monitorClient, resource.id, "Transactions", "Total", timespanDays);
25
- if (transactions !== null && transactions < 100) {
26
- // Very low transactions
24
+ const transactions = await getMetric(monitorClient, resource.id, "Transactions", "Average", timespanDays);
25
+ if (transactions !== null && transactions < 10) {
26
+ // Less than 10 transactions per day on average
27
27
  const result = {
28
28
  costRisk,
29
- reason: `Very low transaction count (${transactions}). `,
29
+ reason: `Very low transaction count (${transactions.toFixed(2)} avg/day). `,
30
30
  suspectedUnused: true,
31
31
  };
32
32
  verboseLogAnalysisResult(verbose, result);
@@ -1 +1 @@
1
- {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../../src/azure/resources/storage.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,OAAO,EACL,SAAS,EACT,UAAU,EACV,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAErB;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,QAAsC,EACtC,aAA4B,EAC5B,YAAoB,EACpB,OAAO,GAAG,KAAK;IAEf,uBAAuB,CACrB,OAAO,EACP,QAAQ,CAAC,IAAI,IAAI,SAAS,EAC1B,qDAAqD,CACtD,CAAC;IACF,UAAU,CAAC,OAAO,EAAE,mBAAmB,EAAE,QAAQ,CAAC,CAAC;IAEnD,MAAM,QAAQ,GAA8B,QAAQ,CAAC;IACrD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,OAAO;YACL,QAAQ;YACR,MAAM,EAAE,yBAAyB;YACjC,eAAe,EAAE,KAAK;SACvB,CAAC;IACJ,CAAC;IACD,MAAM,YAAY,GAAG,MAAM,SAAS,CAClC,aAAa,EACb,QAAQ,CAAC,EAAE,EACX,cAAc,EACd,OAAO,EACP,YAAY,CACb,CAAC;IACF,IAAI,YAAY,KAAK,IAAI,IAAI,YAAY,GAAG,GAAG,EAAE,CAAC;QAChD,wBAAwB;QACxB,MAAM,MAAM,GAAG;YACb,QAAQ;YACR,MAAM,EAAE,+BAA+B,YAAY,KAAK;YACxD,eAAe,EAAE,IAAI;SACtB,CAAC;QACF,wBAAwB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC1C,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,MAAM,MAAM,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC;IAChE,wBAAwB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../../src/azure/resources/storage.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,OAAO,EACL,SAAS,EACT,UAAU,EACV,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAErB;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,QAAsC,EACtC,aAA4B,EAC5B,YAAoB,EACpB,OAAO,GAAG,KAAK;IAEf,uBAAuB,CACrB,OAAO,EACP,QAAQ,CAAC,IAAI,IAAI,SAAS,EAC1B,qDAAqD,CACtD,CAAC;IACF,UAAU,CAAC,OAAO,EAAE,mBAAmB,EAAE,QAAQ,CAAC,CAAC;IAEnD,MAAM,QAAQ,GAA8B,QAAQ,CAAC;IACrD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,OAAO;YACL,QAAQ;YACR,MAAM,EAAE,yBAAyB;YACjC,eAAe,EAAE,KAAK;SACvB,CAAC;IACJ,CAAC;IACD,MAAM,YAAY,GAAG,MAAM,SAAS,CAClC,aAAa,EACb,QAAQ,CAAC,EAAE,EACX,cAAc,EACd,SAAS,EACT,YAAY,CACb,CAAC;IACF,IAAI,YAAY,KAAK,IAAI,IAAI,YAAY,GAAG,EAAE,EAAE,CAAC;QAC/C,+CAA+C;QAC/C,MAAM,MAAM,GAAG;YACb,QAAQ;YACR,MAAM,EAAE,+BAA+B,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa;YAC3E,eAAe,EAAE,IAAI;SACtB,CAAC;QACF,wBAAwB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC1C,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,MAAM,MAAM,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC;IAChE,wBAAwB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -61,14 +61,14 @@ export async function analyzeVM(resource, monitorClient, computeClient, timespan
61
61
  }
62
62
  // Check metrics for low utilization
63
63
  const cpuUsage = await getMetric(monitorClient, resource.id, "Percentage CPU", "Average", timespanDays);
64
- const networkIn = await getMetric(monitorClient, resource.id, "Network In Total", "Total", timespanDays);
64
+ const networkIn = await getMetric(monitorClient, resource.id, "Network In Total", "Average", timespanDays);
65
65
  if (cpuUsage !== null && cpuUsage < 1) {
66
66
  // Less than 1% average CPU
67
67
  reason += `Low CPU usage (avg ${cpuUsage.toFixed(2)}%). `;
68
68
  }
69
- if (networkIn !== null && networkIn < 1024 * 1024 * 10) {
70
- // Less than 10MB total network in
71
- reason += `Low network traffic. `;
69
+ if (networkIn !== null && networkIn < 1024 * 1024 * 3) {
70
+ // Less than 3MB average per day
71
+ reason += `Low network traffic (${(networkIn / 1024 / 1024).toFixed(2)} MB/day avg). `;
72
72
  }
73
73
  const result = { costRisk, reason, suspectedUnused: reason.length > 0 };
74
74
  verboseLogAnalysisResult(verbose, result);
@@ -1 +1 @@
1
- {"version":3,"file":"vm.js","sourceRoot":"","sources":["../../../src/azure/resources/vm.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"vm.js","sourceRoot":"","sources":["../../../src/azure/resources/vm.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,29 @@
3
3
  */
4
4
  import type { MonitorClient } from "@azure/arm-monitor";
5
5
  import type { AnalysisResult } from "../types.js";
6
+ type MetricDataPoint = {
7
+ average?: number;
8
+ count?: number;
9
+ maximum?: number;
10
+ minimum?: number;
11
+ total?: number;
12
+ };
13
+ /**
14
+ * Aggregates metric data points based on aggregation type.
15
+ *
16
+ * @param dataPoints - Array of metric data points
17
+ * @param aggregation - The aggregation type (e.g., "Average", "Total")
18
+ * @returns The aggregated value or null if unavailable
19
+ */
20
+ export declare function aggregateDataPoints(dataPoints: MetricDataPoint[], aggregation: string): null | number;
21
+ /**
22
+ * Extracts the aggregated value from metric data.
23
+ *
24
+ * @param metricData - The metric data point
25
+ * @param aggregation - The aggregation type (e.g., "Average", "Total")
26
+ * @returns The aggregated value or null if unavailable
27
+ */
28
+ export declare function extractAggregatedValue(metricData: MetricDataPoint, aggregation: string): null | number;
6
29
  /**
7
30
  * Fetches a specific metric for a resource from Azure Monitor.
8
31
  *
@@ -37,4 +60,5 @@ export declare function verboseLogAnalysisResult(verbose: boolean, result: Analy
37
60
  * @param resourceType - Type of the resource
38
61
  */
39
62
  export declare function verboseLogResourceStart(verbose: boolean, resourceName: string, resourceType: string): void;
63
+ export {};
40
64
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/azure/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAIxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAC7B,aAAa,EAAE,aAAa,EAC5B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,CAiDxB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,OAAO,QAUjB;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,cAAc,QAYvB;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,QASrB"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/azure/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAIxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,KAAK,eAAe,GAAG;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,eAAe,EAAE,EAC7B,WAAW,EAAE,MAAM,GAClB,IAAI,GAAG,MAAM,CAiCf;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,eAAe,EAC3B,WAAW,EAAE,MAAM,GAClB,IAAI,GAAG,MAAM,CA6Bf;AAED;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAC7B,aAAa,EAAE,aAAa,EAC5B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,CAsCxB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,OAAO,QAUjB;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,cAAc,QAYvB;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,QASrB"}
@@ -2,6 +2,69 @@
2
2
  * Azure utility functions for debugging and metrics
3
3
  */
4
4
  import { getLogger } from "@logtape/logtape";
5
+ /**
6
+ * Aggregates metric data points based on aggregation type.
7
+ *
8
+ * @param dataPoints - Array of metric data points
9
+ * @param aggregation - The aggregation type (e.g., "Average", "Total")
10
+ * @returns The aggregated value or null if unavailable
11
+ */
12
+ export function aggregateDataPoints(dataPoints, aggregation) {
13
+ const aggregationLower = aggregation.toLowerCase();
14
+ // Get all non-null values from the data points
15
+ const values = dataPoints
16
+ .map((dataPoint) => extractAggregatedValue(dataPoint, aggregation))
17
+ .filter((v) => v !== null);
18
+ if (values.length === 0) {
19
+ return null;
20
+ }
21
+ if (aggregationLower === "total" || aggregationLower === "count") {
22
+ // Sum all values for Total/Count
23
+ return values.reduce((sum, v) => sum + v, 0);
24
+ }
25
+ if (aggregationLower === "average") {
26
+ // Calculate the average of all values
27
+ return values.reduce((sum, v) => sum + v, 0) / values.length;
28
+ }
29
+ if (aggregationLower === "maximum") {
30
+ // Find the maximum value
31
+ return Math.max(...values);
32
+ }
33
+ if (aggregationLower === "minimum") {
34
+ // Find the minimum value
35
+ return Math.min(...values);
36
+ }
37
+ return null;
38
+ }
39
+ /**
40
+ * Extracts the aggregated value from metric data.
41
+ *
42
+ * @param metricData - The metric data point
43
+ * @param aggregation - The aggregation type (e.g., "Average", "Total")
44
+ * @returns The aggregated value or null if unavailable
45
+ */
46
+ export function extractAggregatedValue(metricData, aggregation) {
47
+ const aggregationLower = aggregation.toLowerCase();
48
+ if (aggregationLower === "average" &&
49
+ typeof metricData.average === "number") {
50
+ return metricData.average;
51
+ }
52
+ if (aggregationLower === "total" && typeof metricData.total === "number") {
53
+ return metricData.total;
54
+ }
55
+ if (aggregationLower === "minimum" &&
56
+ typeof metricData.minimum === "number") {
57
+ return metricData.minimum;
58
+ }
59
+ if (aggregationLower === "maximum" &&
60
+ typeof metricData.maximum === "number") {
61
+ return metricData.maximum;
62
+ }
63
+ if (aggregationLower === "count" && typeof metricData.count === "number") {
64
+ return metricData.count;
65
+ }
66
+ return null;
67
+ }
5
68
  /**
6
69
  * Fetches a specific metric for a resource from Azure Monitor.
7
70
  *
@@ -20,30 +83,19 @@ export async function getMetric(monitorClient, resourceId, metricName, aggregati
20
83
  metricnames: metricName,
21
84
  timespan,
22
85
  });
23
- const metricData = result.value[0]?.timeseries?.[0]?.data?.[0];
24
- if (!metricData) {
86
+ if (result.value.length === 0) {
25
87
  return null;
26
88
  }
27
- const aggregationLower = aggregation.toLowerCase();
28
- if (aggregationLower === "average" &&
29
- typeof metricData.average === "number") {
30
- return metricData.average;
31
- }
32
- if (aggregationLower === "total" && typeof metricData.total === "number") {
33
- return metricData.total;
34
- }
35
- if (aggregationLower === "minimum" &&
36
- typeof metricData.minimum === "number") {
37
- return metricData.minimum;
38
- }
39
- if (aggregationLower === "maximum" &&
40
- typeof metricData.maximum === "number") {
41
- return metricData.maximum;
89
+ const metric = result.value[0];
90
+ if (!metric.timeseries || metric.timeseries.length === 0) {
91
+ return null;
42
92
  }
43
- if (aggregationLower === "count" && typeof metricData.count === "number") {
44
- return metricData.count;
93
+ const timeserie = metric.timeseries[0];
94
+ if (!timeserie.data || timeserie.data.length === 0) {
95
+ return null;
45
96
  }
46
- return null;
97
+ const aggregatedValue = aggregateDataPoints(timeserie.data, aggregation);
98
+ return aggregatedValue;
47
99
  }
48
100
  catch (error) {
49
101
  const logger = getLogger(["savemoney", "azure", "metrics"]);
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/azure/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAI7C;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,aAA4B,EAC5B,UAAkB,EAClB,UAAkB,EAClB,WAAmB,EACnB,YAAoB;IAEpB,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,YAAY,GAAG,CAAC;QACrC,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE;YAC1D,WAAW;YACX,WAAW,EAAE,UAAU;YACvB,QAAQ;SACT,CAAC,CAAC;QAEH,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC/D,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,gBAAgB,GAAG,WAAW,CAAC,WAAW,EAAE,CAAC;QAEnD,IACE,gBAAgB,KAAK,SAAS;YAC9B,OAAO,UAAU,CAAC,OAAO,KAAK,QAAQ,EACtC,CAAC;YACD,OAAO,UAAU,CAAC,OAAO,CAAC;QAC5B,CAAC;QACD,IAAI,gBAAgB,KAAK,OAAO,IAAI,OAAO,UAAU,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YACzE,OAAO,UAAU,CAAC,KAAK,CAAC;QAC1B,CAAC;QACD,IACE,gBAAgB,KAAK,SAAS;YAC9B,OAAO,UAAU,CAAC,OAAO,KAAK,QAAQ,EACtC,CAAC;YACD,OAAO,UAAU,CAAC,OAAO,CAAC;QAC5B,CAAC;QACD,IACE,gBAAgB,KAAK,SAAS;YAC9B,OAAO,UAAU,CAAC,OAAO,KAAK,QAAQ,EACtC,CAAC;YACD,OAAO,UAAU,CAAC,OAAO,CAAC;QAC5B,CAAC;QACD,IAAI,gBAAgB,KAAK,OAAO,IAAI,OAAO,UAAU,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YACzE,OAAO,UAAU,CAAC,KAAK,CAAC;QAC1B,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,WAAW,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;QAC5D,MAAM,CAAC,KAAK,CACV,0BAA0B,UAAU,iBAAiB,UAAU,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAC7H,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CACxB,OAAgB,EAChB,OAAe,EACf,MAAgB;IAEhB,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,WAAW,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;QAC5D,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAChE,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CACtC,OAAgB,EAChB,MAAsB;IAEtB,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,WAAW,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;QAC5D,MAAM,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;QACtC,MAAM,CAAC,KAAK,CAAC,iBAAiB,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QAC/D,MAAM,CAAC,KAAK,CACV,wBAAwB,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAChE,CAAC;QACF,MAAM,CAAC,KAAK,CAAC,cAAc,MAAM,CAAC,MAAM,IAAI,iBAAiB,EAAE,CAAC,CAAC;QACjE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;IACtC,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAAgB,EAChB,YAAoB,EACpB,YAAoB;IAEpB,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,WAAW,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;QAC5D,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;QACpC,MAAM,CAAC,KAAK,CAAC,iBAAiB,YAAY,EAAE,CAAC,CAAC;QAC9C,MAAM,CAAC,KAAK,CAAC,YAAY,YAAY,EAAE,CAAC,CAAC;QACzC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/B,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/azure/utils.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pagopa/dx-savemoney",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "Azure resource analyzer for finding unused or cost-inefficient resources.",
6
6
  "repository": {
@@ -27,6 +27,7 @@
27
27
  "DX"
28
28
  ],
29
29
  "dependencies": {
30
+ "@azure/arm-appcontainers": "^3.0.0",
30
31
  "@azure/arm-appservice": "^17.0.0",
31
32
  "@azure/arm-compute": "^23.1.0",
32
33
  "@azure/arm-monitor": "^7.0.0",
@@ -37,13 +38,13 @@
37
38
  },
38
39
  "devDependencies": {
39
40
  "@tsconfig/node22": "22.0.2",
40
- "@types/node": "^22.16.2",
41
+ "@types/node": "^22.19.1",
41
42
  "@vitest/coverage-v8": "^3.2.4",
42
- "eslint": "^9.30.0",
43
+ "eslint": "^9.39.1",
43
44
  "prettier": "3.6.2",
44
45
  "typescript": "~5.8.3",
45
46
  "vitest": "^3.2.4",
46
- "@pagopa/eslint-config": "^5.1.0"
47
+ "@pagopa/eslint-config": "^5.1.1"
47
48
  },
48
49
  "scripts": {
49
50
  "build": "rm -rf dist && tsc",
@@ -2,6 +2,7 @@
2
2
  * Azure resource analyzer - Main orchestration logic
3
3
  */
4
4
 
5
+ import { ContainerAppsAPIClient } from "@azure/arm-appcontainers";
5
6
  import { WebSiteManagementClient } from "@azure/arm-appservice";
6
7
  import { ComputeManagementClient } from "@azure/arm-compute";
7
8
  import { MonitorClient } from "@azure/arm-monitor";
@@ -16,6 +17,7 @@ import { type AnalysisResult, mergeResults } from "../types.js";
16
17
  import { generateReport } from "./report.js";
17
18
  import {
18
19
  analyzeAppServicePlan,
20
+ analyzeContainerApp,
19
21
  analyzeDisk,
20
22
  analyzeNic,
21
23
  analyzePrivateEndpoint,
@@ -58,6 +60,10 @@ export async function analyzeAzureResources(
58
60
  credential,
59
61
  subscriptionId.trim(),
60
62
  );
63
+ const containerAppsClient = new ContainerAppsAPIClient(
64
+ credential,
65
+ subscriptionId.trim(),
66
+ );
61
67
 
62
68
  // Use the async iterator to avoid memory explosion for large environments
63
69
  for await (const resource of resourceClient.resources.list()) {
@@ -67,6 +73,7 @@ export async function analyzeAzureResources(
67
73
  computeClient,
68
74
  networkClient,
69
75
  webSiteClient,
76
+ containerAppsClient,
70
77
  config.preferredLocation,
71
78
  config.timespanDays,
72
79
  config.verbose || false,
@@ -104,6 +111,7 @@ export async function analyzeAzureResources(
104
111
  * @param computeClient - Azure Compute client
105
112
  * @param networkClient - Azure Network client
106
113
  * @param webSiteClient - Azure Web Site client
114
+ * @param containerAppsClient - Azure Container Apps client
107
115
  * @param preferredLocation - Preferred Azure location
108
116
  * @param timespanDays - Number of days to analyze metrics
109
117
  * @param verbose - Whether verbose logging is enabled
@@ -115,6 +123,7 @@ export async function analyzeResource(
115
123
  computeClient: ComputeManagementClient,
116
124
  networkClient: NetworkManagementClient,
117
125
  webSiteClient: WebSiteManagementClient,
126
+ containerAppsClient: ContainerAppsAPIClient,
118
127
  preferredLocation: string,
119
128
  timespanDays: number,
120
129
  verbose = false,
@@ -134,6 +143,17 @@ export async function analyzeResource(
134
143
 
135
144
  // Route to type-specific analysis hooks
136
145
  switch (type) {
146
+ case "microsoft.app/containerapps": {
147
+ const containerAppResult = await analyzeContainerApp(
148
+ resource,
149
+ containerAppsClient,
150
+ monitorClient,
151
+ timespanDays,
152
+ verbose,
153
+ );
154
+ result = mergeResults(result, containerAppResult);
155
+ break;
156
+ }
137
157
  case "microsoft.compute/disks": {
138
158
  const diskResult = await analyzeDisk(resource, computeClient, verbose);
139
159
  result = mergeResults(result, diskResult);
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Azure Container App analysis
3
+ */
4
+
5
+ import type { ContainerAppsAPIClient } from "@azure/arm-appcontainers";
6
+ import type { MonitorClient } from "@azure/arm-monitor";
7
+
8
+ import * as armResources from "@azure/arm-resources";
9
+ import { getLogger } from "@logtape/logtape";
10
+
11
+ import type { AnalysisResult } from "../../types.js";
12
+
13
+ import {
14
+ getMetric,
15
+ verboseLog,
16
+ verboseLogAnalysisResult,
17
+ verboseLogResourceStart,
18
+ } from "../utils.js";
19
+
20
+ /**
21
+ * Analyzes an Azure Container App for potential cost optimization.
22
+ *
23
+ * @param resource - The Azure resource object
24
+ * @param containerAppsClient - Azure Container Apps client for details
25
+ * @param monitorClient - Azure Monitor client for metrics
26
+ * @param timespanDays - Number of days to analyze metrics
27
+ * @param verbose - Enable verbose logging
28
+ * @returns Analysis result with cost risk and reason
29
+ */
30
+ export async function analyzeContainerApp(
31
+ resource: armResources.GenericResource,
32
+ containerAppsClient: ContainerAppsAPIClient,
33
+ monitorClient: MonitorClient,
34
+ timespanDays: number,
35
+ verbose = false,
36
+ ): Promise<AnalysisResult> {
37
+ verboseLogResourceStart(
38
+ verbose,
39
+ resource.name || "unknown",
40
+ "Container App (Microsoft.App/containerApps)",
41
+ );
42
+ verboseLog(verbose, "Resource details:", resource);
43
+
44
+ const costRisk: "high" | "low" | "medium" = "medium";
45
+ let reason = "";
46
+
47
+ if (!resource.id) {
48
+ return {
49
+ costRisk,
50
+ reason: "Resource ID is missing.",
51
+ suspectedUnused: false,
52
+ };
53
+ }
54
+
55
+ const resourceParts = resource.id.split("/");
56
+ const resourceGroupName = resourceParts[4];
57
+ const containerAppName = resourceParts[8];
58
+
59
+ try {
60
+ const appDetails = await containerAppsClient.containerApps.get(
61
+ resourceGroupName,
62
+ containerAppName,
63
+ );
64
+
65
+ verboseLog(verbose, "Container App API details:", appDetails);
66
+
67
+ reason = checkRunningStatus(
68
+ {
69
+ properties: {
70
+ provisioningState: appDetails.provisioningState,
71
+ runningStatus: appDetails.runningStatus,
72
+ template: appDetails.template,
73
+ },
74
+ },
75
+ reason,
76
+ verbose,
77
+ );
78
+ reason = await checkResourceMetrics(
79
+ resource,
80
+ monitorClient,
81
+ timespanDays,
82
+ reason,
83
+ verbose,
84
+ );
85
+ reason = await checkNetworkMetrics(
86
+ resource,
87
+ monitorClient,
88
+ timespanDays,
89
+ reason,
90
+ verbose,
91
+ );
92
+ } catch (error) {
93
+ const logger = getLogger(["savemoney", "azure"]);
94
+ logger.warn(
95
+ `Failed to get Container App details for ${containerAppName}: ${error instanceof Error ? error.message : error}`,
96
+ );
97
+ reason += "Could not retrieve detailed Container App information. ";
98
+ }
99
+
100
+ const suspectedUnused = reason.length > 0;
101
+ const result = { costRisk, reason: reason.trim(), suspectedUnused };
102
+ verboseLogAnalysisResult(verbose, result);
103
+ return result;
104
+ }
105
+
106
+ /**
107
+ * Checks network traffic metrics for a Container App.
108
+ */
109
+ async function checkNetworkMetrics(
110
+ resource: armResources.GenericResource,
111
+ monitorClient: MonitorClient,
112
+ timespanDays: number,
113
+ reason: string,
114
+ verbose: boolean,
115
+ ): Promise<string> {
116
+ let newReason = reason;
117
+
118
+ if (!resource.id) {
119
+ return newReason;
120
+ }
121
+
122
+ verboseLog(verbose, "Checking network metrics...");
123
+
124
+ const networkIn = await getMetric(
125
+ monitorClient,
126
+ resource.id,
127
+ "RxBytes",
128
+ "Average",
129
+ timespanDays,
130
+ );
131
+
132
+ const networkOut = await getMetric(
133
+ monitorClient,
134
+ resource.id,
135
+ "TxBytes",
136
+ "Average",
137
+ timespanDays,
138
+ );
139
+
140
+ verboseLog(
141
+ verbose,
142
+ `Network In: ${networkIn !== null ? `${(networkIn / 1048576).toFixed(2)} MB/day avg` : "N/A"}`,
143
+ );
144
+ verboseLog(
145
+ verbose,
146
+ `Network Out: ${networkOut !== null ? `${(networkOut / 1048576).toFixed(2)} MB/day avg` : "N/A"}`,
147
+ );
148
+
149
+ if (
150
+ networkIn !== null &&
151
+ networkOut !== null &&
152
+ networkIn + networkOut < 34000
153
+ ) {
154
+ newReason += `Very low network traffic (${((networkIn + networkOut) / 1048576).toFixed(2)} MB/day avg). `;
155
+ }
156
+
157
+ return newReason;
158
+ }
159
+
160
+ /**
161
+ * Checks resource usage metrics for a Container App.
162
+ */
163
+ async function checkResourceMetrics(
164
+ resource: armResources.GenericResource,
165
+ monitorClient: MonitorClient,
166
+ timespanDays: number,
167
+ reason: string,
168
+ verbose: boolean,
169
+ ): Promise<string> {
170
+ let newReason = reason;
171
+
172
+ if (!resource.id) {
173
+ return newReason;
174
+ }
175
+
176
+ verboseLog(verbose, "Checking resource usage metrics...");
177
+
178
+ const cpuUsage = await getMetric(
179
+ monitorClient,
180
+ resource.id,
181
+ "UsageNanoCores",
182
+ "Average",
183
+ timespanDays,
184
+ );
185
+
186
+ const memoryUsage = await getMetric(
187
+ monitorClient,
188
+ resource.id,
189
+ "WorkingSetBytes",
190
+ "Average",
191
+ timespanDays,
192
+ );
193
+
194
+ verboseLog(
195
+ verbose,
196
+ `CPU Usage: ${cpuUsage !== null ? `${(cpuUsage / 1000000000).toFixed(4)} cores` : "N/A"}`,
197
+ );
198
+ verboseLog(
199
+ verbose,
200
+ `Memory Usage: ${memoryUsage !== null ? `${(memoryUsage / 1048576).toFixed(2)} MB` : "N/A"}`,
201
+ );
202
+
203
+ if (cpuUsage !== null && cpuUsage < 1000000) {
204
+ newReason += `Very low CPU usage (${(cpuUsage / 1000000000).toFixed(4)} cores). `;
205
+ }
206
+
207
+ if (memoryUsage !== null && memoryUsage < 10485760) {
208
+ newReason += `Very low memory usage (${(memoryUsage / 1048576).toFixed(2)} MB). `;
209
+ }
210
+
211
+ return newReason;
212
+ }
213
+
214
+ /**
215
+ * Checks the running status of a Container App.
216
+ */
217
+ function checkRunningStatus(
218
+ appDetails: {
219
+ properties?: {
220
+ provisioningState?: string;
221
+ runningStatus?: string;
222
+ template?: { scale?: { maxReplicas?: number; minReplicas?: number } };
223
+ };
224
+ },
225
+ reason: string,
226
+ verbose: boolean,
227
+ ): string {
228
+ let newReason = reason;
229
+
230
+ const { provisioningState, runningStatus, template } =
231
+ appDetails.properties || {};
232
+
233
+ verboseLog(verbose, `Min Replicas: ${template?.scale?.minReplicas ?? "N/A"}`);
234
+ verboseLog(verbose, `Max Replicas: ${template?.scale?.maxReplicas ?? "N/A"}`);
235
+
236
+ if (provisioningState === "Succeeded" && runningStatus !== "Running") {
237
+ newReason += "Container App is not running. ";
238
+ }
239
+
240
+ const minReplicas = template?.scale?.minReplicas;
241
+ const maxReplicas = template?.scale?.maxReplicas;
242
+ if (minReplicas === 0 && maxReplicas === 0) {
243
+ newReason += "Container App has 0 replicas configured. ";
244
+ }
245
+
246
+ return newReason;
247
+ }
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  export { analyzeAppServicePlan } from "./app-service.js";
6
+ export { analyzeContainerApp } from "./container-app.js";
6
7
  export { analyzeDisk } from "./disk.js";
7
8
  export { analyzeNic } from "./nic.js";
8
9
  export { analyzePrivateEndpoint } from "./private-endpoint.js";
@@ -83,13 +83,13 @@ export async function analyzePublicIp(
83
83
  monitorClient,
84
84
  resource.id,
85
85
  "BytesInDDoS",
86
- "Total",
86
+ "Average",
87
87
  timespanDays,
88
88
  );
89
89
 
90
- if (bytesInDDoS !== null && bytesInDDoS < 1000000) {
91
- // Less than 1MB total in 30 days
92
- reason += `Very low network traffic (${(bytesInDDoS / 1024 / 1024).toFixed(2)} MB). `;
90
+ if (bytesInDDoS !== null && bytesInDDoS < 340000) {
91
+ // Less than ~340KB average per day
92
+ reason += `Very low network traffic (${(bytesInDDoS / 1024 / 1024).toFixed(2)} MB/day avg). `;
93
93
  }
94
94
  } catch (error) {
95
95
  const logger = getLogger(["savemoney", "azure"]);
@@ -48,14 +48,14 @@ export async function analyzeStorageAccount(
48
48
  monitorClient,
49
49
  resource.id,
50
50
  "Transactions",
51
- "Total",
51
+ "Average",
52
52
  timespanDays,
53
53
  );
54
- if (transactions !== null && transactions < 100) {
55
- // Very low transactions
54
+ if (transactions !== null && transactions < 10) {
55
+ // Less than 10 transactions per day on average
56
56
  const result = {
57
57
  costRisk,
58
- reason: `Very low transaction count (${transactions}). `,
58
+ reason: `Very low transaction count (${transactions.toFixed(2)} avg/day). `,
59
59
  suspectedUnused: true,
60
60
  };
61
61
  verboseLogAnalysisResult(verbose, result);
@@ -110,7 +110,7 @@ export async function analyzeVM(
110
110
  monitorClient,
111
111
  resource.id,
112
112
  "Network In Total",
113
- "Total",
113
+ "Average",
114
114
  timespanDays,
115
115
  );
116
116
 
@@ -118,9 +118,9 @@ export async function analyzeVM(
118
118
  // Less than 1% average CPU
119
119
  reason += `Low CPU usage (avg ${cpuUsage.toFixed(2)}%). `;
120
120
  }
121
- if (networkIn !== null && networkIn < 1024 * 1024 * 10) {
122
- // Less than 10MB total network in
123
- reason += `Low network traffic. `;
121
+ if (networkIn !== null && networkIn < 1024 * 1024 * 3) {
122
+ // Less than 3MB average per day
123
+ reason += `Low network traffic (${(networkIn / 1024 / 1024).toFixed(2)} MB/day avg). `;
124
124
  }
125
125
 
126
126
  const result = { costRisk, reason, suspectedUnused: reason.length > 0 };
@@ -8,6 +8,100 @@ import { getLogger } from "@logtape/logtape";
8
8
 
9
9
  import type { AnalysisResult } from "../types.js";
10
10
 
11
+ type MetricDataPoint = {
12
+ average?: number;
13
+ count?: number;
14
+ maximum?: number;
15
+ minimum?: number;
16
+ total?: number;
17
+ };
18
+
19
+ /**
20
+ * Aggregates metric data points based on aggregation type.
21
+ *
22
+ * @param dataPoints - Array of metric data points
23
+ * @param aggregation - The aggregation type (e.g., "Average", "Total")
24
+ * @returns The aggregated value or null if unavailable
25
+ */
26
+ export function aggregateDataPoints(
27
+ dataPoints: MetricDataPoint[],
28
+ aggregation: string,
29
+ ): null | number {
30
+ const aggregationLower = aggregation.toLowerCase();
31
+
32
+ // Get all non-null values from the data points
33
+ const values = dataPoints
34
+ .map((dataPoint) => extractAggregatedValue(dataPoint, aggregation))
35
+ .filter((v): v is number => v !== null);
36
+
37
+ if (values.length === 0) {
38
+ return null;
39
+ }
40
+
41
+ if (aggregationLower === "total" || aggregationLower === "count") {
42
+ // Sum all values for Total/Count
43
+ return values.reduce((sum, v) => sum + v, 0);
44
+ }
45
+
46
+ if (aggregationLower === "average") {
47
+ // Calculate the average of all values
48
+ return values.reduce((sum, v) => sum + v, 0) / values.length;
49
+ }
50
+
51
+ if (aggregationLower === "maximum") {
52
+ // Find the maximum value
53
+ return Math.max(...values);
54
+ }
55
+
56
+ if (aggregationLower === "minimum") {
57
+ // Find the minimum value
58
+ return Math.min(...values);
59
+ }
60
+
61
+ return null;
62
+ }
63
+
64
+ /**
65
+ * Extracts the aggregated value from metric data.
66
+ *
67
+ * @param metricData - The metric data point
68
+ * @param aggregation - The aggregation type (e.g., "Average", "Total")
69
+ * @returns The aggregated value or null if unavailable
70
+ */
71
+ export function extractAggregatedValue(
72
+ metricData: MetricDataPoint,
73
+ aggregation: string,
74
+ ): null | number {
75
+ const aggregationLower = aggregation.toLowerCase();
76
+
77
+ if (
78
+ aggregationLower === "average" &&
79
+ typeof metricData.average === "number"
80
+ ) {
81
+ return metricData.average;
82
+ }
83
+ if (aggregationLower === "total" && typeof metricData.total === "number") {
84
+ return metricData.total;
85
+ }
86
+ if (
87
+ aggregationLower === "minimum" &&
88
+ typeof metricData.minimum === "number"
89
+ ) {
90
+ return metricData.minimum;
91
+ }
92
+ if (
93
+ aggregationLower === "maximum" &&
94
+ typeof metricData.maximum === "number"
95
+ ) {
96
+ return metricData.maximum;
97
+ }
98
+ if (aggregationLower === "count" && typeof metricData.count === "number") {
99
+ return metricData.count;
100
+ }
101
+
102
+ return null;
103
+ }
104
+
11
105
  /**
12
106
  * Fetches a specific metric for a resource from Azure Monitor.
13
107
  *
@@ -33,39 +127,28 @@ export async function getMetric(
33
127
  timespan,
34
128
  });
35
129
 
36
- const metricData = result.value[0]?.timeseries?.[0]?.data?.[0];
37
- if (!metricData) {
130
+ if (result.value.length === 0) {
38
131
  return null;
39
132
  }
40
133
 
41
- const aggregationLower = aggregation.toLowerCase();
134
+ const metric = result.value[0];
42
135
 
43
- if (
44
- aggregationLower === "average" &&
45
- typeof metricData.average === "number"
46
- ) {
47
- return metricData.average;
48
- }
49
- if (aggregationLower === "total" && typeof metricData.total === "number") {
50
- return metricData.total;
51
- }
52
- if (
53
- aggregationLower === "minimum" &&
54
- typeof metricData.minimum === "number"
55
- ) {
56
- return metricData.minimum;
57
- }
58
- if (
59
- aggregationLower === "maximum" &&
60
- typeof metricData.maximum === "number"
61
- ) {
62
- return metricData.maximum;
136
+ if (!metric.timeseries || metric.timeseries.length === 0) {
137
+ return null;
63
138
  }
64
- if (aggregationLower === "count" && typeof metricData.count === "number") {
65
- return metricData.count;
139
+
140
+ const timeserie = metric.timeseries[0];
141
+
142
+ if (!timeserie.data || timeserie.data.length === 0) {
143
+ return null;
66
144
  }
67
145
 
68
- return null;
146
+ const aggregatedValue = aggregateDataPoints(
147
+ timeserie.data as MetricDataPoint[],
148
+ aggregation,
149
+ );
150
+
151
+ return aggregatedValue;
69
152
  } catch (error) {
70
153
  const logger = getLogger(["savemoney", "azure", "metrics"]);
71
154
  logger.error(