blob: 3fb181554dd15d3aa40bfaad8870ef77ecf1a9e3 [file] [log] [blame]
Matteo Scandolo3ed89872020-07-15 17:01:02 -07001# Copyright 2017-present Open Networking Foundation
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15# This tool collects CPU and Memory informations for each container in the VOLTHA stack
16
17# NOTE
18# Collecting the info for all containers in the same chart can be confusing,
19# we may want to create subcharts for the different groups, eg: infra, ONOS, core, adapters
20
Matteo Scandolo7274b432020-08-27 14:28:43 -070021import csv
22from sys import platform as sys_pf
23
24if sys_pf == 'darwin':
25 import matplotlib
26
27 matplotlib.use("TkAgg")
28
Matteo Scandolo3ed89872020-07-15 17:01:02 -070029import argparse
30import requests
31import matplotlib.pyplot as plt
32import matplotlib.dates as mdates
33from datetime import datetime
34import time
35
36EXCLUDED_POD_NAMES = [
37 "kube", "coredns", "kind", "grafana",
38 "prometheus", "tiller", "control-plane",
Matteo Scandolo7274b432020-08-27 14:28:43 -070039 "calico", "nginx", "registry", "cattle", "canal", "metrics",
Matteo Scandolo3ed89872020-07-15 17:01:02 -070040]
41
42DATE_FORMATTER_FN = mdates.DateFormatter('%Y-%m-%d %H:%M:%S')
43
Matteo Scandolo88d01c12020-11-02 17:11:26 -080044KAFKA_TOPICS = [
45 "openolt",
46 "brcm_openomci_onu",
47 "voltha",
48 "adapters",
49 "rwcore"
50]
Matteo Scandolo3ed89872020-07-15 17:01:02 -070051
Andrey Pozolotin0f437712021-07-30 17:36:41 +030052def main(address, out_folder, since, namespace="default", ratePeriod = "5m", step = 30):
Matteo Scandolo3ed89872020-07-15 17:01:02 -070053 """
54 Query Prometheus and generate .pdf files for CPU and Memory consumption for each POD
55 :param address: string The address of the Prometheus instance to query
56 :param out_folder: string The output folder (where to save the .pdf files)
57 :param since: int When to start collection data (minutes in the past)
58 :return: void
59 """
60 time_delta = int(since) * 60
Matteo Scandolo7274b432020-08-27 14:28:43 -070061
Andrea Campanella010428f2021-08-24 11:58:47 +020062 container_mem_query = 'sum by(pod) (container_memory_working_set_bytes{namespace="%s",container!="",container!="POD"})' % namespace
Matteo Scandolo86334f52020-08-28 10:56:25 -070063
Andrea Campanella010428f2021-08-24 11:58:47 +020064 container_cpu_query = 'sum by(pod) (rate(container_cpu_usage_seconds_total{namespace="%s",container!="",container!="POD"}[%s]))' % (namespace, ratePeriod)
Matteo Scandolo3ed89872020-07-15 17:01:02 -070065
66 now = time.time()
67 cpu_params = {
68 "query": container_cpu_query,
69 "start": now - time_delta,
70 "end": now,
Andrey Pozolotin0f437712021-07-30 17:36:41 +030071 "step": step,
Matteo Scandolo3ed89872020-07-15 17:01:02 -070072 }
Andrey Pozolotin0f437712021-07-30 17:36:41 +030073 print("CPU usage query: %s" % cpu_params)
Matteo Scandolo86334f52020-08-28 10:56:25 -070074
Matteo Scandolo3ed89872020-07-15 17:01:02 -070075 r = requests.get("http://%s/api/v1/query_range" % address, cpu_params)
76 print("Downloading CPU info from: %s" % r.url)
77 container_cpu = r.json()["data"]["result"]
Matteo Scandolo7274b432020-08-27 14:28:43 -070078 containers = remove_unwanted_containers(container_cpu)
79 plot_cpu_consumption(containers,
Matteo Scandolo806637d2020-07-30 02:07:06 +000080 output="%s/cpu.pdf" % out_folder)
Matteo Scandolo7274b432020-08-27 14:28:43 -070081 data_to_csv(containers, output="%s/cpu.csv" % out_folder,
Matteo Scandolo86334f52020-08-28 10:56:25 -070082 convert_values=lambda values: ["{:.2f}".format(v) for v in values])
Matteo Scandolo3ed89872020-07-15 17:01:02 -070083
Matteo Scandolo7274b432020-08-27 14:28:43 -070084 mem_params = {
85 "query": container_mem_query,
86 "start": now - time_delta,
87 "end": now,
Andrey Pozolotin0f437712021-07-30 17:36:41 +030088 "step": step,
Matteo Scandolo7274b432020-08-27 14:28:43 -070089 }
Andrey Pozolotin0f437712021-07-30 17:36:41 +030090 print("Memory query: %s" % mem_params)
Matteo Scandolo7274b432020-08-27 14:28:43 -070091
92 r = requests.get("http://%s/api/v1/query_range" % address, mem_params)
Matteo Scandolo3ed89872020-07-15 17:01:02 -070093 print("Downloading Memory info from: %s" % r.url)
94 container_mem = r.json()["data"]["result"]
Matteo Scandolo7274b432020-08-27 14:28:43 -070095 containers = remove_unwanted_containers(container_mem)
96 plot_memory_consumption(containers, output="%s/memory.pdf" % out_folder)
97 data_to_csv(containers, output="%s/memory.csv" % out_folder,
Matteo Scandolo86334f52020-08-28 10:56:25 -070098 convert_values=lambda values: ["{:.2f}".format(bytesto(v, "m")) for v in values])
Matteo Scandolo7274b432020-08-27 14:28:43 -070099
Matteo Scandolo88d01c12020-11-02 17:11:26 -0800100 print("Downloading KAFKA stats")
101 get_kafka_stats(address, out_folder)
102 print("Downloading ETCD stats")
103 get_etcd_stats(address, out_folder)
104
105
Matteo Scandolo7274b432020-08-27 14:28:43 -0700106
107def data_to_csv(containers, output=None, convert_values=None):
108 """
109 Get a list of prometheus metrics and dumps them in a csv
110 :param containers: Prometheus metrics
111 :param output: Destination file
112 :param convert_values: Function to convert the valus, take a list on numbers
113 """
114 csv_file = open(output, "w+")
115 csv_writer = csv.writer(csv_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
116
117 # we assume all the containers have the same timestamps
Matteo Scandolo7e3dd122020-11-04 15:24:00 -0800118 # FIXME pods may have different timestamps depending on when the collection started
119 # - find the longest list in containers
120 # - add empty values at the beginning of the other list
Andrey Pozolotine78670c2021-07-30 13:33:27 +0300121 if not containers:
122 return
Andrey Pozolotin0f437712021-07-30 17:36:41 +0300123
124 container_index_longest_row = 0
125 longest_row = 0
126 for i, c in enumerate(containers):
127 cur_row_len = len(c["values"])
128 if cur_row_len > longest_row:
129 longest_row = cur_row_len
130 container_index_longest_row = i
131
132 dates = [datetime.fromtimestamp(x[0]) for x in containers[container_index_longest_row]["values"]]
Matteo Scandolo7274b432020-08-27 14:28:43 -0700133 csv_writer.writerow([''] + dates)
134
135 for c in containers:
136 name = c["metric"]["pod"]
137 data = c["values"]
138
139 values = [float(x[1]) for x in data]
140
141 if convert_values:
142 values = convert_values(values)
143 csv_writer.writerow([name] + values)
Matteo Scandolo3ed89872020-07-15 17:01:02 -0700144
145
146def plot_cpu_consumption(containers, output=None):
Matteo Scandolo3ed89872020-07-15 17:01:02 -0700147 plt.figure('cpu')
148 fig, ax = plt.subplots()
149 ax.xaxis.set_major_formatter(DATE_FORMATTER_FN)
150 ax.xaxis_date()
151 fig.autofmt_xdate()
152
153 plt.title("CPU Usage per POD")
154 plt.xlabel("Timestamp")
Andrey Pozolotinced58a02021-07-13 18:49:05 +0300155 plt.ylabel("CPU cores used")
Matteo Scandolo3ed89872020-07-15 17:01:02 -0700156
Andrey Pozolotin0f437712021-07-30 17:36:41 +0300157 for i, c in enumerate(containers):
Matteo Scandolo7274b432020-08-27 14:28:43 -0700158 name = c["metric"]["pod"]
Matteo Scandolo3ed89872020-07-15 17:01:02 -0700159 data = c["values"]
160
161 dates = [datetime.fromtimestamp(x[0]) for x in data]
162
163 values = [float(x[1]) for x in data]
164
Andrey Pozolotin0f437712021-07-30 17:36:41 +0300165 plt.plot(dates, values, label=name, lw=2, color=get_line_color(name, i))
Matteo Scandolo3ed89872020-07-15 17:01:02 -0700166 # plt.plot(dates[1:], get_diff(values), label=name, lw=2, color=get_line_color(name))
167
Matteo Scandolo7274b432020-08-27 14:28:43 -0700168 plt.legend(loc='upper left', title="CPU Consumption", bbox_to_anchor=(1.05, 1))
Matteo Scandolo3ed89872020-07-15 17:01:02 -0700169
170 fig = plt.gcf()
171 fig.set_size_inches(20, 11)
172
Matteo Scandolo7274b432020-08-27 14:28:43 -0700173 plt.savefig(output, bbox_inches="tight")
Matteo Scandolo3ed89872020-07-15 17:01:02 -0700174
175
176def plot_memory_consumption(containers, output=None):
177 plt.figure("memory")
178 fig, ax = plt.subplots()
179 ax.xaxis.set_major_formatter(DATE_FORMATTER_FN)
180 ax.xaxis_date()
181 fig.autofmt_xdate()
182 plt.title("Memory Usage")
183 plt.xlabel("Timestamp")
184 plt.ylabel("MB")
185
Andrey Pozolotin0f437712021-07-30 17:36:41 +0300186 for i, c in enumerate(containers):
Matteo Scandolo7274b432020-08-27 14:28:43 -0700187 name = c["metric"]["pod"]
Matteo Scandolo3ed89872020-07-15 17:01:02 -0700188 data = c["values"]
189
190 dates = [datetime.fromtimestamp(x[0]) for x in data]
191 values = [bytesto(float(x[1]), "m") for x in data]
192
Matteo Scandolo7274b432020-08-27 14:28:43 -0700193 # plt.plot(dates[1:], get_diff(values), label=name, lw=2, color=get_line_color(name))
Andrey Pozolotin0f437712021-07-30 17:36:41 +0300194 plt.plot(dates[1:], values[1:], label=name, lw=2, color=get_line_color(name, i))
Matteo Scandolo3ed89872020-07-15 17:01:02 -0700195
Matteo Scandolo7274b432020-08-27 14:28:43 -0700196 plt.legend(loc='upper left', title="Memory Usage", bbox_to_anchor=(1.05, 1))
Matteo Scandolo3ed89872020-07-15 17:01:02 -0700197
198 fig = plt.gcf()
199 fig.set_size_inches(20, 11)
200
Matteo Scandolo7274b432020-08-27 14:28:43 -0700201 plt.savefig(output, bbox_inches="tight")
Matteo Scandolo3ed89872020-07-15 17:01:02 -0700202
203
204def remove_unwanted_containers(cpus):
205 res = []
206 for c in cpus:
Matteo Scandolo3ed89872020-07-15 17:01:02 -0700207
Matteo Scandolo7274b432020-08-27 14:28:43 -0700208 if "pod" in c["metric"]:
209 pod_name = c["metric"]["pod"]
Matteo Scandolo3ed89872020-07-15 17:01:02 -0700210 if any(x in pod_name for x in EXCLUDED_POD_NAMES):
211 continue
Matteo Scandolo3ed89872020-07-15 17:01:02 -0700212 res.append(c)
Matteo Scandolo806637d2020-07-30 02:07:06 +0000213
Matteo Scandolo3ed89872020-07-15 17:01:02 -0700214 return res
215
216
Andrey Pozolotin0f437712021-07-30 17:36:41 +0300217def get_line_color(container_name, i):
Matteo Scandolo3ed89872020-07-15 17:01:02 -0700218 colors = {
219 "bbsim0": "#884EA0",
220 "bbsim1": "#9B59B6",
221 "bbsim-sadis-server": "#D2B4DE",
222 "onos-atomix-0": "#85C1E9",
223 "onos-atomix-1": "#7FB3D5",
224 "onos-atomix-2": "#3498DB",
225 "onos-onos-classic-0": "#1A5276",
226 "onos-onos-classic-1": "#1B4F72",
227 "onos-onos-classic-2": "#154360",
228 "etcd-0": "#7D6608",
229 "etcd-1": "#9A7D0A",
230 "etcd-2": "#B7950B",
231 "open-olt-voltha-adapter-openolt": "#7E5109",
232 "open-onu-voltha-adapter-openonu-0": "#6E2C00",
233 "open-onu-voltha-adapter-openonu-1": "#873600",
234 "open-onu-voltha-adapter-openonu-2": "#A04000",
235 "open-onu-voltha-adapter-openonu-3": "#BA4A00",
236 "open-onu-voltha-adapter-openonu-4": "#D35400",
237 "open-onu-voltha-adapter-openonu-5": "#D35400",
238 "open-onu-voltha-adapter-openonu-6": "#E59866",
239 "open-onu-voltha-adapter-openonu-7": "#EDBB99",
240 "kafka-0": "#4D5656",
241 "kafka-1": "#5F6A6A",
242 "kafka-2": "#717D7E",
243 "kafka-zookeeper-0": "#839192",
244 "kafka-zookeeper-1": "#95A5A6",
245 "kafka-zookeeper-2": "#717D7E",
246 "radius": "#82E0AA",
247 "voltha-voltha-ofagent": "#641E16",
248 "voltha-voltha-rw-core": "#7B241C",
249 }
250
Andrey Pozolotin0f437712021-07-30 17:36:41 +0300251 colorsToPickup = [
252 "#f44336",
253 "#4bde31",
254 "#31dea7",
255 "#31a5de",
256 "#313dde",
257 "#ffac2c",
258 "#f16443",
259 "#8cff00",
260 "#990000",
261 "#b8ce85",
262 "#5662f6",
263 "#e42491",
264 "#5b4f5b",
265 "#df1019",
266 "#b9faf8",
267 "#1d903f",
268 "#56c7f2",
269 "#40dfa0",
270 "#5662f6",
271 "#400080",
272 "#b73e34",
273 ]
274
Matteo Scandolo3ed89872020-07-15 17:01:02 -0700275 if container_name in colors:
276 return colors[container_name]
277 elif "openolt" in container_name:
278 return colors["open-olt-voltha-adapter-openolt"]
279 elif "ofagent" in container_name:
280 return colors["voltha-voltha-ofagent"]
281 elif "rw-core" in container_name:
282 return colors["voltha-voltha-rw-core"]
283 elif "bbsim0" in container_name:
284 return colors["bbsim0"]
285 elif "bbsim1" in container_name:
286 return colors["bbsim1"]
287 elif "bbsim-sadis-server" in container_name:
288 return colors["bbsim-sadis-server"]
289 elif "radius" in container_name:
290 return colors["radius"]
291 else:
Andrey Pozolotin0f437712021-07-30 17:36:41 +0300292 colorIdx = i % len(colorsToPickup)
293 pickupColor = colorsToPickup[colorIdx]
294 return pickupColor
Matteo Scandolo3ed89872020-07-15 17:01:02 -0700295
296
297def get_diff(data):
Matteo Scandolo7274b432020-08-27 14:28:43 -0700298 # get the delta between the current data and the previous point
Matteo Scandolo3ed89872020-07-15 17:01:02 -0700299 return [x - data[i - 1] for i, x in enumerate(data)][1:]
300
301
302def bytesto(b, to, bsize=1024):
303 """convert bytes to megabytes, etc.
304 sample code:
305 print('mb= ' + str(bytesto(314575262000000, 'm')))
306 sample output:
307 mb= 300002347.946
308 """
309
310 a = {'k': 1, 'm': 2, 'g': 3, 't': 4, 'p': 5, 'e': 6}
311 r = float(b)
312 for i in range(a[to]):
313 r = r / bsize
314
315 return r
316
317
Matteo Scandolo88d01c12020-11-02 17:11:26 -0800318
319def get_etcd_stats(address, out_folder):
320 """
321 :param address: The prometheus address
322 :param out_folder: The folder in which store the output files
323 """
324
325 etcd_stats = {
326 "size":"etcd_debugging_mvcc_db_total_size_in_bytes",
327 "keys":"etcd_debugging_mvcc_keys_total"
328 }
329
330 etcd = {}
331
332 time_delta = 80
333 for stat,query in etcd_stats.items():
334 now = time.time()
335 etcd_params = {
336 "query": "%s{}" % query,
337 "start": now - time_delta,
338 "end": now,
339 "step": "30",
340 }
341 r = requests.get("http://%s/api/v1/query_range" % address, etcd_params)
Andrey Pozolotine78670c2021-07-30 13:33:27 +0300342 etcdStats = r.json()["data"]["result"]
343 if etcdStats:
344 i = etcdStats[0]
345 etcd[stat] = i["values"][-1][1]
Matteo Scandolo88d01c12020-11-02 17:11:26 -0800346
347 csv_file = open("%s/etcd_stats.csv" % out_folder, "w+")
348 csv_writer = csv.writer(csv_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
349
350 for k,v in etcd.items():
351 csv_writer.writerow([k, v])
352
353def get_kafka_stats(address, out_folder):
354 """
355 :param address: The prometheus address
356 :param out_folder: The folder in which store the output files
357 """
358 # get the last information for all topics, we only care about the last value so a short interval is fine
359 now = time.time()
360 time_delta = 80
361 kafka_params = {
362 "query": "kafka_topic_partition_current_offset{}",
363 "start": now - time_delta,
364 "end": now,
365 "step": "30",
366 }
367
368 r = requests.get("http://%s/api/v1/query_range" % address, kafka_params)
369
370 msg_per_topic = {}
371
372 for t in r.json()["data"]["result"]:
373 # we only care about some topics
374 topic_name = t["metric"]["topic"]
375
376 if any(x in topic_name for x in KAFKA_TOPICS):
377 # get only the value at the last timestamp
378 msg_per_topic[t["metric"]["topic"]] = t["values"][-1][1]
379
380 csv_file = open("%s/kafka_msg_per_topic.csv" % out_folder, "w+")
381 csv_writer = csv.writer(csv_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
382
383 for k,v in msg_per_topic.items():
384 csv_writer.writerow([k, v])
385
Matteo Scandolo3ed89872020-07-15 17:01:02 -0700386if __name__ == "__main__":
387 parser = argparse.ArgumentParser(prog="sizing")
388 parser.add_argument("-a", "--address", help="The address of the Prometheus instance we're targeting",
389 default="127.0.0.1:31301")
390 parser.add_argument("-o", "--output", help="Where to output the generated files",
391 default="plots")
392 parser.add_argument("-s", "--since", help="When to start sampling the data (in minutes before now)",
393 default=10)
Andrey Pozolotine78670c2021-07-30 13:33:27 +0300394 parser.add_argument("-n", "--namespace", help="Kubernetes namespace for collecting metrics",
395 default="default")
Andrey Pozolotin0f437712021-07-30 17:36:41 +0300396 parser.add_argument("-r", "--rate", help="Rate period",
397 default="5m")
398 parser.add_argument("-t", "--step", help="Step in seconds",
399 default=30)
Matteo Scandolo3ed89872020-07-15 17:01:02 -0700400
401 args = parser.parse_args()
Andrey Pozolotin0f437712021-07-30 17:36:41 +0300402 main(args.address, args.output, args.since, args.namespace, args.rate, args.step)