]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/services/ceph_service.py
import 14.2.4 nautilus point release
[ceph.git] / ceph / src / pybind / mgr / dashboard / services / ceph_service.py
CommitLineData
11fdf7f2
TL
1# -*- coding: utf-8 -*-
2from __future__ import absolute_import
3
4import json
5
6import rados
7
8from mgr_module import CommandResult
9
10try:
11 from more_itertools import pairwise
12except 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
19from .. import logger, mgr
20
81eedcae
TL
21try:
22 from typing import Dict, Any # pylint: disable=unused-import
23except ImportError:
24 pass # For typing only
25
11fdf7f2
TL
26
27class 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
34class 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
259def 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
275def 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]))