Skip to content

Commit

Permalink
Merge pull request #4 from electrolux-oss/support-multiple-aggregations
Browse files Browse the repository at this point in the history
support multiple aggregations
  • Loading branch information
gluckzhang authored Jul 10, 2024
2 parents 3a28fad + f46631d commit ea1c543
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 36 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ kubernetes_daily_cost_usd{namespace="kube-system"} 1.1914006049581642
...
```

*ps: As the metric name indicate, the metric shows the daily costs in USD. `Daily` is based a fixed 24h time window, from UTC 00:00 to UTC 24:00. `namespace` is a label based on `--aggregate` option. Users can also add custom labels using the `--label` option.*
*ps: As the metric name indicate, the metric shows the daily costs in USD. `Daily` is based a fixed 24h time window, from UTC 00:00 to UTC 24:00. `namespace` is a label based on `--aggregate` option, which can be used several times for multiple aggregations. Users can also add custom labels using the `--label` option.*


## How Does This Work
Expand Down Expand Up @@ -45,4 +45,4 @@ The Kubernetes Cost Exporter needs to be deployed to the same cluster where the

```
kubectl create --namespace <NAMESPACE> -f ./deployment/k8s/deployment.yaml
```
```
42 changes: 26 additions & 16 deletions app/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ def __init__(self, endpoint, aggregate, interval, name, extra_labels):
self.interval = interval
self.name = name
self.extra_labels = extra_labels
self.labels = set([aggregate])
self.labels = set(aggregate)
if extra_labels is not None:
self.labels.update(extra_labels.keys())
self.kubernetes_daily_cost_usd = Gauge(
self.name, "Kubernetes daily cost in USD aggregated by %s" % self.aggregate, self.labels)
self.name, "Kubernetes daily cost in USD aggregated by %s" % ", ".join(self.aggregate), self.labels
)

def run_metrics_loop(self):
while True:
Expand All @@ -29,18 +30,27 @@ def run_metrics_loop(self):
time.sleep(self.interval)

def fetch(self):
api = (f"/allocation/view?aggregate={self.aggregate}&window=today&shareIdle=true&idle=true&"
"idleByNode=false&shareTenancyCosts=true&shareNamespaces=&shareCost=NaN&"
"shareSplit=weighted&chartType=costovertime&costUnit=cumulative&step=")
api = (
f"/allocation/view?aggregate={','.join(self.aggregate)}&window=today&shareIdle=true&idle=true&"
"idleByNode=false&shareTenancyCosts=true&shareNamespaces=&shareCost=NaN&"
"shareSplit=weighted&chartType=costovertime&costUnit=cumulative&step="
)
response = requests.get(self.endpoint + api)
if (response.status_code != 200 or response.json().get("code") != 200):
logging.error("error while fetching data from %s, status code %s, message %s!" % (
api, response.status_code, response.text))
items = response.json()["data"]["items"]["items"]
for item in items:
if self.extra_labels:
self.kubernetes_daily_cost_usd.labels(
**{self.aggregate: item["name"]}, **self.extra_labels).set(item["totalCost"])
else:
self.kubernetes_daily_cost_usd.labels(
**{self.aggregate: item["name"]}).set(item["totalCost"])
if response.status_code != 200 or response.json().get("code") != 200:
logging.error(
"error while fetching data from %s, status code %s, message %s!"
% (api, response.status_code, response.text)
)
else:
items = response.json()["data"]["items"]["items"]
for item in items:
aggregation_labels = {}
names = item["name"].split("/")
for i in range(len(self.aggregate)):
aggregation_labels[self.aggregate[i]] = names[i]

if self.extra_labels:
self.kubernetes_daily_cost_usd.labels(**aggregation_labels, **self.extra_labels).set(item["totalCost"])
else:
self.kubernetes_daily_cost_usd.labels(**aggregation_labels).set(item["totalCost"])
logging.info(f"cost metric {self.name} updated successfully")
52 changes: 35 additions & 17 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@


class key_value_arg(argparse.Action):
def __call__(self, parser, namespace,
values, option_string=None):
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, dict())

for kvpair in values:
Expand All @@ -22,32 +21,51 @@ def __call__(self, parser, namespace,

def get_args():
parser = argparse.ArgumentParser(
description="Kubernetes Cost Exporter, exposing Kubernetes cost data as Prometheus metrics.")
parser.add_argument("-p", "--port", default=9090, type=int,
help="Exporter's port (default: 9090)")
parser.add_argument("-i", "--interval", default=60, type=int,
help="Update interval in seconds (default: 60)")
parser.add_argument("-n", "--name", default="kubernetes_daily_cost_usd",
help="Name of the exposed metric (default: kubernetes_daily_cost_usd)")
parser.add_argument("-e", "--endpoint", default="http://kubecost-cost-analyzer.monitoring.svc:9003",
help="Kubecost service endpoint (default: http://kubecost-cost-analyzer.monitoring.svc:9003)")
parser.add_argument("-a", "--aggregate", default="namespace",
help="Aggregation level, e.g., namespace, cluster")
parser.add_argument("-l", "--label", nargs="*", action=key_value_arg,
help="Additional labels that need to be added to the metric, \
may be used several times, e.g., --label project=foo environment=dev")
description="Kubernetes Cost Exporter, exposing Kubernetes cost data as Prometheus metrics."
)
parser.add_argument("-p", "--port", default=9090, type=int, help="Exporter's port (default: 9090)")
parser.add_argument("-i", "--interval", default=60, type=int, help="Update interval in seconds (default: 60)")
parser.add_argument(
"-n",
"--name",
default="kubernetes_daily_cost_usd",
help="Name of the exposed metric (default: kubernetes_daily_cost_usd)",
)
parser.add_argument(
"-e",
"--endpoint",
default="http://kubecost-cost-analyzer.monitoring.svc:9003",
help="Kubecost service endpoint (default: http://kubecost-cost-analyzer.monitoring.svc:9003)",
)
parser.add_argument(
"-a",
"--aggregate",
action="append",
help="Aggregation level (default: namespace), e.g., cluster, namespace, deployment, pod, etc. Multiple options are supported, e.g., -a namespace -a deployment",
)
parser.add_argument(
"-l",
"--label",
nargs="*",
action=key_value_arg,
help="Additional labels that need to be added to the metric, \
may be used several times, e.g., --label project=foo environment=dev",
)
args = parser.parse_args()

return args


def main(args):
if args.aggregate is None:
args.aggregate = ["namespace"]

app_metrics = MetricExporter(
endpoint=args.endpoint,
aggregate=args.aggregate,
interval=args.interval,
name=args.name,
extra_labels=args.label
extra_labels=args.label,
)
start_http_server(args.port)
app_metrics.run_metrics_loop()
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"name": "kubernetes-cost-exporter",
"version": "v1.0.3"
"version": "v1.0.4"
}

0 comments on commit ea1c543

Please sign in to comment.