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()