Skip to content

Commit 9120450

Browse files
author
Simca
committed
Update quota command
Magnifiec graph option, which alows to see top consumers with ease
1 parent efca8c2 commit 9120450

File tree

3 files changed

+199
-33
lines changed

3 files changed

+199
-33
lines changed

src/openstack_cli/commands/quota.py

Lines changed: 193 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,20 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515

16-
from typing import Dict
16+
from typing import Dict, List
1717

1818
from openstack_cli.modules.apputils.terminal import TableOutput, TableColumn, TableColumnPosition
19+
from openstack_cli.modules.apputils.terminal.get_terminal_size import get_terminal_size
1920

2021
from openstack_cli.modules.apputils.terminal.colors import Colors
2122
from openstack_cli.core.config import Configuration
2223
from openstack_cli.modules.apputils.discovery import CommandMetaInfo
23-
from openstack_cli.modules.openstack import OpenStack, OpenStackQuotas, OpenStackQuotaType, OpenStackVMInfo
24+
from openstack_cli.modules.openstack import OpenStack, OpenStackQuotas, OpenStackQuotaType, OpenStackUsers
2425

2526
__module__ = CommandMetaInfo("quota", item_help="Show the allowed resource limits for the project")
2627
__args__ = __module__.arg_builder\
2728
.add_argument("details", bool, "Show detailed resource consumption", default=False)\
29+
.add_argument("graph", bool, "Show Graphical statistic per-user", default=False)\
2830
.add_argument("show_clusters", bool, "Show user instances on details page", alias="show-clusters", default=False)
2931

3032

@@ -35,44 +37,202 @@ def get_percents(current: float, fmax: float):
3537
return (current * 100) / fmax
3638

3739

38-
def get_progressbar(percents, color=""):
39-
width = 30
40+
def get_plain_progress(percents, color:str = "", width: int = 30):
4041
f = int(round((percents * width) / 100))
4142
nf = int(width - f)
4243
if color:
43-
return f"[{color}{'#' * f}{' '* nf}{Colors.RESET}]"
44+
return f"{color}{'#' * f}{' '* nf}{Colors.RESET}"
4445
else:
45-
return f"[{'#' * f}{' '* nf}]"
46+
return f"{'#' * f}{' '* nf}"
47+
48+
def get_progressbar(percents, color:str = "", width: int = 30):
49+
return f"[{get_plain_progress(percents, color, width)}]"
50+
51+
52+
def display_graph(_t: OpenStackQuotaType, metrics: Dict[str, Dict[OpenStackQuotaType, int]], users: OpenStackUsers,
53+
quotas: OpenStackQuotas):
54+
"""
55+
Interface example:
56+
57+
QuotaType Graph
58+
----------------
59+
60+
nnnnnn |
61+
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | ################################## XXX XX / 10.94%
62+
|
63+
mmmmmmmmmmm |
64+
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | ################################################################### XXX XX / 25.00%
65+
|
66+
xxxxxxxx |
67+
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | ## X XX / 1.00%
68+
| | | |
69+
6 3 7
70+
"""
71+
72+
screen_max_width, _ = get_terminal_size()
73+
column12_del: str = " | "
74+
column23_del: str = " "
75+
column1_max_width: int = max(
76+
len(next(iter(metrics.keys()))),
77+
max([len(user) for id in metrics.keys() if (user:=users.get_user(id))])
78+
)
79+
column3_max_width: int = 16
80+
column2_max_width: int = screen_max_width - column1_max_width - column3_max_width - len(column12_del) - len(column23_del)
81+
82+
max_metric_value: int = max([m for v in metrics.values() if (m:=v[_t])])
83+
84+
row_frmt_str: str = f"{{:<{column1_max_width}}}{column12_del}{{:<{column2_max_width}}}{column23_del}{{:>{column3_max_width}}}"
85+
86+
print(f"{_t.name} Graph")
87+
print(f"{'-'*len(_t.name)}---------\n")
88+
89+
_color = Colors.WHITE
90+
for user_id, user_metric in metrics.items():
91+
user_name = users.get_user(user_id)
92+
percents = get_percents(user_metric[_t], max_metric_value)
93+
actual_percents = get_percents(user_metric[_t], quotas.get_quota(_t)[0])
94+
progress_bar = get_plain_progress(percents, "", column2_max_width)
95+
96+
if _t ==OpenStackQuotaType.RAM_MB:
97+
user_metric_str = f"{int(user_metric[_t]/1024):>4} GB"
98+
else:
99+
user_metric_str = user_metric[_t]
100+
101+
metric_title = f"{user_metric_str:<6}/{actual_percents:6.2f}%"
102+
103+
_col1_filler = " " * (column1_max_width - len(user_name))
104+
print(row_frmt_str.format(f"{_color}{user_name}{_col1_filler}{Colors.RESET}", "", ""))
105+
print(row_frmt_str.format(f"{_color}{user_id}{Colors.RESET}", progress_bar, metric_title))
106+
107+
_color = Colors.WHITE if _color == Colors.BRIGHT_WHITE else Colors.BRIGHT_WHITE
108+
109+
print("\n")
110+
111+
112+
def display_combined_graph(metrics: Dict[str, Dict[OpenStackQuotaType, int]], users: OpenStackUsers,
113+
quotas: OpenStackQuotas):
114+
"""
115+
Interface example:
116+
117+
QuotaType Graph
118+
----------------
119+
120+
nnnnnn | ############################################# XXX XX / 10.94%
121+
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | ################################## XXX XX / 10.94%
122+
| ###################################### XXX XX / 10.94%
123+
|
124+
mmmmmmmmmmm | ########## XXX XX / 25.00%
125+
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | ################################################################### XXX XX / 25.00%
126+
| ######################### XXX XX / 25.00%
127+
|
128+
xxxxxxxx | ##### XXX XX / 25.00%
129+
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | ## X XX / 1.00%
130+
| # | | | |
131+
6 3 7
132+
"""
133+
_combined_quotas: List[OpenStackQuotaType] = [
134+
OpenStackQuotaType.INSTANCES,
135+
OpenStackQuotaType.CPU_CORES,
136+
OpenStackQuotaType.RAM_MB
137+
]
138+
_colors: Colors = [
139+
Colors.WHITE,
140+
Colors.YELLOW,
141+
Colors.CYAN
142+
]
143+
_quota_colors = {
144+
_combined_quotas[i]: _colors[i].value for i in range(0, len(_combined_quotas))
145+
}
146+
147+
screen_max_width, _ = get_terminal_size()
148+
column12_del: str = " | "
149+
column23_del: str = " "
150+
column1_max_width: int = max(
151+
len(next(iter(metrics.keys()))),
152+
max([len(user) for id in metrics.keys() if (user:=users.get_user(id))])
153+
)
154+
column3_max_width: int = 16
155+
column2_max_width: int = screen_max_width - column1_max_width - column3_max_width - len(column12_del) - len(column23_del)
46156

47-
def _show_details(conf: Configuration, ostack: OpenStack, quotas: OpenStackQuotas, show_clusters: bool):
48-
project_cpu_max, project_cpu_current = quotas.get_quota(OpenStackQuotaType.CPU_CORES)
49-
project_mem_max, project_mem_current = quotas.get_quota(OpenStackQuotaType.RAM_MB)
50-
project_instances_max, project_instances_current = quotas.get_quota(OpenStackQuotaType.INSTANCES)
157+
max_metric_value: Dict[OpenStackQuotaType, int] = {
158+
q: max([m for v in metrics.values() if (m:=v[q])]) for q in _combined_quotas
159+
}
51160

52-
servers = ostack.servers
161+
row_frmt_str: str = f"{{:<{column1_max_width}}}{column12_del}{{:<{column2_max_width}}}{column23_del}{{:>{column3_max_width}}}"
162+
163+
164+
caption_title = ", ".join([f"{_quota_colors[quota]}{quota.name}{Colors.RESET}" for quota in _combined_quotas])
165+
print(f"{caption_title} Graph")
166+
print(f"{'-'*len(caption_title)}---------\n")
167+
168+
for user_id, user_metric in metrics.items():
169+
columns = [users.get_user(user_id), user_id, ""]
170+
_combined = {_combined_quotas[i]: columns[i] for i in range(0, len(_combined_quotas))}
171+
progress_bar: Dict[OpenStackQuotaType, str] = {}
172+
metric_title: Dict[OpenStackQuotaType, str] = {}
173+
174+
for _t in _combined_quotas:
175+
percents = get_percents(user_metric[_t], max_metric_value[_t])
176+
actual_percents = get_percents(user_metric[_t], quotas.get_quota(_t)[0])
177+
user_metric_str = f"{int(user_metric[_t]/1024):>4} GB" if _t == OpenStackQuotaType.RAM_MB else user_metric[_t]
178+
179+
progress_bar[_t] = get_plain_progress(percents, "", column2_max_width)
180+
metric_title[_t] = f"{user_metric_str:<6}/{actual_percents:6.2f}%"
181+
182+
for _t in _combined_quotas:
183+
print(row_frmt_str.format(
184+
_combined[_t],
185+
f"{_quota_colors[_t]}{progress_bar[_t]}{Colors.RESET}",
186+
metric_title[_t]
187+
))
53188

54-
resources = {}
189+
print(row_frmt_str.format("", "", ""))
55190

56-
for server in ostack.servers.items:
57-
if server.owner_id not in resources:
58-
resources[server.owner_id] = {
59-
"clusters": {},
60-
OpenStackQuotaType.CPU_CORES: 0,
61-
OpenStackQuotaType.RAM_MB: 0,
62-
OpenStackQuotaType.INSTANCES: 0
63-
}
64191

65-
record = resources[server.owner_id]
192+
print("\n")
66193

67-
record[OpenStackQuotaType.CPU_CORES] += server.flavor.vcpus
68-
record[OpenStackQuotaType.INSTANCES] += 1
69-
record[OpenStackQuotaType.RAM_MB] += server.flavor.raw.ram
70-
if server.cluster_name not in record["clusters"]:
71-
record["clusters"][server.cluster_name] = 0
194+
def _get_per_user_stats(ostack: OpenStack) -> Dict[str, Dict[OpenStackQuotaType, int]]:
195+
resources = {}
72196

73-
record["clusters"][server.cluster_name] += 1
197+
for server in ostack.servers.items:
198+
if server.owner_id not in resources:
199+
resources[server.owner_id] = {
200+
"clusters": {},
201+
OpenStackQuotaType.CPU_CORES: 0,
202+
OpenStackQuotaType.RAM_MB: 0,
203+
OpenStackQuotaType.INSTANCES: 0
204+
}
74205

75-
resources[server.owner_id] = record
206+
record = resources[server.owner_id]
207+
208+
record[OpenStackQuotaType.CPU_CORES] += server.flavor.vcpus
209+
record[OpenStackQuotaType.INSTANCES] += 1
210+
record[OpenStackQuotaType.RAM_MB] += server.flavor.raw.ram
211+
if server.cluster_name not in record["clusters"]:
212+
record["clusters"][server.cluster_name] = 0
213+
214+
record["clusters"][server.cluster_name] += 1
215+
resources[server.owner_id] = record
216+
217+
return resources
218+
219+
def _show_graph(ostack: OpenStack, quotas: OpenStackQuotas):
220+
users = ostack.users
221+
resources = _get_per_user_stats(ostack)
222+
223+
display_combined_graph(resources, users, quotas)
224+
225+
# display_graph(OpenStackQuotaType.INSTANCES, resources, users, quotas)
226+
# display_graph(OpenStackQuotaType.CPU_CORES, resources, users, quotas)
227+
# display_graph(OpenStackQuotaType.RAM_MB, resources, users, quotas)
228+
229+
def _show_details(conf: Configuration, ostack: OpenStack, quotas: OpenStackQuotas, show_clusters: bool):
230+
project_cpu_max, project_cpu_current = quotas.get_quota(OpenStackQuotaType.CPU_CORES)
231+
project_mem_max, project_mem_current = quotas.get_quota(OpenStackQuotaType.RAM_MB)
232+
project_instances_max, project_instances_current = quotas.get_quota(OpenStackQuotaType.INSTANCES)
233+
users = ostack.users
234+
servers = ostack.servers
235+
resources = _get_per_user_stats(ostack)
76236

77237
padding = " " * 5
78238
print("Per-User statistic: ")
@@ -125,7 +285,7 @@ def _show_details(conf: Configuration, ostack: OpenStack, quotas: OpenStackQuota
125285
print()
126286

127287

128-
def __init__(conf: Configuration, details: bool, show_clusters: bool):
288+
def __init__(conf: Configuration, details: bool, show_clusters: bool, graph: bool):
129289
stack = OpenStack(conf)
130290
limits = stack.quotas
131291

@@ -156,7 +316,10 @@ def __init__(conf: Configuration, details: bool, show_clusters: bool):
156316

157317
print()
158318
print("* Used/Avl. - raw metric")
319+
print()
159320

160-
if details:
321+
if graph:
322+
_show_graph(stack, limits)
323+
elif details:
161324
print("Calculating per-user statistic....")
162325
_show_details(conf, stack, limits, show_clusters)

src/openstack_cli/commands/up.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def __filter(vm: OpenStackVMInfo) -> bool:
9292
_pass = conf.default_vm_password if not password else password
9393

9494
print(f" |Image flavor to use: {img_flavor.name}")
95-
print(f" |Image to use : {image.alias}")
95+
print(f" |Image to use : {image.alias}")
9696
print(f" |Key to use : {_key.name}")
9797
print(f" |Hosts to add : {', '.join(name)}")
9898
else:

src/openstack_cli/modules/openstack/objects.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,11 +316,11 @@ def available(self):
316316
class OpenStackQuotas(object):
317317
def __init__(self):
318318
# Max, Used, Available
319-
self.__metrics: Dict[OpenStackQuotaType, Tuple[int or float, int or float]] = {}
319+
self.__metrics: Dict[OpenStackQuotaType, Tuple[Union[int,float], Union[int,float]]] = {}
320320
self.__max_metric_length = 0
321321

322322
def add(self, metric_name: OpenStackQuotaType, max_count: int or float, used: int or float):
323-
self.__metrics[metric_name] = (max_count, used)
323+
self.__metrics[metric_name] = max_count, used,
324324
if self.__max_metric_length < len(metric_name.value):
325325
self.__max_metric_length = len(metric_name.value)
326326

@@ -390,6 +390,9 @@ def get_user(self, user_id: str) -> Optional[str]:
390390

391391
return self.__users_db[user_id]
392392

393+
def exists(self, user_id: str) -> bool:
394+
return user_id in self.__users_db
395+
393396
class OSImageInfo(object):
394397
def __init__(self, name: str, ver: str, orig: DiskImageInfo):
395398
self.__name = name.strip()

0 commit comments

Comments
 (0)