Fix some bugs when testing opensds ansible
[stor4nfv.git] / src / ceph / src / pybind / mgr / status / module.py
1
2 """
3 High level status display commands
4 """
5
6 from collections import defaultdict
7 from prettytable import PrettyTable
8 import prettytable
9 import fnmatch
10 import errno
11
12 from mgr_module import MgrModule
13
14
15 class Module(MgrModule):
16     COMMANDS = [
17         {
18             "cmd": "fs status "
19                    "name=fs,type=CephString,req=false",
20             "desc": "Show the status of a CephFS filesystem",
21             "perm": "r"
22         },
23         {
24             "cmd": "osd status "
25                    "name=bucket,type=CephString,req=false",
26             "desc": "Show the status of OSDs within a bucket, or all",
27             "perm": "r"
28         },
29     ]
30
31     (
32         BLACK,
33         RED,
34         GREEN,
35         YELLOW,
36         BLUE,
37         MAGENTA,
38         CYAN,
39         GRAY
40     ) = range(8)
41
42     RESET_SEQ = "\033[0m"
43     COLOR_SEQ = "\033[1;%dm"
44     COLOR_DARK_SEQ = "\033[0;%dm"
45     BOLD_SEQ = "\033[1m"
46     UNDERLINE_SEQ = "\033[4m"
47
48     def colorize(self, msg, color, dark=False):
49         """
50         Decorate `msg` with escape sequences to give the requested color
51         """
52         return (self.COLOR_DARK_SEQ if dark else self.COLOR_SEQ) % (30 + color) \
53                + msg + self.RESET_SEQ
54
55     def bold(self, msg):
56         """
57         Decorate `msg` with escape sequences to make it appear bold
58         """
59         return self.BOLD_SEQ + msg + self.RESET_SEQ
60
61     def format_units(self, n, width, colored, decimal):
62         """
63         Format a number without units, so as to fit into `width` characters, substituting
64         an appropriate unit suffix.
65         
66         Use decimal for dimensionless things, use base 2 (decimal=False) for byte sizes/rates.
67         """
68         
69         factor = 1000 if decimal else 1024
70         units = [' ', 'k', 'M', 'G', 'T', 'P']
71         unit = 0
72         while len("%s" % (int(n) // (factor**unit))) > width - 1:
73             unit += 1
74
75         if unit > 0:
76             truncated_float = ("%f" % (n / (float(factor) ** unit)))[0:width - 1]
77             if truncated_float[-1] == '.':
78                 truncated_float = " " + truncated_float[0:-1]
79         else:
80             truncated_float = "%{wid}d".format(wid=width-1) % n
81         formatted = "%s%s" % (truncated_float, units[unit])
82
83         if colored:
84             if n == 0:
85                 color = self.BLACK, False
86             else:
87                 color = self.YELLOW, False
88             return self.bold(self.colorize(formatted[0:-1], color[0], color[1])) \
89                 + self.bold(self.colorize(formatted[-1], self.BLACK, False))
90         else:
91             return formatted
92
93     def format_dimless(self, n, width, colored=True):
94         return self.format_units(n, width, colored, decimal=True)
95     
96     def format_bytes(self, n, width, colored=True):
97         return self.format_units(n, width, colored, decimal=False)
98         
99     def get_latest(self, daemon_type, daemon_name, stat):
100         data = self.get_counter(daemon_type, daemon_name, stat)[stat]
101         #self.log.error("get_latest {0} data={1}".format(stat, data))
102         if data:
103             return data[-1][1]
104         else:
105             return 0
106
107     def get_rate(self, daemon_type, daemon_name, stat):
108         data = self.get_counter(daemon_type, daemon_name, stat)[stat]
109
110         #self.log.error("get_latest {0} data={1}".format(stat, data))
111         if data and len(data) > 1:
112             return (data[-1][1] - data[-2][1]) / float(data[-1][0] - data[-2][0])
113         else:
114             return 0
115
116     def handle_fs_status(self, cmd):
117         output = ""
118
119         fs_filter = cmd.get('fs', None)
120
121         mds_versions = defaultdict(list)
122
123         fsmap = self.get("fs_map")
124         for filesystem in fsmap['filesystems']:
125             if fs_filter and filesystem['mdsmap']['fs_name'] != fs_filter:
126                 continue
127
128             rank_table = PrettyTable(
129                 ("Rank", "State", "MDS", "Activity", "dns", "inos"),
130                 hrules=prettytable.FRAME
131             )
132
133             mdsmap = filesystem['mdsmap']
134
135             client_count = 0
136
137             for rank in mdsmap["in"]:
138                 up = "mds_{0}".format(rank) in mdsmap["up"]
139                 if up:
140                     gid = mdsmap['up']["mds_{0}".format(rank)]
141                     info = mdsmap['info']['gid_{0}'.format(gid)]
142                     dns = self.get_latest("mds", info['name'], "mds_mem.dn")
143                     inos = self.get_latest("mds", info['name'], "mds_mem.ino")
144
145                     if rank == 0:
146                         client_count = self.get_latest("mds", info['name'],
147                                                        "mds_sessions.session_count")
148                     elif client_count == 0:
149                         # In case rank 0 was down, look at another rank's
150                         # sessionmap to get an indication of clients.
151                         client_count = self.get_latest("mds", info['name'],
152                                                        "mds_sessions.session_count")
153
154                     laggy = "laggy_since" in info
155
156                     state = info['state'].split(":")[1]
157                     if laggy:
158                         state += "(laggy)"
159                     if state == "active" and not laggy:
160                         c_state = self.colorize(state, self.GREEN)
161                     else:
162                         c_state = self.colorize(state, self.YELLOW)
163
164                     # Populate based on context of state, e.g. client
165                     # ops for an active daemon, replay progress, reconnect
166                     # progress
167                     activity = ""
168
169                     if state == "active":
170                         activity = "Reqs: " + self.format_dimless(
171                             self.get_rate("mds", info['name'], "mds_server.handle_client_request"),
172                             5
173                         ) + "/s"
174
175                     metadata = self.get_metadata('mds', info['name'])
176                     mds_versions[metadata.get('ceph_version', "unknown")].append(info['name'])
177                     rank_table.add_row([
178                         self.bold(rank.__str__()), c_state, info['name'],
179                         activity,
180                         self.format_dimless(dns, 5),
181                         self.format_dimless(inos, 5)
182                     ])
183
184                 else:
185                     rank_table.add_row([
186                         rank, "failed", "", "", "", ""
187                     ])
188
189             # Find the standby replays
190             for gid_str, daemon_info in mdsmap['info'].iteritems():
191                 if daemon_info['state'] != "up:standby-replay":
192                     continue
193
194                 inos = self.get_latest("mds", daemon_info['name'], "mds_mem.ino")
195                 dns = self.get_latest("mds", daemon_info['name'], "mds_mem.dn")
196
197                 activity = "Evts: " + self.format_dimless(
198                     self.get_rate("mds", daemon_info['name'], "mds_log.replay"),
199                     5
200                 ) + "/s"
201
202                 rank_table.add_row([
203                     "{0}-s".format(daemon_info['rank']), "standby-replay",
204                     daemon_info['name'], activity,
205                     self.format_dimless(dns, 5),
206                     self.format_dimless(inos, 5)
207                 ])
208
209             df = self.get("df")
210             pool_stats = dict([(p['id'], p['stats']) for p in df['pools']])
211             osdmap = self.get("osd_map")
212             pools = dict([(p['pool'], p) for p in osdmap['pools']])
213             metadata_pool_id = mdsmap['metadata_pool']
214             data_pool_ids = mdsmap['data_pools']
215
216             pools_table = PrettyTable(["Pool", "type", "used", "avail"])
217             for pool_id in [metadata_pool_id] + data_pool_ids:
218                 pool_type = "metadata" if pool_id == metadata_pool_id else "data"
219                 stats = pool_stats[pool_id]
220                 pools_table.add_row([
221                     pools[pool_id]['pool_name'], pool_type,
222                     self.format_bytes(stats['bytes_used'], 5),
223                     self.format_bytes(stats['max_avail'], 5)
224                 ])
225
226             output += "{0} - {1} clients\n".format(
227                 mdsmap['fs_name'], client_count)
228             output += "=" * len(mdsmap['fs_name']) + "\n"
229             output += rank_table.get_string()
230             output += "\n" + pools_table.get_string() + "\n"
231
232         standby_table = PrettyTable(["Standby MDS"])
233         for standby in fsmap['standbys']:
234             metadata = self.get_metadata('mds', standby['name'])
235             mds_versions[metadata.get('ceph_version', "unknown")].append(standby['name'])
236
237             standby_table.add_row([standby['name']])
238
239         output += "\n" + standby_table.get_string() + "\n"
240
241         if len(mds_versions) == 1:
242             output += "MDS version: {0}".format(mds_versions.keys()[0])
243         else:
244             version_table = PrettyTable(["version", "daemons"])
245             for version, daemons in mds_versions.iteritems():
246                 version_table.add_row([
247                     version,
248                     ", ".join(daemons)
249                 ])
250             output += version_table.get_string() + "\n"
251
252         return 0, "", output
253
254     def handle_osd_status(self, cmd):
255         osd_table = PrettyTable(['id', 'host', 'used', 'avail', 'wr ops', 'wr data', 'rd ops', 'rd data'])
256         osdmap = self.get("osd_map")
257
258         filter_osds = set()
259         bucket_filter = None
260         if 'bucket' in cmd:
261             self.log.debug("Filtering to bucket '{0}'".format(cmd['bucket']))
262             bucket_filter = cmd['bucket']
263             crush = self.get("osd_map_crush")
264             found = False
265             for bucket in crush['buckets']:
266                 if fnmatch.fnmatch(bucket['name'], bucket_filter):
267                     found = True
268                     filter_osds.update([i['id'] for i in bucket['items']])
269
270             if not found:
271                 msg = "Bucket '{0}' not found".format(bucket_filter)
272                 return errno.ENOENT, msg, ""
273
274         # Build dict of OSD ID to stats
275         osd_stats = dict([(o['osd'], o) for o in self.get("osd_stats")['osd_stats']])
276
277         for osd in osdmap['osds']:
278             osd_id = osd['osd']
279             if bucket_filter and osd_id not in filter_osds:
280                 continue
281
282             metadata = self.get_metadata('osd', "%s" % osd_id)
283             stats = osd_stats[osd_id]
284
285             osd_table.add_row([osd_id, metadata['hostname'],
286                                self.format_bytes(stats['kb_used'] * 1024, 5),
287                                self.format_bytes(stats['kb_avail'] * 1024, 5),
288                                self.format_dimless(self.get_rate("osd", osd_id.__str__(), "osd.op_w") +
289                                self.get_rate("osd", osd_id.__str__(), "osd.op_rw"), 5),
290                                self.format_bytes(self.get_rate("osd", osd_id.__str__(), "osd.op_in_bytes"), 5),
291                                self.format_dimless(self.get_rate("osd", osd_id.__str__(), "osd.op_r"), 5),
292                                self.format_bytes(self.get_rate("osd", osd_id.__str__(), "osd.op_out_bytes"), 5),
293                                ])
294
295         return 0, "", osd_table.get_string()
296
297     def handle_command(self, cmd):
298         self.log.error("handle_command")
299
300         if cmd['prefix'] == "fs status":
301             return self.handle_fs_status(cmd)
302         elif cmd['prefix'] == "osd status":
303             return self.handle_osd_status(cmd)
304         else:
305             # mgr should respect our self.COMMANDS and not call us for
306             # any prefix we don't advertise
307             raise NotImplementedError(cmd['prefix'])