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
1818from openstack_cli .modules .apputils .terminal import TableOutput , TableColumn , TableColumnPosition
19+ from openstack_cli .modules .apputils .terminal .get_terminal_size import get_terminal_size
1920
2021from openstack_cli .modules .apputils .terminal .colors import Colors
2122from openstack_cli .core .config import Configuration
2223from 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 )
0 commit comments