21121: Support both Bearer and Basic authentication
[arvados.git] / tools / cluster-activity / arvados_cluster_activity / main.py
1 #!/usr/bin/env python3
2 # Copyright (C) The Arvados Authors. All rights reserved.
3 #
4 # SPDX-License-Identifier: AGPL-3.0
5
6 import argparse
7 import sys
8
9 import arvados
10 import arvados.util
11 import datetime
12 import ciso8601
13 import csv
14 import os
15 from prometheus_api_client.utils import parse_datetime
16 from datetime import timedelta
17 import pandas
18 import base64
19
20 from prometheus_api_client import PrometheusConnect, MetricsList, Metric
21
22 def parse_arguments(arguments):
23     arg_parser = argparse.ArgumentParser()
24     arg_parser.add_argument('--start', help='Start date for the report in YYYY-MM-DD format (UTC)')
25     arg_parser.add_argument('--end', help='End date for the report in YYYY-MM-DD format (UTC), default "now"')
26     arg_parser.add_argument('--days', type=int, help='Number of days before "end" to start the report')
27     arg_parser.add_argument('--cluster', type=str, help='Cluster to query')
28     args = arg_parser.parse_args(arguments)
29
30     if args.days and args.start:
31         arg_parser.print_help()
32         print("Error: either specify --days or both --start and --end")
33         exit(1)
34
35     if not args.days and not args.start:
36         arg_parser.print_help()
37         print("\nError: either specify --days or both --start and --end")
38         exit(1)
39
40     if (args.start and not args.end):
41         arg_parser.print_help()
42         print("\nError: no start or end date found, either specify --days or both --start and --end")
43         exit(1)
44
45     if args.end:
46         try:
47             to = datetime.datetime.strptime(args.end,"%Y-%m-%d")
48         except:
49             arg_parser.print_help()
50             print("\nError: end date must be in YYYY-MM-DD format")
51             exit(1)
52     else:
53         to = datetime.datetime.utcnow()
54
55     if args.days:
56         since = to - datetime.timedelta(days=args.days)
57
58     if args.start:
59         try:
60             since = datetime.datetime.strptime(args.start,"%Y-%m-%d")
61         except:
62             arg_parser.print_help()
63             print("\nError: start date must be in YYYY-MM-DD format")
64             exit(1)
65
66
67     return args, since, to
68
69 def data_usage(prom, timestamp, cluster, label):
70     metric_data = prom.get_current_metric_value(metric_name='arvados_keep_total_bytes',
71                                                 label_config={"cluster": cluster},
72                                                 params={"time": timestamp.timestamp()})
73
74     metric_object_list = MetricsList(metric_data)
75
76     if len(metric_data) == 0:
77         return
78
79     my_metric_object = metric_object_list[0] # one of the metrics from the list
80     value = my_metric_object.metric_values.iloc[0]["y"]
81     summary_value = value
82
83     metric_data = prom.get_current_metric_value(metric_name='arvados_keep_dedup_byte_ratio',
84                                                 label_config={"cluster": cluster},
85                                                 params={"time": timestamp.timestamp()})
86
87     if len(metric_data) == 0:
88         return
89
90     my_metric_object = MetricsList(metric_data)[0]
91     dedup_ratio = my_metric_object.metric_values.iloc[0]["y"]
92
93     value_gb = value / (1024*1024*1024)
94     first_50tb = min(1024*50, value_gb)
95     next_450tb = max(min(1024*450, value_gb-1024*50), 0)
96     over_500tb = max(value_gb-1024*500, 0)
97
98     monthly_cost = (first_50tb * 0.023) + (next_450tb * 0.022) + (over_500tb * 0.021)
99
100     for scale in ["KiB", "MiB", "GiB", "TiB", "PiB"]:
101         summary_value = summary_value / 1024
102         if summary_value < 1024:
103             print(label,
104                   "%.3f %s apparent," % (summary_value*dedup_ratio, scale),
105                   "%.3f %s actually stored," % (summary_value, scale),
106                   "$%.2f monthly S3 storage cost" % monthly_cost)
107             break
108
109
110
111
112 def container_usage(prom, start_time, end_time, metric, label, fn=None):
113     start = start_time
114     chunk_size = timedelta(days=1)
115     cumulative = 0
116
117     while start < end_time:
118         if start + chunk_size > end_time:
119             chunk_size = end_time - start
120
121         metric_data = prom.custom_query_range(metric,
122                                               start_time=start,
123                                               end_time=(start + chunk_size),
124                                               step=15
125                                               )
126
127         if len(metric_data) == 0:
128             break
129
130         if "__name__" not in metric_data[0]["metric"]:
131             metric_data[0]["metric"]["__name__"] = metric
132
133         metric_object_list = MetricsList(metric_data)
134         my_metric_object = metric_object_list[0] # one of the metrics from the list
135
136         series = my_metric_object.metric_values.set_index(pandas.DatetimeIndex(my_metric_object.metric_values['ds']))
137
138         # Resample to 1 minute increments, fill in missing values
139         rs = series.resample("min").mean(1).ffill()
140
141         # Calculate the sum of values
142         #print(rs.sum()["y"])
143         cumulative += rs.sum()["y"]
144
145         start += chunk_size
146
147     if fn is not None:
148         cumulative = fn(cumulative)
149
150     print(label % cumulative)
151
152
153 def main(arguments=None):
154     if arguments is None:
155         arguments = sys.argv[1:]
156
157     args, since, to = parse_arguments(arguments)
158
159     #arv = arvados.api()
160
161     prom_host = os.environ["PROMETHEUS_HOST"]
162     prom_token = os.environ.get("PROMETHEUS_APIKEY")
163     prom_user = os.environ.get("PROMETHEUS_USER")
164     prom_pw = os.environ.get("PROMETHEUS_PASSWORD")
165
166     headers = {}
167     if prom_token:
168         headers["Authorization"] = "Bearer "+prom_token
169
170     if prom_user:
171         headers["Authorization"] = "Basic "+(base64.b64encode("%s:%s" % (prom_user, prom_pw)))
172
173     prom = PrometheusConnect(url=prom_host, headers=header)
174
175     cluster = args.cluster
176
177     print(cluster, "between", since, "and", to, "timespan", (to-since))
178
179     data_usage(prom, since, cluster, "at start:")
180     data_usage(prom, to - timedelta(minutes=240), cluster, "current :")
181
182     container_usage(prom, since, to, "arvados_dispatchcloud_containers_running{cluster='%s'}" % cluster, '%.1f container hours', lambda x: x/60)
183     container_usage(prom, since, to, "sum(arvados_dispatchcloud_instances_price{cluster='%s'})" % cluster, '$%.2f spent on compute', lambda x: x/60)
184     print()
185
186 if __name__ == "__main__":
187     main()