| #!/usr/bin/env python |
| |
| # |
| # Copyright (c) 2018 Orange |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| |
| """ |
| Provides utilities to display oom (sub)modules resources stats |
| """ |
| |
| import os |
| import sys |
| import getopt |
| from fnmatch import fnmatch as match |
| import yaml |
| |
| def info(thing): |
| if thing: |
| sys.stderr.write("{}\n".format(thing)) |
| |
| try: |
| from tabulate import tabulate |
| except ImportError as e: |
| info("Warning: cannot import tabulate module (): {}".format(str(e))) |
| def tabulate(lines, headers, tablefmt=None): |
| ''' basic tabulate function ''' |
| fmt = "" |
| nbco = len(headers) |
| lenco = map(len, headers) |
| for line in lines: |
| for i in range(nbco): |
| lenco[i] = max(lenco[i], len(str(line[i]))) |
| |
| fmt = map(lambda n: "{{:<{}}}".format(n), map(lambda i: i+2, lenco)) |
| fmt = " ".join(fmt) |
| sep = map(lambda x: '-'*(x+2), lenco) |
| |
| output = [fmt.format(*headers), fmt.format(*sep)] |
| for line in lines: |
| output.append(fmt.format(*line)) |
| return "\n".join(output) |
| |
| |
| def values(root='.'): |
| ''' Get the list of values.yaml files ''' |
| a = [] |
| for dirname, dirnames, filenames in os.walk(root): |
| for filename in filenames: |
| if filename == 'values.yaml': |
| a.append((dirname, filename)) |
| |
| if '.git' in dirnames: |
| # don't go into any .git directories. |
| dirnames.remove('.git') |
| return a |
| |
| |
| def keys(dic, prefix=None): |
| ''' recursively traverse the specified dict to collect existing keys ''' |
| result = [] |
| if dic: |
| for k, v in dic.items(): |
| if prefix: |
| k = '.'.join((prefix, k)) |
| if isinstance(v, dict): |
| result += keys(v, k) |
| else: |
| result.append(k) |
| return result |
| |
| |
| class Project: |
| ''' |
| class to access to oom (sub)module (aka project) resources |
| ''' |
| |
| def __init__(self, dirname, filename): |
| self.dirname = os.path.normpath(dirname) |
| self.name = self.explicit() |
| self.filename = os.path.join(dirname, filename) |
| self.resources = None |
| self.load() |
| |
| def load(self): |
| ''' load resources from yaml description ''' |
| with open(self.filename, 'r') as istream: |
| try: |
| v = yaml.load(istream) |
| if v: |
| self.resources = v.get('resources', None) |
| except Exception as e: |
| print(e) |
| raise |
| |
| def explicit(self): |
| ''' return an explicit name for the project ''' |
| path = [] |
| head, name = os.path.split(self.dirname) |
| if not name: |
| return head |
| while head: |
| head, tail = os.path.split(head) |
| if tail: |
| path.append(tail) |
| else: |
| path.append(head) |
| head = None |
| path.reverse() |
| index = path.index('charts') if 'charts' in path else None |
| if index: |
| name = os.path.join(path[index-1], name) |
| return name |
| |
| def __contains__(self, key): |
| params = self.resources |
| if key: |
| for k in key.split('.'): |
| if params and k in params: |
| params = params[k] |
| else: |
| return False |
| return True |
| |
| def __getitem__(self, key): |
| params = self.resources |
| for k in key.split('.'): |
| if k in params: |
| params = params[k] |
| if params != self.resources: |
| return params |
| |
| def get(self, key, default="-"): |
| """ mimic dict method """ |
| if key in self: |
| return self[key] |
| return default |
| |
| def keys(self): |
| """ mimic dict method """ |
| return keys(self.resources) |
| |
| |
| # |
| # |
| # |
| |
| def usage(status=None): |
| """ usage doc """ |
| arg0 = os.path.basename(os.path.abspath(sys.argv[0])) |
| print("""Usage: {} [options] <root-directory>""".format(arg0)) |
| print(( |
| "\n" |
| "Options:\n" |
| "-h, --help Show this help message and exit\n" |
| "-t, --table <format> Use the specified format to display the result table.\n" |
| " Valid formats are those from the python `tabulate'\n" |
| " module. When not available, a basic builtin tabular\n" |
| " function is used and this field has no effect\n" |
| "-f, --fields Comma separated list of resources fields to display.\n" |
| " You may use wildcard patterns, eg small.*. Implicit\n" |
| " value is *, ie all available fields will be used\n" |
| "Examples:\n" |
| " # {0} /opt/oom/kubernetes\n" |
| " # {0} -f small.\\* /opt/oom/kubernetes\n" |
| " # {0} -f '*requests.*' -t fancy_grid /opt/oom/kubernetes\n" |
| " # {0} -f small.requests.cpu,small.requests.memory /opt/oom/kubernetes\n" |
| ).format(arg0)) |
| if status is not None: |
| sys.exit(status) |
| |
| |
| def getopts(): |
| """ read options from cmdline """ |
| opts, args = getopt.getopt(sys.argv[1:], |
| "hf:t:", |
| ["help", "fields=", "table="]) |
| if len(args) != 1: |
| usage(1) |
| |
| root = args[0] |
| table = None |
| fields = ['*'] |
| patterns = [] |
| |
| for opt, arg in opts: |
| if opt in ("-h", '--help'): |
| usage(0) |
| elif opt in ("-f", "--fields"): |
| fields = arg.split(',') |
| elif opt in ("-t", "--table"): |
| table = arg |
| |
| return root, table, fields, patterns |
| |
| |
| def main(): |
| """ main """ |
| try: |
| root, table, fields, patterns = getopts() |
| except getopt.GetoptError as e: |
| print("Error: {}".format(e)) |
| usage(1) |
| |
| if not os.path.isdir(root): |
| info("Cannot open {}: Not a directory".format(root)) |
| return |
| |
| # find projects |
| projects = [] |
| for dirname, filename in values(root): |
| projects.append(Project(dirname, filename)) |
| if not projects: |
| info("No projects found in {} directory".format(root)) |
| return |
| |
| # check if we want to use pattern matching (wildcard only) |
| if fields and reduce(lambda x, y: x or y, |
| map(lambda string: '*' in string, fields)): |
| patterns = fields |
| fields = [] |
| |
| # if fields are not specified or patterns are used, discover available fields |
| # and use them (sort for readability) |
| if patterns or not fields: |
| avail = sorted(set(reduce(lambda x, y: x+y, |
| map(lambda p: p.keys(), projects)))) |
| if patterns: |
| for pattern in patterns: |
| fields += filter(lambda string: match(string, pattern), avail) |
| else: |
| fields = avail |
| |
| # collect values for each project |
| results = map(lambda project: [project.name] + map(project.get, |
| fields), |
| projects) |
| |
| # and then print |
| if results: |
| headers = ['project'] + fields |
| print(tabulate(sorted(results), headers, tablefmt=table)) |
| |
| |
| main() |