Skip to content

Commit

Permalink
Merge pull request #4994 from consideRatio/pr/dashboards-dev
Browse files Browse the repository at this point in the history
AWS cost attribution: distinguish total costs for attributable costs and the AWS account's total
  • Loading branch information
consideRatio authored Oct 22, 2024
2 parents 3623159 + 5b47aed commit 1d0dbe4
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 39 deletions.
21 changes: 21 additions & 0 deletions grafana-dashboards/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# About these grafana dashboard files

These are dashboard definitions as jsonnet templates. They are deployed using a
Python script from https://github.com/jupyterhub/grafana-dashboard, which can be
done via the deployer command:

```bash
deployer grafana deploy-dashboards $CLUSTER_NAME
```

Running this command has a pre-requisite that you have jsonnet installed,
specifically the jsonnet binary built using golang called go-jsonnet.

To just render the jsonnet templates, which is relevant during development, you
can:

1. Clone https://github.com/jupyterhub/grafana-dashboard somewhere
2. Go to that folder, and then run something like:
```bash
jsonnet -J vendor /some/path/2i2c-org/infrastructure/grafana-dashboards/cloud-cost-aws.jsonnet
```
24 changes: 5 additions & 19 deletions grafana-dashboards/cloud-cost-aws.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ local totalDailyCosts =
common.queryTarget
+ {
url: "http://aws-ce-grafana-backend.support.svc.cluster.local/total-costs?from=${__from:date}&to=${__to:date}",
columns: [
{selector: "cost", text: "Cost", type: "number"},
{selector: "date", text: "Date", type: "timestamp"},
],
}
]);

Expand All @@ -39,11 +35,6 @@ local totalDailyCostsPerHub =
common.queryTarget
+ {
url: "http://aws-ce-grafana-backend.support.svc.cluster.local/total-costs-per-hub?from=${__from:date}&to=${__to:date}",
columns: [
{selector: "date", text: "Date", type: "timestamp"},
{selector: "name", text: "Name", type: "string"},
{selector: "cost", text: "Cost", type: "number"}
],
}
]);

Expand All @@ -60,11 +51,6 @@ local totalDailyCostsPerComponent =
common.queryTarget
+ {
url: "http://aws-ce-grafana-backend.support.svc.cluster.local/total-costs-per-component?from=${__from:date}&to=${__to:date}",
columns: [
{selector: "date", text: "Date", type: "timestamp"},
{selector: "name", text: "Name", type: "string"},
{selector: "cost", text: "Cost", type: "number"}
],
}
]);

Expand All @@ -83,16 +69,16 @@ local totalDailyCostsPerComponentAndHub =
common.queryTarget
+ {
url: "http://aws-ce-grafana-backend.support.svc.cluster.local/total-costs-per-component?from=${__from:date}&to=${__to:date}&hub=${hub}",
columns: [
{selector: "date", text: "Date", type: "timestamp"},
{selector: "name", text: "Name", type: "string"},
{selector: "cost", text: "Cost", type: "number"}
],
}
]);


// grafonnet ref: https://grafana.github.io/grafonnet/API/dashboard/index.html
//
// A dashboard description can be provided, but isn't used much it seems, due to
// that we aren't providing one atm.
// See https://community.grafana.com/t/dashboard-description-is-it-used-anywhere/53273.
//
dashboard.new("Cloud cost attribution")
+ dashboard.withUid("cloud-cost-aws")
+ dashboard.withTimezone("utc")
Expand Down
5 changes: 5 additions & 0 deletions grafana-dashboards/common.libsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ local ts = grafonnet.panel.timeSeries;
type: "yesoreyeram-infinity-datasource",
uid: "${infinity_datasource}",
},
columns: [
{selector: "date", text: "Date", type: "timestamp"},
{selector: "name", text: "Name", type: "string"},
{selector: "cost", text: "Cost", type: "number"}
],
parser: "backend",
type: "json",
source: "url",
Expand Down
14 changes: 7 additions & 7 deletions helm-charts/aws-ce-grafana-backend/mounted-files/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,20 @@ faster.

### Testing Python changes locally

First authenticate yourself against the AWS openscapes account.
First authenticate yourself against an AWS account.

```bash
cd helm-charts/aws-ce-grafana-backend/mounted-files
export AWS_CE_GRAFANA_BACKEND__CLUSTER_NAME=openscapeshub
export AWS_CE_GRAFANA_BACKEND__CLUSTER_NAME=<name of cluster according to eksctl config>
python -m flask --app=webserver run --port=8080

# visit http://localhost:8080/aws
# visit http://localhost:8080/hub-names
```

### Testing Python changes in k8s

This was initially developed in the openscapes cluster. It depends on a k8s
ServiceAccount coupled to an IAM Role there as well.
This requires a k8s ServiceAccount coupled to an IAM Role prepared in advance
via terraform.

The image shouldn't need to be rebuilt unless additional dependencies needs to
be installed etc, so if you've only made code changes, you can do the following
Expand All @@ -35,7 +35,7 @@ During development, a procedure like below can be used to iterate faster than by
using the deployer.

```bash
deployer use-cluster-credentials openscapes
deployer use-cluster-credentials $CLUSTER_NAME

cd helm-charts/aws-ce-grafana-backend
helm upgrade --install --create-namespace -n support --values my-test-config.yaml aws-ce-grafana-backend .
Expand All @@ -45,7 +45,7 @@ helm upgrade --install --create-namespace -n support --values my-test-config.yam
# restarts.
kubectl port-forward -n support service/aws-ce-grafana-backend 8080:http

# visit http://localhost:8080/total-costs and other urls
# visit http://localhost:8080/hub-names and other urls
```

It assumes that you have a `my-test-config.yaml` file looking like this:
Expand Down
60 changes: 47 additions & 13 deletions helm-charts/aws-ce-grafana-backend/mounted-files/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,20 +94,54 @@ def query_hub_names(from_date, to_date):
@ttl_lru_cache(seconds_to_live=3600)
def query_total_costs(from_date, to_date):
"""
A query with processing of the response tailored query to report hub
independent total costs.
A query with processing of the response tailored to report both the total
AWS account cost, and the total attributable cost.
Not all costs will be successfully attributed, such as the cost of accessing
the AWS Cost Explorer API - its not something that can be attributed based
on a tag.
"""
total_account_costs = _query_total_costs(
from_date, to_date, add_attributable_costs_filter=False
)
total_attributable_costs = _query_total_costs(
from_date, to_date, add_attributable_costs_filter=True
)

processed_response = total_account_costs + total_attributable_costs

# the infinity plugin appears needs us to sort by date, otherwise it fails
# to distinguish time series by the name field for some reason
processed_response = sorted(processed_response, key=lambda x: x["date"])

return processed_response


@ttl_lru_cache(seconds_to_live=3600)
def _query_total_costs(from_date, to_date, add_attributable_costs_filter):
"""
A query with processing of the response tailored to report total costs.
It can either be the total account costs, or only the attributable costs.
"""
if add_attributable_costs_filter:
name = "attributable"
filter = {
"And": [
FILTER_USAGE_COSTS,
FILTER_ATTRIBUTABLE_COSTS,
]
}
else:
name = "account"
filter = FILTER_USAGE_COSTS

response = query_aws_cost_explorer(
metrics=[METRICS_UNBLENDED_COST],
granularity=GRANULARITY_DAILY,
from_date=from_date,
to_date=to_date,
filter={
"And": [
FILTER_USAGE_COSTS,
FILTER_ATTRIBUTABLE_COSTS,
]
},
filter=filter,
group_by=[],
)

Expand Down Expand Up @@ -144,6 +178,7 @@ def query_total_costs(from_date, to_date):
{
"date": e["TimePeriod"]["Start"],
"cost": f'{float(e["Total"]["UnblendedCost"]["Amount"]):.2f}',
"name": name,
}
for e in response["ResultsByTime"]
]
Expand All @@ -153,9 +188,8 @@ def query_total_costs(from_date, to_date):
@ttl_lru_cache(seconds_to_live=3600)
def query_total_costs_per_hub(from_date, to_date):
"""
A query with processing of the response tailored query to report total costs
per hub, where costs not attributed to a specific hub is listed under
'shared'.
A query with processing of the response tailored to report total costs per
hub, where costs not attributed to a specific hub is listed under 'shared'.
"""
response = query_aws_cost_explorer(
metrics=[METRICS_UNBLENDED_COST],
Expand Down Expand Up @@ -238,8 +272,8 @@ def query_total_costs_per_hub(from_date, to_date):
@ttl_lru_cache(seconds_to_live=3600)
def query_total_costs_per_component(from_date, to_date, hub_name=None):
"""
A query with processing of the response tailored query to report total costs
per component - a grouping of services.
A query with processing of the response tailored to report total costs per
component - a grouping of services.
If a hub_name is specified, component costs are filtered to only consider
costs directly attributable to the hub name.
Expand Down

0 comments on commit 1d0dbe4

Please sign in to comment.