blob: 464290d3f6ca00b55d7d816c87b6214578e78a80 [file] [log] [blame]
Nicolas Edela991cf52018-11-22 14:05:30 +01001#!/usr/bin/env python
2
3#
4# Copyright (c) 2018 Orange
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17#
18
19"""
20Provides utilities to display oom (sub)modules resources stats
21"""
22
23import os
24import sys
25import getopt
26from fnmatch import fnmatch as match
27import yaml
28
Nicolas Edela7b2ee32018-11-23 08:42:21 +010029def info(thing):
30 if thing:
31 sys.stderr.write("{}\n".format(thing))
32
Nicolas Edela991cf52018-11-22 14:05:30 +010033try:
34 from tabulate import tabulate
35except ImportError as e:
Nicolas Edela7b2ee32018-11-23 08:42:21 +010036 info("Warning: cannot import tabulate module (): {}".format(str(e)))
Nicolas Edela991cf52018-11-22 14:05:30 +010037 def tabulate(lines, headers, tablefmt=None):
Nicolas Edela7b2ee32018-11-23 08:42:21 +010038 ''' basic tabulate function '''
Nicolas Edela991cf52018-11-22 14:05:30 +010039 fmt = ""
40 nbco = len(headers)
41 lenco = map(len, headers)
42 for line in lines:
43 for i in range(nbco):
44 lenco[i] = max(lenco[i], len(str(line[i])))
45
46 fmt = map(lambda n: "{{:<{}}}".format(n), map(lambda i: i+2, lenco))
47 fmt = " ".join(fmt)
48 sep = map(lambda x: '-'*(x+2), lenco)
49
50 output = [fmt.format(*headers), fmt.format(*sep)]
51 for line in lines:
52 output.append(fmt.format(*line))
53 return "\n".join(output)
54
55
56def values(root='.'):
57 ''' Get the list of values.yaml files '''
58 a = []
59 for dirname, dirnames, filenames in os.walk(root):
60 for filename in filenames:
61 if filename == 'values.yaml':
62 a.append((dirname, filename))
63
64 if '.git' in dirnames:
65 # don't go into any .git directories.
66 dirnames.remove('.git')
67 return a
68
69
70def keys(dic, prefix=None):
71 ''' recursively traverse the specified dict to collect existing keys '''
72 result = []
73 if dic:
74 for k, v in dic.items():
75 if prefix:
76 k = '.'.join((prefix, k))
77 if isinstance(v, dict):
78 result += keys(v, k)
79 else:
80 result.append(k)
81 return result
82
83
84class Project:
85 '''
86 class to access to oom (sub)module (aka project) resources
87 '''
88
89 def __init__(self, dirname, filename):
90 self.dirname = os.path.normpath(dirname)
91 self.name = self.explicit()
92 self.filename = os.path.join(dirname, filename)
93 self.resources = None
94 self.load()
95
96 def load(self):
97 ''' load resources from yaml description '''
98 with open(self.filename, 'r') as istream:
99 try:
100 v = yaml.load(istream)
101 if v:
102 self.resources = v.get('resources', None)
103 except Exception as e:
104 print(e)
105 raise
106
107 def explicit(self):
108 ''' return an explicit name for the project '''
109 path = []
110 head, name = os.path.split(self.dirname)
111 if not name:
112 return head
113 while head:
114 head, tail = os.path.split(head)
115 if tail:
116 path.append(tail)
117 else:
118 path.append(head)
119 head = None
120 path.reverse()
121 index = path.index('charts') if 'charts' in path else None
122 if index:
123 name = os.path.join(path[index-1], name)
124 return name
125
126 def __contains__(self, key):
127 params = self.resources
128 if key:
129 for k in key.split('.'):
130 if params and k in params:
131 params = params[k]
132 else:
133 return False
134 return True
135
136 def __getitem__(self, key):
137 params = self.resources
138 for k in key.split('.'):
139 if k in params:
140 params = params[k]
141 if params != self.resources:
142 return params
143
144 def get(self, key, default="-"):
145 """ mimic dict method """
146 if key in self:
147 return self[key]
148 return default
149
150 def keys(self):
151 """ mimic dict method """
152 return keys(self.resources)
153
154
155#
156#
157#
158
159def usage(status=None):
160 """ usage doc """
161 arg0 = os.path.basename(os.path.abspath(sys.argv[0]))
162 print("""Usage: {} [options] <root-directory>""".format(arg0))
163 print((
164 "\n"
165 "Options:\n"
166 "-h, --help Show this help message and exit\n"
167 "-t, --table <format> Use the specified format to display the result table.\n"
168 " Valid formats are those from the python `tabulate'\n"
169 " module. When not available, a basic builtin tabular\n"
170 " function is used and this field has no effect\n"
171 "-f, --fields Comma separated list of resources fields to display.\n"
172 " You may use wildcard patterns, eg small.*. Implicit\n"
173 " value is *, ie all available fields will be used\n"
174 "Examples:\n"
Nicolas Edela7b2ee32018-11-23 08:42:21 +0100175 " # {0} /opt/oom/kubernetes\n"
176 " # {0} -f small.\\* /opt/oom/kubernetes\n"
177 " # {0} -f '*requests.*' -t fancy_grid /opt/oom/kubernetes\n"
178 " # {0} -f small.requests.cpu,small.requests.memory /opt/oom/kubernetes\n"
179 ).format(arg0))
Nicolas Edela991cf52018-11-22 14:05:30 +0100180 if status is not None:
181 sys.exit(status)
182
183
184def getopts():
185 """ read options from cmdline """
186 opts, args = getopt.getopt(sys.argv[1:],
187 "hf:t:",
188 ["help", "fields=", "table="])
189 if len(args) != 1:
190 usage(1)
191
192 root = args[0]
193 table = None
194 fields = ['*']
195 patterns = []
196
197 for opt, arg in opts:
198 if opt in ("-h", '--help'):
199 usage(0)
200 elif opt in ("-f", "--fields"):
201 fields = arg.split(',')
202 elif opt in ("-t", "--table"):
203 table = arg
204
205 return root, table, fields, patterns
206
207
208def main():
209 """ main """
210 try:
211 root, table, fields, patterns = getopts()
212 except getopt.GetoptError as e:
213 print("Error: {}".format(e))
214 usage(1)
215
Nicolas Edela7b2ee32018-11-23 08:42:21 +0100216 if not os.path.isdir(root):
217 info("Cannot open {}: Not a directory".format(root))
218 return
219
Nicolas Edela991cf52018-11-22 14:05:30 +0100220 # find projects
221 projects = []
222 for dirname, filename in values(root):
223 projects.append(Project(dirname, filename))
Nicolas Edela7b2ee32018-11-23 08:42:21 +0100224 if not projects:
225 info("No projects found in {} directory".format(root))
226 return
Nicolas Edela991cf52018-11-22 14:05:30 +0100227
228 # check if we want to use pattern matching (wildcard only)
229 if fields and reduce(lambda x, y: x or y,
230 map(lambda string: '*' in string, fields)):
231 patterns = fields
232 fields = []
233
234 # if fields are not specified or patterns are used, discover available fields
235 # and use them (sort for readability)
236 if patterns or not fields:
237 avail = sorted(set(reduce(lambda x, y: x+y,
238 map(lambda p: p.keys(), projects))))
239 if patterns:
240 for pattern in patterns:
241 fields += filter(lambda string: match(string, pattern), avail)
242 else:
243 fields = avail
244
245 # collect values for each project
246 results = map(lambda project: [project.name] + map(project.get,
247 fields),
248 projects)
249
250 # and then print
251 if results:
252 headers = ['project'] + fields
253 print(tabulate(sorted(results), headers, tablefmt=table))
254
255
256main()