diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b1bb39e --- /dev/null +++ b/LICENSE @@ -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. \ No newline at end of file diff --git a/README.md b/README.md index b0aea6c..50cf24b 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/aws-monitor.toml b/aws-monitor.toml new file mode 100644 index 0000000..e69de29 diff --git a/aws_monitoring/__init__.py b/aws_monitoring/__init__.py index f384a61..47ce861 100644 --- a/aws_monitoring/__init__.py +++ b/aws_monitoring/__init__.py @@ -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 \ No newline at end of file diff --git a/aws_monitoring/org/__init__.py b/aws_monitoring/org/__init__.py new file mode 100644 index 0000000..754e108 --- /dev/null +++ b/aws_monitoring/org/__init__.py @@ -0,0 +1 @@ +from aws_monitoring.org.utils import AWSOrgMonitor \ No newline at end of file diff --git a/aws_monitoring/org/utils.py b/aws_monitoring/org/utils.py new file mode 100644 index 0000000..c4a37ac --- /dev/null +++ b/aws_monitoring/org/utils.py @@ -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() diff --git a/aws_monitoring/utils.py b/aws_monitoring/utils.py new file mode 100644 index 0000000..9bb589c --- /dev/null +++ b/aws_monitoring/utils.py @@ -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"] + ] + } + + diff --git a/setup.py b/setup.py index 276feee..6da08f1 100644 --- a/setup.py +++ b/setup.py @@ -11,6 +11,7 @@ packages=find_packages(), install_requires=[ 'boto3', + 'matplotlib' ], entry_points={ 'console_scripts': [ diff --git a/test.py b/test.py index b1801b1..87d57e4 100644 --- a/test.py +++ b/test.py @@ -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) \ No newline at end of file