4 # Copyright (c) 2018 Orange
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
10 # http://www.apache.org/licenses/LICENSE-2.0
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.
20 Provides utilities to display oom (sub)modules resources stats
26 from fnmatch import fnmatch as match
31 sys.stderr.write("{}\n".format(thing))
34 from tabulate import tabulate
35 except ImportError as e:
36 info("Warning: cannot import tabulate module (): {}".format(str(e)))
37 def tabulate(lines, headers, tablefmt=None):
38 ''' basic tabulate function '''
41 lenco = map(len, headers)
44 lenco[i] = max(lenco[i], len(str(line[i])))
46 fmt = map(lambda n: "{{:<{}}}".format(n), map(lambda i: i+2, lenco))
48 sep = map(lambda x: '-'*(x+2), lenco)
50 output = [fmt.format(*headers), fmt.format(*sep)]
52 output.append(fmt.format(*line))
53 return "\n".join(output)
57 ''' Get the list of values.yaml files '''
59 for dirname, dirnames, filenames in os.walk(root):
60 for filename in filenames:
61 if filename == 'values.yaml':
62 a.append((dirname, filename))
64 if '.git' in dirnames:
65 # don't go into any .git directories.
66 dirnames.remove('.git')
70 def keys(dic, prefix=None):
71 ''' recursively traverse the specified dict to collect existing keys '''
74 for k, v in dic.items():
76 k = '.'.join((prefix, k))
77 if isinstance(v, dict):
86 class to access to oom (sub)module (aka project) resources
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)
97 ''' load resources from yaml description '''
98 with open(self.filename, 'r') as istream:
100 v = yaml.load(istream)
102 self.resources = v.get('resources', None)
103 except Exception as e:
108 ''' return an explicit name for the project '''
110 head, name = os.path.split(self.dirname)
114 head, tail = os.path.split(head)
121 index = path.index('charts') if 'charts' in path else None
123 name = os.path.join(path[index-1], name)
126 def __contains__(self, key):
127 params = self.resources
129 for k in key.split('.'):
130 if params and k in params:
136 def __getitem__(self, key):
137 params = self.resources
138 for k in key.split('.'):
141 if params != self.resources:
144 def get(self, key, default="-"):
145 """ mimic dict method """
151 """ mimic dict method """
152 return keys(self.resources)
159 def usage(status=None):
161 arg0 = os.path.basename(os.path.abspath(sys.argv[0]))
162 print("""Usage: {} [options] <root-directory>""".format(arg0))
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"
175 " # {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"
180 if status is not None:
185 """ read options from cmdline """
186 opts, args = getopt.getopt(sys.argv[1:],
188 ["help", "fields=", "table="])
197 for opt, arg in opts:
198 if opt in ("-h", '--help'):
200 elif opt in ("-f", "--fields"):
201 fields = arg.split(',')
202 elif opt in ("-t", "--table"):
205 return root, table, fields, patterns
211 root, table, fields, patterns = getopts()
212 except getopt.GetoptError as e:
213 print("Error: {}".format(e))
216 if not os.path.isdir(root):
217 info("Cannot open {}: Not a directory".format(root))
222 for dirname, filename in values(root):
223 projects.append(Project(dirname, filename))
225 info("No projects found in {} directory".format(root))
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)):
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))))
240 for pattern in patterns:
241 fields += filter(lambda string: match(string, pattern), avail)
245 # collect values for each project
246 results = map(lambda project: [project.name] + map(project.get,
252 headers = ['project'] + fields
253 print(tabulate(sorted(results), headers, tablefmt=table))