@pagopa/dx-savemoney 0.1.1 → 0.1.3

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 (36) hide show
  1. package/README.md +58 -9
  2. package/dist/azure/analyzer.d.ts +3 -1
  3. package/dist/azure/analyzer.d.ts.map +1 -1
  4. package/dist/azure/analyzer.js +16 -3
  5. package/dist/azure/analyzer.js.map +1 -1
  6. package/dist/azure/resources/container-app.d.ts +19 -0
  7. package/dist/azure/resources/container-app.d.ts.map +1 -0
  8. package/dist/azure/resources/container-app.js +113 -0
  9. package/dist/azure/resources/container-app.js.map +1 -0
  10. package/dist/azure/resources/index.d.ts +2 -0
  11. package/dist/azure/resources/index.d.ts.map +1 -1
  12. package/dist/azure/resources/index.js +2 -0
  13. package/dist/azure/resources/index.js.map +1 -1
  14. package/dist/azure/resources/public-ip.js +4 -4
  15. package/dist/azure/resources/public-ip.js.map +1 -1
  16. package/dist/azure/resources/static-web-app.d.ts +17 -0
  17. package/dist/azure/resources/static-web-app.d.ts.map +1 -0
  18. package/dist/azure/resources/static-web-app.js +60 -0
  19. package/dist/azure/resources/static-web-app.js.map +1 -0
  20. package/dist/azure/resources/storage.js +4 -4
  21. package/dist/azure/resources/storage.js.map +1 -1
  22. package/dist/azure/resources/vm.js +4 -4
  23. package/dist/azure/resources/vm.js.map +1 -1
  24. package/dist/azure/utils.d.ts +24 -0
  25. package/dist/azure/utils.d.ts.map +1 -1
  26. package/dist/azure/utils.js +72 -20
  27. package/dist/azure/utils.js.map +1 -1
  28. package/package.json +5 -4
  29. package/src/azure/analyzer.ts +31 -0
  30. package/src/azure/resources/container-app.ts +247 -0
  31. package/src/azure/resources/index.ts +2 -0
  32. package/src/azure/resources/public-ip.ts +4 -4
  33. package/src/azure/resources/static-web-app.ts +108 -0
  34. package/src/azure/resources/storage.ts +4 -4
  35. package/src/azure/resources/vm.ts +4 -4
  36. package/src/azure/utils.ts +109 -26
@@ -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":"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"}
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.3",
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,10 +17,12 @@ 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,
22
24
  analyzePublicIp,
25
+ analyzeStaticSite,
23
26
  analyzeStorageAccount,
24
27
  analyzeVM,
25
28
  } from "./resources/index.js";
@@ -58,6 +61,10 @@ export async function analyzeAzureResources(
58
61
  credential,
59
62
  subscriptionId.trim(),
60
63
  );
64
+ const containerAppsClient = new ContainerAppsAPIClient(
65
+ credential,
66
+ subscriptionId.trim(),
67
+ );
61
68
 
62
69
  // Use the async iterator to avoid memory explosion for large environments
63
70
  for await (const resource of resourceClient.resources.list()) {
@@ -67,6 +74,7 @@ export async function analyzeAzureResources(
67
74
  computeClient,
68
75
  networkClient,
69
76
  webSiteClient,
77
+ containerAppsClient,
70
78
  config.preferredLocation,
71
79
  config.timespanDays,
72
80
  config.verbose || false,
@@ -104,6 +112,7 @@ export async function analyzeAzureResources(
104
112
  * @param computeClient - Azure Compute client
105
113
  * @param networkClient - Azure Network client
106
114
  * @param webSiteClient - Azure Web Site client
115
+ * @param containerAppsClient - Azure Container Apps client
107
116
  * @param preferredLocation - Preferred Azure location
108
117
  * @param timespanDays - Number of days to analyze metrics
109
118
  * @param verbose - Whether verbose logging is enabled
@@ -115,6 +124,7 @@ export async function analyzeResource(
115
124
  computeClient: ComputeManagementClient,
116
125
  networkClient: NetworkManagementClient,
117
126
  webSiteClient: WebSiteManagementClient,
127
+ containerAppsClient: ContainerAppsAPIClient,
118
128
  preferredLocation: string,
119
129
  timespanDays: number,
120
130
  verbose = false,
@@ -134,6 +144,17 @@ export async function analyzeResource(
134
144
 
135
145
  // Route to type-specific analysis hooks
136
146
  switch (type) {
147
+ case "microsoft.app/containerapps": {
148
+ const containerAppResult = await analyzeContainerApp(
149
+ resource,
150
+ containerAppsClient,
151
+ monitorClient,
152
+ timespanDays,
153
+ verbose,
154
+ );
155
+ result = mergeResults(result, containerAppResult);
156
+ break;
157
+ }
137
158
  case "microsoft.compute/disks": {
138
159
  const diskResult = await analyzeDisk(resource, computeClient, verbose);
139
160
  result = mergeResults(result, diskResult);
@@ -196,6 +217,16 @@ export async function analyzeResource(
196
217
  result = mergeResults(result, aspResult);
197
218
  break;
198
219
  }
220
+ case "microsoft.web/staticsites": {
221
+ const staticSiteResult = await analyzeStaticSite(
222
+ resource,
223
+ monitorClient,
224
+ timespanDays,
225
+ verbose,
226
+ );
227
+ result = mergeResults(result, staticSiteResult);
228
+ break;
229
+ }
199
230
  default:
200
231
  result.reason += "No specific analysis for this resource type. ";
201
232
  break;
@@ -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,9 +3,11 @@
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";
9
10
  export { analyzePublicIp } from "./public-ip.js";
11
+ export { analyzeStaticSite } from "./static-web-app.js";
10
12
  export { analyzeStorageAccount } from "./storage.js";
11
13
  export { analyzeVM } from "./vm.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"]);
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Azure Static Web App analysis
3
+ */
4
+
5
+ import type { MonitorClient } from "@azure/arm-monitor";
6
+
7
+ import * as armResources from "@azure/arm-resources";
8
+ import { getLogger } from "@logtape/logtape";
9
+
10
+ import type { AnalysisResult } from "../../types.js";
11
+
12
+ import {
13
+ getMetric,
14
+ verboseLog,
15
+ verboseLogAnalysisResult,
16
+ verboseLogResourceStart,
17
+ } from "../utils.js";
18
+
19
+ /**
20
+ * Analyzes an Azure Static Web App for potential cost optimization.
21
+ *
22
+ * @param resource - The Azure resource object
23
+ * @param monitorClient - Azure Monitor client for metrics
24
+ * @param timespanDays - Number of days to analyze metrics
25
+ * @param verbose - Enable verbose logging
26
+ * @returns Analysis result with cost risk and reason
27
+ */
28
+ export async function analyzeStaticSite(
29
+ resource: armResources.GenericResource,
30
+ monitorClient: MonitorClient,
31
+ timespanDays: number,
32
+ verbose = false,
33
+ ): Promise<AnalysisResult> {
34
+ verboseLogResourceStart(
35
+ verbose,
36
+ resource.name || "unknown",
37
+ "Static Web App (Microsoft.Web/staticSites)",
38
+ );
39
+ verboseLog(verbose, "Resource details:", resource);
40
+
41
+ const costRisk: "high" | "low" | "medium" = "low";
42
+ let reason = "";
43
+
44
+ if (!resource.id) {
45
+ return {
46
+ costRisk,
47
+ reason: "Resource ID is missing.",
48
+ suspectedUnused: false,
49
+ };
50
+ }
51
+
52
+ try {
53
+ verboseLog(verbose, "Checking metrics...");
54
+
55
+ // Check for site hits (requests to the static site)
56
+ // Note: Static Web Apps metrics use Total aggregation, not Average
57
+ // Ref. https://learn.microsoft.com/en-us/azure/azure-monitor/reference/supported-metrics/microsoft-web-staticsites-metrics
58
+ const siteHits = await getMetric(
59
+ monitorClient,
60
+ resource.id,
61
+ "SiteHits",
62
+ "Total",
63
+ timespanDays,
64
+ );
65
+
66
+ const bytesSent = await getMetric(
67
+ monitorClient,
68
+ resource.id,
69
+ "BytesSent",
70
+ "Total",
71
+ timespanDays,
72
+ );
73
+
74
+ verboseLog(
75
+ verbose,
76
+ `Site Hits: ${siteHits !== null ? `${siteHits.toFixed(0)} total requests` : "N/A"}`,
77
+ );
78
+ verboseLog(
79
+ verbose,
80
+ `Bytes Sent: ${bytesSent !== null ? `${(bytesSent / 1024 / 1024).toFixed(2)} MB total` : "N/A"}`,
81
+ );
82
+
83
+ // If both metrics are null, it means no data points exist (no traffic at all)
84
+ if (siteHits === null && bytesSent === null) {
85
+ reason += `No traffic data available in ${timespanDays} days. `;
86
+ } else {
87
+ if (siteHits !== null && siteHits < 100) {
88
+ // Less than 100 requests total in the timespan (< ~3.3 requests/day)
89
+ reason += `Very low site traffic (${siteHits.toFixed(0)} requests in ${timespanDays} days). `;
90
+ }
91
+
92
+ if (bytesSent !== null && bytesSent < 1048576) {
93
+ // Less than 1MB total in the timespan (< ~34KB/day)
94
+ reason += `Very low data transfer (${(bytesSent / 1024 / 1024).toFixed(2)} MB in ${timespanDays} days). `;
95
+ }
96
+ }
97
+ } catch (error) {
98
+ const logger = getLogger(["savemoney", "azure"]);
99
+ logger.warn(
100
+ `Failed to get metrics for Static Web App ${resource.name}: ${error instanceof Error ? error.message : error}`,
101
+ );
102
+ }
103
+
104
+ const suspectedUnused = reason.length > 0;
105
+ const result = { costRisk, reason: reason.trim(), suspectedUnused };
106
+ verboseLogAnalysisResult(verbose, result);
107
+ return result;
108
+ }
@@ -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);