]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | # -*- coding: utf-8 -*- |
2 | from __future__ import absolute_import | |
3 | ||
4 | import json | |
5 | ||
6 | import rados | |
7 | ||
8 | from mgr_module import CommandResult | |
9 | ||
10 | try: | |
11 | from more_itertools import pairwise | |
12 | except ImportError: | |
13 | def pairwise(iterable): | |
14 | from itertools import tee | |
15 | a, b = tee(iterable) | |
16 | next(b, None) | |
17 | return zip(a, b) | |
18 | ||
19 | from .. import logger, mgr | |
20 | ||
81eedcae TL |
21 | try: |
22 | from typing import Dict, Any # pylint: disable=unused-import | |
23 | except ImportError: | |
24 | pass # For typing only | |
25 | ||
11fdf7f2 TL |
26 | |
27 | class SendCommandError(rados.Error): | |
28 | def __init__(self, err, prefix, argdict, errno): | |
29 | self.prefix = prefix | |
30 | self.argdict = argdict | |
31 | super(SendCommandError, self).__init__(err, errno) | |
32 | ||
33 | ||
34 | class CephService(object): | |
35 | ||
36 | OSD_FLAG_NO_SCRUB = 'noscrub' | |
37 | OSD_FLAG_NO_DEEP_SCRUB = 'nodeep-scrub' | |
38 | ||
39 | PG_STATUS_SCRUBBING = 'scrubbing' | |
40 | PG_STATUS_DEEP_SCRUBBING = 'deep' | |
41 | ||
42 | SCRUB_STATUS_DISABLED = 'Disabled' | |
43 | SCRUB_STATUS_ACTIVE = 'Active' | |
44 | SCRUB_STATUS_INACTIVE = 'Inactive' | |
45 | ||
46 | @classmethod | |
47 | def get_service_map(cls, service_name): | |
81eedcae | 48 | service_map = {} # type: Dict[str, Dict[str, Any]] |
11fdf7f2 TL |
49 | for server in mgr.list_servers(): |
50 | for service in server['services']: | |
51 | if service['type'] == service_name: | |
52 | if server['hostname'] not in service_map: | |
53 | service_map[server['hostname']] = { | |
54 | 'server': server, | |
55 | 'services': [] | |
56 | } | |
57 | inst_id = service['id'] | |
58 | metadata = mgr.get_metadata(service_name, inst_id) | |
59 | status = mgr.get_daemon_status(service_name, inst_id) | |
60 | service_map[server['hostname']]['services'].append({ | |
61 | 'id': inst_id, | |
62 | 'type': service_name, | |
63 | 'hostname': server['hostname'], | |
64 | 'metadata': metadata, | |
65 | 'status': status | |
66 | }) | |
67 | return service_map | |
68 | ||
69 | @classmethod | |
70 | def get_service_list(cls, service_name): | |
71 | service_map = cls.get_service_map(service_name) | |
72 | return [svc for _, svcs in service_map.items() for svc in svcs['services']] | |
73 | ||
74 | @classmethod | |
75 | def get_service(cls, service_name, service_id): | |
76 | for server in mgr.list_servers(): | |
77 | for service in server['services']: | |
78 | if service['type'] == service_name: | |
79 | inst_id = service['id'] | |
80 | if inst_id == service_id: | |
81 | metadata = mgr.get_metadata(service_name, inst_id) | |
82 | status = mgr.get_daemon_status(service_name, inst_id) | |
83 | return { | |
84 | 'id': inst_id, | |
85 | 'type': service_name, | |
86 | 'hostname': server['hostname'], | |
87 | 'metadata': metadata, | |
88 | 'status': status | |
89 | } | |
90 | return None | |
91 | ||
92 | @classmethod | |
93 | def get_pool_list(cls, application=None): | |
94 | osd_map = mgr.get('osd_map') | |
95 | if not application: | |
96 | return osd_map['pools'] | |
97 | return [pool for pool in osd_map['pools'] | |
98 | if application in pool.get('application_metadata', {})] | |
99 | ||
100 | @classmethod | |
101 | def get_pool_list_with_stats(cls, application=None): | |
102 | # pylint: disable=too-many-locals | |
103 | pools = cls.get_pool_list(application) | |
104 | ||
105 | pools_w_stats = [] | |
106 | ||
107 | pg_summary = mgr.get("pg_summary") | |
108 | pool_stats = mgr.get_updated_pool_stats() | |
109 | ||
110 | for pool in pools: | |
111 | pool['pg_status'] = pg_summary['by_pool'][pool['pool'].__str__()] | |
112 | stats = pool_stats[pool['pool']] | |
113 | s = {} | |
114 | ||
115 | def get_rate(series): | |
116 | if len(series) >= 2: | |
117 | return differentiate(*list(series)[-2:]) | |
118 | return 0 | |
119 | ||
120 | for stat_name, stat_series in stats.items(): | |
121 | s[stat_name] = { | |
122 | 'latest': stat_series[0][1], | |
123 | 'rate': get_rate(stat_series), | |
494da23a | 124 | 'rates': get_rates_from_data(stat_series) |
11fdf7f2 TL |
125 | } |
126 | pool['stats'] = s | |
127 | pools_w_stats.append(pool) | |
128 | return pools_w_stats | |
129 | ||
130 | @classmethod | |
131 | def get_pool_name_from_id(cls, pool_id): | |
132 | pool_list = cls.get_pool_list() | |
133 | for pool in pool_list: | |
134 | if pool['pool'] == pool_id: | |
135 | return pool['pool_name'] | |
136 | return None | |
137 | ||
138 | @classmethod | |
139 | def send_command(cls, srv_type, prefix, srv_spec='', **kwargs): | |
140 | """ | |
141 | :type prefix: str | |
142 | :param srv_type: mon | | |
143 | :param kwargs: will be added to argdict | |
144 | :param srv_spec: typically empty. or something like "<fs_id>:0" | |
145 | ||
146 | :raises PermissionError: See rados.make_ex | |
147 | :raises ObjectNotFound: See rados.make_ex | |
148 | :raises IOError: See rados.make_ex | |
149 | :raises NoSpace: See rados.make_ex | |
150 | :raises ObjectExists: See rados.make_ex | |
151 | :raises ObjectBusy: See rados.make_ex | |
152 | :raises NoData: See rados.make_ex | |
153 | :raises InterruptedOrTimeoutError: See rados.make_ex | |
154 | :raises TimedOut: See rados.make_ex | |
155 | :raises ValueError: return code != 0 | |
156 | """ | |
157 | argdict = { | |
158 | "prefix": prefix, | |
159 | "format": "json", | |
160 | } | |
161 | argdict.update({k: v for k, v in kwargs.items() if v is not None}) | |
162 | result = CommandResult("") | |
163 | mgr.send_command(result, srv_type, srv_spec, json.dumps(argdict), "") | |
164 | r, outb, outs = result.wait() | |
165 | if r != 0: | |
166 | msg = "send_command '{}' failed. (r={}, outs=\"{}\", kwargs={})".format(prefix, r, outs, | |
167 | kwargs) | |
168 | logger.error(msg) | |
169 | raise SendCommandError(outs, prefix, argdict, r) | |
170 | else: | |
171 | try: | |
172 | return json.loads(outb) | |
173 | except Exception: # pylint: disable=broad-except | |
174 | return outb | |
175 | ||
176 | @classmethod | |
177 | def get_rates(cls, svc_type, svc_name, path): | |
178 | """ | |
179 | :return: the derivative of mgr.get_counter() | |
180 | :rtype: list[tuple[int, float]]""" | |
181 | data = mgr.get_counter(svc_type, svc_name, path)[path] | |
494da23a | 182 | return get_rates_from_data(data) |
11fdf7f2 TL |
183 | |
184 | @classmethod | |
185 | def get_rate(cls, svc_type, svc_name, path): | |
186 | """returns most recent rate""" | |
187 | data = mgr.get_counter(svc_type, svc_name, path)[path] | |
188 | ||
189 | if data and len(data) > 1: | |
190 | return differentiate(*data[-2:]) | |
191 | return 0.0 | |
192 | ||
193 | @classmethod | |
194 | def get_client_perf(cls): | |
195 | pools_stats = mgr.get('osd_pool_stats')['pool_stats'] | |
196 | ||
197 | io_stats = { | |
198 | 'read_bytes_sec': 0, | |
199 | 'read_op_per_sec': 0, | |
200 | 'write_bytes_sec': 0, | |
201 | 'write_op_per_sec': 0, | |
202 | } | |
203 | recovery_stats = {'recovering_bytes_per_sec': 0} | |
204 | ||
205 | for pool_stats in pools_stats: | |
206 | client_io = pool_stats['client_io_rate'] | |
207 | for stat in list(io_stats.keys()): | |
208 | if stat in client_io: | |
209 | io_stats[stat] += client_io[stat] | |
210 | ||
211 | client_recovery = pool_stats['recovery_rate'] | |
212 | for stat in list(recovery_stats.keys()): | |
213 | if stat in client_recovery: | |
214 | recovery_stats[stat] += client_recovery[stat] | |
215 | ||
216 | client_perf = io_stats.copy() | |
217 | client_perf.update(recovery_stats) | |
218 | ||
219 | return client_perf | |
220 | ||
221 | @classmethod | |
222 | def get_scrub_status(cls): | |
223 | enabled_flags = mgr.get('osd_map')['flags_set'] | |
224 | if cls.OSD_FLAG_NO_SCRUB in enabled_flags or cls.OSD_FLAG_NO_DEEP_SCRUB in enabled_flags: | |
225 | return cls.SCRUB_STATUS_DISABLED | |
226 | ||
227 | grouped_pg_statuses = mgr.get('pg_summary')['all'] | |
228 | for grouped_pg_status in grouped_pg_statuses.keys(): | |
229 | if len(grouped_pg_status.split(cls.PG_STATUS_SCRUBBING)) > 1 \ | |
230 | or len(grouped_pg_status.split(cls.PG_STATUS_DEEP_SCRUBBING)) > 1: | |
231 | return cls.SCRUB_STATUS_ACTIVE | |
232 | ||
233 | return cls.SCRUB_STATUS_INACTIVE | |
234 | ||
235 | @classmethod | |
236 | def get_pg_info(cls): | |
237 | pg_summary = mgr.get('pg_summary') | |
81eedcae TL |
238 | object_stats = {stat: pg_summary['pg_stats_sum']['stat_sum'][stat] for stat in [ |
239 | 'num_objects', 'num_object_copies', 'num_objects_degraded', | |
240 | 'num_objects_misplaced', 'num_objects_unfound']} | |
11fdf7f2 TL |
241 | |
242 | pgs_per_osd = 0.0 | |
243 | total_osds = len(pg_summary['by_osd']) | |
244 | if total_osds > 0: | |
245 | total_pgs = 0.0 | |
246 | for _, osd_pg_statuses in pg_summary['by_osd'].items(): | |
247 | for _, pg_amount in osd_pg_statuses.items(): | |
248 | total_pgs += pg_amount | |
249 | ||
250 | pgs_per_osd = total_pgs / total_osds | |
251 | ||
252 | return { | |
81eedcae | 253 | 'object_stats': object_stats, |
11fdf7f2 TL |
254 | 'statuses': pg_summary['all'], |
255 | 'pgs_per_osd': pgs_per_osd, | |
256 | } | |
257 | ||
258 | ||
494da23a TL |
259 | def get_rates_from_data(data): |
260 | """ | |
261 | >>> get_rates_from_data([]) | |
262 | [(0, 0.0)] | |
263 | >>> get_rates_from_data([[1, 42]]) | |
264 | [(1, 0.0)] | |
265 | >>> get_rates_from_data([[0, 100], [2, 101], [3, 100], [4, 100]]) | |
266 | [(2, 0.5), (3, 1.0), (4, 0.0)] | |
267 | """ | |
268 | if not data: | |
269 | return [(0, 0.0)] | |
270 | if len(data) == 1: | |
271 | return [(data[0][0], 0.0)] | |
272 | return [(data2[0], differentiate(data1, data2)) for data1, data2 in pairwise(data)] | |
273 | ||
274 | ||
11fdf7f2 TL |
275 | def differentiate(data1, data2): |
276 | """ | |
277 | >>> times = [0, 2] | |
278 | >>> values = [100, 101] | |
279 | >>> differentiate(*zip(times, values)) | |
280 | 0.5 | |
494da23a TL |
281 | >>> times = [0, 2] |
282 | >>> values = [100, 99] | |
283 | >>> differentiate(*zip(times, values)) | |
284 | 0.5 | |
11fdf7f2 | 285 | """ |
494da23a | 286 | return abs((data2[1] - data1[1]) / float(data2[0] - data1[0])) |