-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from Theemiss/dev
Dev
- Loading branch information
Showing
9 changed files
with
314 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
Copyright (c) 2024 The Python Packaging Authority | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,115 @@ | ||
# aws-monitor | ||
# AWS Cost Monitoring Package | ||
|
||
A python package for monitoring AWS resources. | ||
|
||
## Features | ||
|
||
- Monitor AWS Cost By Period & Service, Linked Account | ||
- onitor AWS Cost to monitor Service by Linked Account | ||
- Graphs | ||
This package provides tools for monitoring and analyzing AWS costs, both at the organizational level and individual account level. It includes functionality to get detailed cost breakdowns by linked accounts and AWS services, along with visualizations. | ||
|
||
## Installation | ||
|
||
To install the package, use pip: | ||
|
||
```bash | ||
pip install aws-monitor==0.4 | ||
pip install aws-monitor | ||
``` | ||
|
||
This will also install the following dependencies: | ||
|
||
- `boto3` | ||
- `matplotlib` | ||
|
||
## Usage | ||
|
||
### AWSOrgMonitor Class | ||
|
||
This class inherits from `AWSCostMonitor` and provides methods to fetch and analyze AWS cost data. | ||
|
||
#### Initialization | ||
|
||
```python | ||
from aws_monitoring.utils import AWSCostMonitor | ||
|
||
class AWSOrgMonitor(AWSCostMonitor): | ||
def __init__(self): | ||
super().__init__() | ||
``` | ||
|
||
#### Get Cost | ||
|
||
Fetches the cost data within the specified time period and granularity. | ||
|
||
```python | ||
def get_cost(self, start: str, end: str, granularity: str, metrics: list, group_by: list = [], json=False) -> dict: | ||
``` | ||
|
||
- **start**: Start date in `YYYY-MM-DD` format. | ||
- **end**: End date in `YYYY-MM-DD` format. | ||
- **granularity**: Granularity of the data (DAILY, MONTHLY). | ||
- **metrics**: List of metrics to fetch (e.g., ['UNBLENDED_COST']). | ||
- **group_by**: List of dimensions to group by. | ||
- **json**: Whether to return the response in JSON format. | ||
|
||
#### Get Billed Accounts | ||
|
||
Fetches the list of billed accounts within the specified time period. | ||
|
||
```python | ||
def get_billed_accounts(self, start: str, end: str) -> dict: | ||
``` | ||
|
||
#### Get Cost Per Account | ||
|
||
Fetches the cost per account within the specified time period. | ||
|
||
```python | ||
def get_cost_per_account(self, start, end) -> dict: | ||
``` | ||
|
||
#### Get Cost Per Account Graph | ||
|
||
Displays a bar graph of the cost per account within the specified time period. | ||
|
||
```python | ||
def get_cost_per_account_graph(self, start, end): | ||
``` | ||
|
||
#### Get Cost Per Service | ||
|
||
Fetches the cost per AWS service within the specified time period and optionally for a specific linked account. | ||
|
||
```python | ||
def get_cost_per_service(self, start: str, end: str, linked_account: str = None) -> dict: | ||
``` | ||
|
||
#### Get Cost Per Service Graph | ||
|
||
Displays a bar graph of the cost per AWS service within the specified time period and optionally for a specific linked account. | ||
|
||
```python | ||
def get_cost_per_service_graph(self, start: str, end: str, linked_account: str = None): | ||
``` | ||
|
||
### Example Usage | ||
|
||
```python | ||
from aws_monitoring.utils import AWSOrgMonitor | ||
|
||
monitor = AWSOrgMonitor() | ||
start_date = '2023-06-01' | ||
end_date = '2023-06-30' | ||
|
||
# Get cost per service | ||
service_costs = monitor.get_cost_per_service(start_date, end_date) | ||
print(service_costs) | ||
|
||
# Visualize cost per service | ||
monitor.get_cost_per_service_graph(start_date, end_date) | ||
|
||
# Get cost per account | ||
account_costs = monitor.get_cost_per_account(start_date, end_date) | ||
print(account_costs) | ||
|
||
# Visualize cost per account | ||
monitor.get_cost_per_account_graph(start_date, end_date) | ||
``` | ||
|
||
## License | ||
|
||
This package is licensed under the MIT License. See the [LICENSE](LICENSE) file for more information. |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,2 @@ | ||
import boto3 | ||
from datetime import datetime, timedelta | ||
from aws_monitoring.config import AWSConfig | ||
|
||
|
||
class AWSCostMonitor(): | ||
def __init__(self, config: AWSConfig = AWSConfig()) -> None: | ||
self.client = boto3.client( | ||
'ce', region_name=config.DEFAULT_REGIONS[0], aws_access_key_id=config.AWS_ACCESS_KEY_ID, aws_secret_access_key=config.AWS_SECRET_ACCESS_KEY) | ||
|
||
def get_cost_and_usage(self, start: str, end: str, granularity: str, metrics: list, group_by: list) -> dict: | ||
response = self.client.get_cost_and_usage( | ||
TimePeriod={ | ||
'Start': start, | ||
'End': end | ||
}, | ||
Granularity=granularity, | ||
Metrics=metrics, | ||
GroupBy=[{'Type': 'COST_CATEGORY', 'Key': item} | ||
for item in group_by] | ||
) | ||
return response | ||
from aws_monitoring.utils import AWSCostMonitor | ||
from aws_monitoring.org import AWSOrgMonitor |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from aws_monitoring.org.utils import AWSOrgMonitor |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
|
||
from aws_monitoring.utils import AWSCostMonitor | ||
|
||
|
||
class AWSOrgMonitor(AWSCostMonitor): | ||
def __init__(self): | ||
super().__init__() | ||
|
||
def get_cost(self, start: str, end: str, granularity: str, metrics: list, group_by: list = [], json=False) -> dict: | ||
group_by = ["LINKED_ACCOUNT"] + group_by | ||
response = self.client.get_cost_and_usage( | ||
TimePeriod={ | ||
'Start': start, | ||
'End': end | ||
}, | ||
Granularity=granularity, | ||
Metrics=metrics, | ||
GroupBy=[{'Type': 'COST_CATEGORY', 'Key': item} | ||
for item in group_by] | ||
) | ||
if json: | ||
return self.to_json(response) | ||
return response | ||
|
||
def get_billed_accounts(self, start: str, end: str) -> dict: | ||
response = self.client.get_dimension_values( | ||
TimePeriod={ | ||
'Start': start, | ||
'End': end | ||
}, | ||
Dimension='LINKED_ACCOUNT', | ||
Context='COST_AND_USAGE', | ||
SearchString="*" | ||
) | ||
users = [{"id": item['Value'], "name": item['Attributes'] | ||
['description']} for item in response['DimensionValues']] | ||
|
||
return users | ||
|
||
def get_cost_per_account(self, start, end) -> dict: | ||
billed_accounts = self.get_billed_accounts(start, end) | ||
response = self.client.get_cost_and_usage( | ||
TimePeriod={ | ||
'Start': start, | ||
'End': end | ||
}, | ||
Granularity='MONTHLY', | ||
Metrics=['UNBLENDED_COST'], | ||
GroupBy=[ | ||
{'Type': 'DIMENSION', 'Key': 'LINKED_ACCOUNT'} | ||
] | ||
) | ||
|
||
accounts_cost = {} | ||
for group in response['ResultsByTime'][0]['Groups']: | ||
account_id = group['Keys'][0] | ||
cost = group['Metrics']['UnblendedCost']['Amount'] | ||
account_name = next( | ||
(account['name'] for account in billed_accounts if account['id'] == account_id), account_id) | ||
accounts_cost[account_name] = cost | ||
|
||
return accounts_cost | ||
|
||
def get_cost_per_account_graph(self, start, end): | ||
import matplotlib.pyplot as plt | ||
|
||
accounts_cost = self.get_cost_per_account(start, end) | ||
accounts = list(accounts_cost.keys()) | ||
costs = list(accounts_cost.values()) | ||
sorted_accounts_cost = sorted(zip(costs, accounts), reverse=False) | ||
sorted_costs, sorted_accounts = zip(*sorted_accounts_cost) | ||
|
||
fig, ax = plt.subplots() | ||
ax.bar(sorted_accounts, sorted_costs) | ||
ax.set_xlabel('Accounts') | ||
ax.set_ylabel('Cost (USD)') | ||
ax.set_title(f'Cost per Account From {start} to {end}') | ||
|
||
# Rotate the x-axis labels for better readability | ||
plt.xticks(rotation=45) | ||
plt.tight_layout() | ||
|
||
return plt.show() | ||
|
||
def get_cost_per_service(self, start: str, end: str, linked_account: str = None) -> dict: | ||
group_by = [{'Type': 'DIMENSION', 'Key': 'SERVICE'}] | ||
if linked_account: | ||
group_by.append({'Type': 'DIMENSION', 'Key': 'LINKED_ACCOUNT'}) | ||
|
||
response = self.client.get_cost_and_usage( | ||
TimePeriod={ | ||
'Start': start, | ||
'End': end | ||
}, | ||
Granularity='DAILY', | ||
Metrics=['UNBLENDED_COST'], | ||
GroupBy=group_by | ||
) | ||
|
||
service_cost = {} | ||
for group in response['ResultsByTime'][0]['Groups']: | ||
service_name = group['Keys'][0] | ||
cost = group['Metrics']['UnblendedCost']['Amount'] | ||
service_cost[service_name] = cost | ||
service_cost = {k: v for k, v in service_cost.items() if v != 0} | ||
return service_cost | ||
|
||
def get_cost_per_service_graph(self, start: str, end: str, linked_account: str = None): | ||
import matplotlib.pyplot as plt | ||
service_cost = self.get_cost_per_service(start, end, linked_account) | ||
services = list(service_cost.keys()) | ||
costs = list(service_cost.values()) | ||
sorted_service_cost = sorted(zip(costs, services), reverse=False) | ||
sorted_costs, sorted_services = zip(*sorted_service_cost) | ||
fig, ax = plt.subplots() | ||
ax.bar(sorted_services, sorted_costs) | ||
ax.set_xlabel('Services') | ||
ax.set_ylabel('Cost (USD)') | ||
ax.set_title(f'Cost per Service From {start} to {end}') | ||
|
||
plt.xticks(rotation=45) | ||
plt.tight_layout() | ||
|
||
plt.show() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import boto3 | ||
from datetime import datetime, timedelta | ||
from aws_monitoring.config import AWSConfig | ||
import json | ||
|
||
class AWSCostMonitor(): | ||
def __init__(self, config: AWSConfig = AWSConfig()) -> None: | ||
self.client = boto3.client( | ||
'ce', region_name=config.DEFAULT_REGIONS[0], aws_access_key_id=config.AWS_ACCESS_KEY_ID, aws_secret_access_key=config.AWS_SECRET_ACCESS_KEY) | ||
|
||
def get_cost(self, start: str, end: str, granularity: str, metrics: list, group_by: list = []) -> dict: | ||
response = self.client.get_cost_and_usage( | ||
TimePeriod={ | ||
'Start': start, | ||
'End': end | ||
}, | ||
Granularity=granularity, | ||
Metrics=metrics, | ||
GroupBy=[{'Type': 'COST_CATEGORY', 'Key': item} | ||
for item in group_by] | ||
) | ||
|
||
return response | ||
|
||
@staticmethod | ||
def to_json(response): | ||
with open('response.json', 'w') as f: | ||
json.dump(response, f, indent=4) | ||
return { | ||
"ResultsByTime": [ | ||
{ | ||
"TimePeriod": result["TimePeriod"], | ||
"Total": result["Total"], | ||
"Groups": [ | ||
{ | ||
"Keys": group["Keys"], | ||
"Metrics": { | ||
metric["Key"]: metric["Amount"] for metric in group["Metrics"].values() | ||
} | ||
} for group in result["Groups"] | ||
] | ||
} for result in response["ResultsByTime"] | ||
] | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,21 @@ | ||
|
||
|
||
from aws_monitoring import AWSCostMonitor | ||
import json | ||
|
||
from aws_monitoring import AWSCostMonitor, AWSOrgMonitor | ||
import datetime | ||
from datetime import timedelta | ||
|
||
cost = AWSCostMonitor() | ||
|
||
org_cost = AWSOrgMonitor() | ||
|
||
data = cost.get_cost_and_usage(start='2024-01-01', end='2024-06-30', | ||
granularity='MONTHLY', metrics=['BlendedCost'], group_by=[]) | ||
|
||
print(data) | ||
print('\n\n\n') | ||
# Get The costs for last month | ||
last_month = datetime.datetime.now() - timedelta(days=30) | ||
start_date = last_month.replace(day=1) | ||
end_date = start_date + timedelta(days=32) | ||
|
||
# Get the costs for the last 30 days | ||
end_date = datetime.datetime.now() | ||
start_date = end_date - timedelta(days=30) | ||
|
||
data = cost.get_cost_and_usage(start=start_date.strftime('%Y-%m-%d'), end=end_date.strftime('%Y-%m-%d'), | ||
granularity='DAILY', metrics=['BlendedCost'], group_by=[]) | ||
|
||
# Get The costs for last 30 days and group them by service | ||
end_date = datetime.datetime.now() | ||
start_date = end_date - timedelta(days=30) | ||
data = cost.get_cost_and_usage(start=start_date.strftime('%Y-%m-%d'), end=end_date.strftime('%Y-%m-%d'), | ||
granularity='DAILY', metrics=['BlendedCost'], group_by=["Service","Linked account"]) | ||
service= org_cost.get_cost_per_service_graph(start=start_date.strftime( | ||
'%Y-%m-%d'), end=end_date.strftime('%Y-%m-%d')) | ||
print(service) |