@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 +56 -9
- package/dist/azure/analyzer.d.ts +3 -1
- package/dist/azure/analyzer.d.ts.map +1 -1
- package/dist/azure/analyzer.js +11 -3
- package/dist/azure/analyzer.js.map +1 -1
- package/dist/azure/resources/container-app.d.ts +19 -0
- package/dist/azure/resources/container-app.d.ts.map +1 -0
- package/dist/azure/resources/container-app.js +113 -0
- package/dist/azure/resources/container-app.js.map +1 -0
- package/dist/azure/resources/index.d.ts +1 -0
- package/dist/azure/resources/index.d.ts.map +1 -1
- package/dist/azure/resources/index.js +1 -0
- package/dist/azure/resources/index.js.map +1 -1
- package/dist/azure/resources/public-ip.js +4 -4
- package/dist/azure/resources/public-ip.js.map +1 -1
- package/dist/azure/resources/storage.js +4 -4
- package/dist/azure/resources/storage.js.map +1 -1
- package/dist/azure/resources/vm.js +4 -4
- package/dist/azure/resources/vm.js.map +1 -1
- package/dist/azure/utils.d.ts +24 -0
- package/dist/azure/utils.d.ts.map +1 -1
- package/dist/azure/utils.js +72 -20
- package/dist/azure/utils.js.map +1 -1
- package/package.json +5 -4
- package/src/azure/analyzer.ts +20 -0
- package/src/azure/resources/container-app.ts +247 -0
- package/src/azure/resources/index.ts +1 -0
- package/src/azure/resources/public-ip.ts +4 -4
- package/src/azure/resources/storage.ts +4 -4
- package/src/azure/resources/vm.ts +4 -4
- package/src/azure/utils.ts +109 -26
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 (<
|
|
45
|
-
| **App Service Plans** | API Details + Metrics | 🔴 High | No apps deployed, Very low CPU (<5%), Very low memory (<10%), Oversized Premium tier
|
|
46
|
-
| **
|
|
47
|
-
| **
|
|
48
|
-
| **
|
|
49
|
-
| **
|
|
50
|
-
| **
|
|
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
|
|
package/dist/azure/analyzer.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/azure/analyzer.js
CHANGED
|
@@ -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":"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;AACrD,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAI7C,OAAO,EAAuB,YAAY,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EACL,qBAAqB,EACrB,WAAW,EACX,UAAU,EACV,sBAAsB,EACtB,eAAe,EACf,qBAAqB,EACrB,SAAS,GACV,MAAM,sBAAsB,CAAC;AAE9B;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,MAAmB,EACnB,MAA0C;IAE1C,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;IACjD,MAAM,UAAU,GAAG,IAAI,sBAAsB,EAAE,CAAC;IAChD,MAAM,UAAU,GAAkC,EAAE,CAAC;IAErD,KAAK,MAAM,cAAc,IAAI,MAAM,CAAC,eAAe,EAAE,CAAC;QACpD,MAAM,CAAC,IAAI,CAAC,2BAA2B,cAAc,EAAE,CAAC,CAAC;QAEzD,MAAM,cAAc,GAAG,IAAI,YAAY,CAAC,wBAAwB,CAC9D,UAAU,EACV,cAAc,CAAC,IAAI,EAAE,CACtB,CAAC;QACF,MAAM,aAAa,GAAG,IAAI,aAAa,CAAC,UAAU,EAAE,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC;QAC3E,MAAM,aAAa,GAAG,IAAI,uBAAuB,CAC/C,UAAU,EACV,cAAc,CAAC,IAAI,EAAE,CACtB,CAAC;QACF,MAAM,aAAa,GAAG,IAAI,uBAAuB,CAC/C,UAAU,EACV,cAAc,CAAC,IAAI,EAAE,CACtB,CAAC;QACF,MAAM,aAAa,GAAG,IAAI,uBAAuB,CAC/C,UAAU,EACV,cAAc,CAAC,IAAI,EAAE,CACtB,CAAC;QAEF,0EAA0E;QAC1E,IAAI,KAAK,EAAE,MAAM,QAAQ,IAAI,cAAc,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC;YAC7D,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,eAAe,CACjE,QAAQ,EACR,aAAa,EACb,aAAa,EACb,aAAa,EACb,aAAa,EACb,MAAM,CAAC,iBAAiB,EACxB,MAAM,CAAC,YAAY,EACnB,MAAM,CAAC,OAAO,IAAI,KAAK,CACxB,CAAC;YAEF,IAAI,eAAe,EAAE,CAAC;gBACpB,UAAU,CAAC,IAAI,CAAC;oBACd,QAAQ,EAAE;wBACR,QAAQ;wBACR,MAAM,EAAE,MAAM,IAAI,uBAAuB;wBACzC,eAAe;qBAChB;oBACD,QAAQ,EAAE,QAAQ;iBACnB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,wCAAwC;IACxC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACvB,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,KAAK,CAAC,CAAC,QAAQ,CAAC,QAAQ;YAC7C,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;QACtE,MAAM,KAAK,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;QAC7C,OAAO,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,MAAM,cAAc,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AAC3C,CAAC;AAED
|
|
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", "
|
|
46
|
-
if (bytesInDDoS !== null && bytesInDDoS <
|
|
47
|
-
// Less than
|
|
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,
|
|
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", "
|
|
25
|
-
if (transactions !== null && transactions <
|
|
26
|
-
//
|
|
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,
|
|
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", "
|
|
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 *
|
|
70
|
-
// Less than
|
|
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":"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;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,QAAsC,EACtC,aAA4B,EAC5B,aAAsC,EACtC,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,MAAM,CAAC;IACnD,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,sDAAsD;IACtD,MAAM,aAAa,GAAG,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7C,MAAM,iBAAiB,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAEhC,IAAI,CAAC;QACH,uDAAuD;QACvD,MAAM,YAAY,GAAG,MAAM,aAAa,CAAC,eAAe,CAAC,YAAY,CACnE,iBAAiB,EACjB,MAAM,CACP,CAAC;QAEF,UAAU,CAAC,OAAO,EAAE,mBAAmB,EAAE,YAAY,CAAC,CAAC;QAEvD,uCAAuC;QACvC,MAAM,QAAQ,GAAG,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAoB,EAAE,EAAE,CACpE,CAAC,CAAC,IAAI,EAAE,UAAU,CAAC,aAAa,CAAC,CAClC,CAAC;QAEF,IAAI,QAAQ,EAAE,IAAI,KAAK,wBAAwB,EAAE,CAAC;YAChD,MAAM,MAAM,GAAG;gBACb,QAAQ;gBACR,MAAM,EAAE,qBAAqB;gBAC7B,eAAe,EAAE,IAAI;aACtB,CAAC;YACF,wBAAwB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC1C,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,IAAI,QAAQ,EAAE,IAAI,KAAK,oBAAoB,EAAE,CAAC;YAC5C,MAAM,MAAM,GAAG;gBACb,QAAQ;gBACR,MAAM,EAAE,iBAAiB;gBACzB,eAAe,EAAE,IAAI;aACtB,CAAC;YACF,wBAAwB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC1C,OAAO,MAAM,CAAC;QAChB,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,sCAAsC,MAAM,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,CAClG,CAAC;QACF,uDAAuD;IACzD,CAAC;IAED,oCAAoC;IACpC,MAAM,QAAQ,GAAG,MAAM,SAAS,CAC9B,aAAa,EACb,QAAQ,CAAC,EAAE,EACX,gBAAgB,EAChB,SAAS,EACT,YAAY,CACb,CAAC;IACF,MAAM,SAAS,GAAG,MAAM,SAAS,CAC/B,aAAa,EACb,QAAQ,CAAC,EAAE,EACX,kBAAkB,EAClB,
|
|
1
|
+
{"version":3,"file":"vm.js","sourceRoot":"","sources":["../../../src/azure/resources/vm.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;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,QAAsC,EACtC,aAA4B,EAC5B,aAAsC,EACtC,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,MAAM,CAAC;IACnD,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,sDAAsD;IACtD,MAAM,aAAa,GAAG,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7C,MAAM,iBAAiB,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAEhC,IAAI,CAAC;QACH,uDAAuD;QACvD,MAAM,YAAY,GAAG,MAAM,aAAa,CAAC,eAAe,CAAC,YAAY,CACnE,iBAAiB,EACjB,MAAM,CACP,CAAC;QAEF,UAAU,CAAC,OAAO,EAAE,mBAAmB,EAAE,YAAY,CAAC,CAAC;QAEvD,uCAAuC;QACvC,MAAM,QAAQ,GAAG,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAoB,EAAE,EAAE,CACpE,CAAC,CAAC,IAAI,EAAE,UAAU,CAAC,aAAa,CAAC,CAClC,CAAC;QAEF,IAAI,QAAQ,EAAE,IAAI,KAAK,wBAAwB,EAAE,CAAC;YAChD,MAAM,MAAM,GAAG;gBACb,QAAQ;gBACR,MAAM,EAAE,qBAAqB;gBAC7B,eAAe,EAAE,IAAI;aACtB,CAAC;YACF,wBAAwB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC1C,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,IAAI,QAAQ,EAAE,IAAI,KAAK,oBAAoB,EAAE,CAAC;YAC5C,MAAM,MAAM,GAAG;gBACb,QAAQ;gBACR,MAAM,EAAE,iBAAiB;gBACzB,eAAe,EAAE,IAAI;aACtB,CAAC;YACF,wBAAwB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC1C,OAAO,MAAM,CAAC;QAChB,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,sCAAsC,MAAM,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,CAClG,CAAC;QACF,uDAAuD;IACzD,CAAC;IAED,oCAAoC;IACpC,MAAM,QAAQ,GAAG,MAAM,SAAS,CAC9B,aAAa,EACb,QAAQ,CAAC,EAAE,EACX,gBAAgB,EAChB,SAAS,EACT,YAAY,CACb,CAAC;IACF,MAAM,SAAS,GAAG,MAAM,SAAS,CAC/B,aAAa,EACb,QAAQ,CAAC,EAAE,EACX,kBAAkB,EAClB,SAAS,EACT,YAAY,CACb,CAAC;IAEF,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;QACtC,2BAA2B;QAC3B,MAAM,IAAI,sBAAsB,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;IAC5D,CAAC;IACD,IAAI,SAAS,KAAK,IAAI,IAAI,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC;QACtD,gCAAgC;QAChC,MAAM,IAAI,wBAAwB,CAAC,SAAS,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC;IACzF,CAAC;IAED,MAAM,MAAM,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;IACxE,wBAAwB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/dist/azure/utils.d.ts
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/azure/utils.js
CHANGED
|
@@ -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
|
-
|
|
24
|
-
if (!metricData) {
|
|
86
|
+
if (result.value.length === 0) {
|
|
25
87
|
return null;
|
|
26
88
|
}
|
|
27
|
-
const
|
|
28
|
-
if (
|
|
29
|
-
|
|
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
|
-
|
|
44
|
-
|
|
93
|
+
const timeserie = metric.timeseries[0];
|
|
94
|
+
if (!timeserie.data || timeserie.data.length === 0) {
|
|
95
|
+
return null;
|
|
45
96
|
}
|
|
46
|
-
|
|
97
|
+
const aggregatedValue = aggregateDataPoints(timeserie.data, aggregation);
|
|
98
|
+
return aggregatedValue;
|
|
47
99
|
}
|
|
48
100
|
catch (error) {
|
|
49
101
|
const logger = getLogger(["savemoney", "azure", "metrics"]);
|
package/dist/azure/utils.js.map
CHANGED
|
@@ -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;
|
|
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.
|
|
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.
|
|
41
|
+
"@types/node": "^22.19.1",
|
|
41
42
|
"@vitest/coverage-v8": "^3.2.4",
|
|
42
|
-
"eslint": "^9.
|
|
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.
|
|
47
|
+
"@pagopa/eslint-config": "^5.1.1"
|
|
47
48
|
},
|
|
48
49
|
"scripts": {
|
|
49
50
|
"build": "rm -rf dist && tsc",
|
package/src/azure/analyzer.ts
CHANGED
|
@@ -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
|
-
"
|
|
86
|
+
"Average",
|
|
87
87
|
timespanDays,
|
|
88
88
|
);
|
|
89
89
|
|
|
90
|
-
if (bytesInDDoS !== null && bytesInDDoS <
|
|
91
|
-
// Less than
|
|
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
|
-
"
|
|
51
|
+
"Average",
|
|
52
52
|
timespanDays,
|
|
53
53
|
);
|
|
54
|
-
if (transactions !== null && transactions <
|
|
55
|
-
//
|
|
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
|
-
"
|
|
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 *
|
|
122
|
-
// Less than
|
|
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 };
|
package/src/azure/utils.ts
CHANGED
|
@@ -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
|
-
|
|
37
|
-
if (!metricData) {
|
|
130
|
+
if (result.value.length === 0) {
|
|
38
131
|
return null;
|
|
39
132
|
}
|
|
40
133
|
|
|
41
|
-
const
|
|
134
|
+
const metric = result.value[0];
|
|
42
135
|
|
|
43
|
-
if (
|
|
44
|
-
|
|
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
|
-
|
|
65
|
-
|
|
139
|
+
|
|
140
|
+
const timeserie = metric.timeseries[0];
|
|
141
|
+
|
|
142
|
+
if (!timeserie.data || timeserie.data.length === 0) {
|
|
143
|
+
return null;
|
|
66
144
|
}
|
|
67
145
|
|
|
68
|
-
|
|
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(
|