Add script to list (sub)projects resources claims
[oom.git] / kubernetes / contrib / tools / oomstat
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 try:
30     from tabulate import tabulate
31 except ImportError as e:
32     message = "Warning: cannot import tabulate module (): {}\n".format(str(e))
33     sys.stderr.write(message)
34     def tabulate(lines, headers, tablefmt=None):
35         fmt = ""
36         nbco = len(headers)
37         lenco = map(len, headers)
38         for line in lines:
39             for i in range(nbco):
40                 lenco[i] = max(lenco[i], len(str(line[i])))
41
42         fmt = map(lambda n: "{{:<{}}}".format(n), map(lambda i: i+2, lenco))
43         fmt = "  ".join(fmt)
44         sep = map(lambda x: '-'*(x+2), lenco)
45
46         output = [fmt.format(*headers), fmt.format(*sep)]
47         for line in lines:
48             output.append(fmt.format(*line))
49         return "\n".join(output)
50
51
52 def values(root='.'):
53     ''' Get the list of values.yaml files '''
54     a = []
55     for dirname, dirnames, filenames in os.walk(root):
56         for filename in filenames:
57             if filename == 'values.yaml':
58                 a.append((dirname, filename))
59
60         if '.git' in dirnames:
61             # don't go into any .git directories.
62             dirnames.remove('.git')
63     return a
64
65
66 def keys(dic, prefix=None):
67     ''' recursively traverse the specified dict to collect existing keys '''
68     result = []
69     if dic:
70         for k, v in dic.items():
71             if prefix:
72                 k = '.'.join((prefix, k))
73             if isinstance(v, dict):
74                 result += keys(v, k)
75             else:
76                 result.append(k)
77     return result
78
79
80 class Project:
81     '''
82     class to access to oom (sub)module (aka project) resources
83     '''
84
85     def __init__(self, dirname, filename):
86         self.dirname = os.path.normpath(dirname)
87         self.name = self.explicit()
88         self.filename = os.path.join(dirname, filename)
89         self.resources = None
90         self.load()
91
92     def load(self):
93         ''' load resources from yaml description '''
94         with open(self.filename, 'r') as istream:
95             try:
96                 v = yaml.load(istream)
97                 if v:
98                     self.resources = v.get('resources', None)
99             except Exception as e:
100                 print(e)
101                 raise
102
103     def explicit(self):
104         ''' return an explicit name for the project '''
105         path = []
106         head, name = os.path.split(self.dirname)
107         if not name:
108             return head
109         while head:
110             head, tail = os.path.split(head)
111             if tail:
112                 path.append(tail)
113             else:
114                 path.append(head)
115                 head = None
116         path.reverse()
117         index = path.index('charts') if 'charts' in path else None
118         if index:
119             name = os.path.join(path[index-1], name)
120         return name
121
122     def __contains__(self, key):
123         params = self.resources
124         if key:
125             for k in key.split('.'):
126                 if params and k in params:
127                     params = params[k]
128                 else:
129                     return False
130         return True
131
132     def __getitem__(self, key):
133         params = self.resources
134         for k in key.split('.'):
135             if k in params:
136                 params = params[k]
137         if params != self.resources:
138             return params
139
140     def get(self, key, default="-"):
141         """ mimic dict method """
142         if key in self:
143             return self[key]
144         return default
145
146     def keys(self):
147         """ mimic dict method """
148         return keys(self.resources)
149
150
151 #
152 #
153 #
154
155 def usage(status=None):
156     """ usage doc """
157     arg0 = os.path.basename(os.path.abspath(sys.argv[0]))
158     print("""Usage: {} [options] <root-directory>""".format(arg0))
159     print((
160         "\n"
161         "Options:\n"
162         "-h, --help           Show this help message and exit\n"
163         "-t, --table <format> Use the specified format to display the result table.\n"
164         "                     Valid formats are those from the python `tabulate'\n"
165         "                     module. When not available, a basic builtin tabular\n"
166         "                     function is used and this field has no effect\n"
167         "-f, --fields         Comma separated list of resources fields to display.\n"
168         "                     You may use wildcard patterns, eg small.*. Implicit\n"
169         "                     value is *, ie all available fields will be used\n"
170         "Examples:\n"
171         "    # oomstat /opt/oom/kubernetes\n"
172         "    # oomstat -f small.\\* /opt/oom/kubernetes\n"
173         "    # oomstat -f '*requests.*' -t fancy_grid /opt/oom/kubernetes\n"
174         "    # oomstat -f small.requests.cpu,small.requests.memory /opt/oom/kubernetes\n"
175     ))
176     if status is not None:
177         sys.exit(status)
178
179
180 def getopts():
181     """ read options from cmdline """
182     opts, args = getopt.getopt(sys.argv[1:],
183                                "hf:t:",
184                                ["help", "fields=", "table="])
185     if len(args) != 1:
186         usage(1)
187
188     root = args[0]
189     table = None
190     fields = ['*']
191     patterns = []
192
193     for opt, arg in opts:
194         if opt in ("-h", '--help'):
195             usage(0)
196         elif opt in ("-f", "--fields"):
197             fields = arg.split(',')
198         elif opt in ("-t", "--table"):
199             table = arg
200
201     return root, table, fields, patterns
202
203
204 def main():
205     """ main """
206     try:
207         root, table, fields, patterns = getopts()
208     except getopt.GetoptError as e:
209         print("Error: {}".format(e))
210         usage(1)
211
212     # find projects
213     projects = []
214     for dirname, filename in values(root):
215         projects.append(Project(dirname, filename))
216
217     # check if we want to use pattern matching (wildcard only)
218     if fields and reduce(lambda x, y: x or y,
219                          map(lambda string: '*' in string, fields)):
220         patterns = fields
221         fields = []
222
223     # if fields are not specified or patterns are used, discover available fields
224     #  and use them (sort for readability)
225     if patterns or not fields:
226         avail = sorted(set(reduce(lambda x, y: x+y,
227                                   map(lambda p: p.keys(), projects))))
228         if patterns:
229             for pattern in patterns:
230                 fields += filter(lambda string: match(string, pattern), avail)
231         else:
232             fields = avail
233
234     # collect values for each project
235     results = map(lambda project: [project.name] + map(project.get,
236                                                        fields),
237                   projects)
238
239     # and then print
240     if results:
241         headers = ['project'] + fields
242         print(tabulate(sorted(results), headers, tablefmt=table))
243
244
245 main()