Merge "[DMAAP] DMaaP ServiceMesh compatibility"
[oom.git] / kubernetes / contrib / tools / oomstat.py
1 #!/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 """
20 Provides utilities to display oom (sub)modules resources stats
21 """
22
23 import os
24 import sys
25 import getopt
26 from fnmatch import fnmatch as match
27 import yaml
28
29 def info(thing):
30     if thing:
31         sys.stderr.write("{}\n".format(thing))
32
33 try:
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 '''
39         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
56 def 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
70 def 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
84 class 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
159 def 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"
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"
179     ).format(arg0))
180     if status is not None:
181         sys.exit(status)
182
183
184 def 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
208 def 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
216     if not os.path.isdir(root):
217         info("Cannot open {}: Not a directory".format(root))
218         return
219
220     # find projects
221     projects = []
222     for dirname, filename in values(root):
223         projects.append(Project(dirname, filename))
224     if not projects:
225         info("No projects found in {} directory".format(root))
226         return
227
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
256 main()