Add script to list (sub)projects resources claims

Change-Id: I9282754ec9d53ad78847235dd00c6e403bed7687
Issue-ID: OOM-1527
Signed-off-by: Nicolas Edel <nicolas.edel@orange.com>
diff --git a/kubernetes/contrib/tools/oomstat b/kubernetes/contrib/tools/oomstat
new file mode 100755
index 0000000..82453b0
--- /dev/null
+++ b/kubernetes/contrib/tools/oomstat
@@ -0,0 +1,245 @@
+#!/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
+
+try:
+    from tabulate import tabulate
+except ImportError as e:
+    message = "Warning: cannot import tabulate module (): {}\n".format(str(e))
+    sys.stderr.write(message)
+    def tabulate(lines, headers, tablefmt=None):
+        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"
+        "    # oomstat /opt/oom/kubernetes\n"
+        "    # oomstat -f small.\\* /opt/oom/kubernetes\n"
+        "    # oomstat -f '*requests.*' -t fancy_grid /opt/oom/kubernetes\n"
+        "    # oomstat -f small.requests.cpu,small.requests.memory /opt/oom/kubernetes\n"
+    ))
+    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)
+
+    # find projects
+    projects = []
+    for dirname, filename in values(root):
+        projects.append(Project(dirname, filename))
+
+    # 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()