2 Automatically scale pg_num based on how much data is stored in each pool.
8 from typing
import Any
, Dict
, List
, Optional
, Set
, Tuple
, TYPE_CHECKING
, Union
10 from prettytable
import PrettyTable
11 from mgr_module
import HealthChecksT
, CLIReadCommand
, CLIWriteCommand
, CRUSHMap
, MgrModule
, Option
, OSDMap
14 Some terminology is made up for the purposes of this module:
16 - "raw pgs": pg count after applying replication, i.e. the real resource
17 consumption of a pool.
18 - "grow/shrink" - increase/decrease the pg_num in a pool
19 - "crush subtree" - non-overlapping domains in crush hierarchy: used as
20 units of resource management.
25 PG_NUM_MIN
= 32 # unless specified on a per-pool basis
29 if sys
.version_info
>= (3, 8):
30 from typing
import Literal
32 from typing_extensions
import Literal
34 PassT
= Literal
['first', 'second', 'third']
37 def nearest_power_of_two(n
: int) -> int:
47 # High bound power of two
50 # Low bound power of tow
53 return x
if (v
- n
) > (n
- x
) else v
56 def effective_target_ratio(target_ratio
: float,
57 total_target_ratio
: float,
58 total_target_bytes
: int,
59 capacity
: int) -> float:
61 Returns the target ratio after normalizing for ratios across pools and
62 adjusting for capacity reserved by pools that have target_size_bytes set.
64 target_ratio
= float(target_ratio
)
65 if total_target_ratio
:
66 target_ratio
= target_ratio
/ total_target_ratio
68 if total_target_bytes
and capacity
:
69 fraction_available
= 1.0 - min(1.0, float(total_target_bytes
) / capacity
)
70 target_ratio
*= fraction_available
75 class PgAdjustmentProgress(object):
77 Keeps the initial and target pg_num values
80 def __init__(self
, pool_id
: int, pg_num
: int, pg_num_target
: int) -> None:
81 self
.ev_id
= str(uuid
.uuid4())
82 self
.pool_id
= pool_id
83 self
.reset(pg_num
, pg_num_target
)
85 def reset(self
, pg_num
: int, pg_num_target
: int) -> None:
87 self
.pg_num_target
= pg_num_target
89 def update(self
, module
: MgrModule
, progress
: float) -> None:
90 desc
= 'increasing' if self
.pg_num
< self
.pg_num_target
else 'decreasing'
91 module
.remote('progress', 'update', self
.ev_id
,
92 ev_msg
="PG autoscaler %s pool %d PGs from %d to %d" %
93 (desc
, self
.pool_id
, self
.pg_num
, self
.pg_num_target
),
95 refs
=[("pool", self
.pool_id
)])
98 class CrushSubtreeResourceStatus
:
99 def __init__(self
) -> None:
100 self
.root_ids
: List
[int] = []
101 self
.osds
: Set
[int] = set()
102 self
.osd_count
: Optional
[int] = None # Number of OSDs
103 self
.pg_target
: Optional
[int] = None # Ideal full-capacity PG count?
104 self
.pg_current
= 0 # How many PGs already?
106 self
.capacity
: Optional
[int] = None # Total capacity of OSDs in subtree
107 self
.pool_ids
: List
[int] = []
108 self
.pool_names
: List
[str] = []
109 self
.pool_count
: Optional
[int] = None
111 self
.total_target_ratio
= 0.0
112 self
.total_target_bytes
= 0 # including replication / EC overhead
115 class PgAutoscaler(MgrModule
):
120 'mon_target_pg_per_osd',
121 'mon_max_pg_per_osd',
126 name
='sleep_interval',
133 desc
='scaling threshold',
134 long_desc
=('The factor by which the `NEW PG_NUM` must vary from the current'
135 '`PG_NUM` before being accepted. Cannot be less than 1.0'),
141 desc
='global autoscale flag',
142 long_desc
=('Option to turn on/off the autoscaler for all pools'),
146 def __init__(self
, *args
: Any
, **kwargs
: Any
) -> None:
147 super(PgAutoscaler
, self
).__init
__(*args
, **kwargs
)
148 self
._shutdown
= threading
.Event()
149 self
._event
: Dict
[int, PgAdjustmentProgress
] = {}
151 # So much of what we do peeks at the osdmap that it's easiest
152 # to just keep a copy of the pythonized version.
155 self
.sleep_interval
= 60
156 self
.mon_target_pg_per_osd
= 0
158 self
.noautoscale
= False
160 def config_notify(self
) -> None:
161 for opt
in self
.NATIVE_OPTIONS
:
164 self
.get_ceph_option(opt
))
165 self
.log
.debug(' native option %s = %s', opt
, getattr(self
, opt
))
166 for opt
in self
.MODULE_OPTIONS
:
169 self
.get_module_option(opt
['name']))
170 self
.log
.debug(' mgr option %s = %s',
171 opt
['name'], getattr(self
, opt
['name']))
173 @CLIReadCommand('osd pool autoscale-status')
174 def _command_autoscale_status(self
, format
: str = 'plain') -> Tuple
[int, str, str]:
176 report on pool pg_num sizing recommendation and intent
178 osdmap
= self
.get_osdmap()
179 pools
= osdmap
.get_pools_by_name()
180 ps
, root_map
= self
._get
_pool
_status
(osdmap
, pools
)
182 if format
in ('json', 'json-pretty'):
183 return 0, json
.dumps(ps
, indent
=4, sort_keys
=True), ''
185 table
= PrettyTable(['POOL', 'SIZE', 'TARGET SIZE',
186 'RATE', 'RAW CAPACITY',
187 'RATIO', 'TARGET RATIO',
192 'NEW PG_NUM', 'AUTOSCALE',
195 table
.left_padding_width
= 0
196 table
.right_padding_width
= 2
197 table
.align
['POOL'] = 'l'
198 table
.align
['SIZE'] = 'r'
199 table
.align
['TARGET SIZE'] = 'r'
200 table
.align
['RATE'] = 'r'
201 table
.align
['RAW CAPACITY'] = 'r'
202 table
.align
['RATIO'] = 'r'
203 table
.align
['TARGET RATIO'] = 'r'
204 table
.align
['EFFECTIVE RATIO'] = 'r'
205 table
.align
['BIAS'] = 'r'
206 table
.align
['PG_NUM'] = 'r'
207 # table.align['IDEAL'] = 'r'
208 table
.align
['NEW PG_NUM'] = 'r'
209 table
.align
['AUTOSCALE'] = 'l'
210 table
.align
['BULK'] = 'l'
212 if p
['would_adjust']:
213 final
= str(p
['pg_num_final'])
216 if p
['target_bytes'] > 0:
217 ts
= mgr_util
.format_bytes(p
['target_bytes'], 6)
220 if p
['target_ratio'] > 0.0:
221 tr
= '%.4f' % p
['target_ratio']
224 if p
['effective_target_ratio'] > 0.0:
225 etr
= '%.4f' % p
['effective_target_ratio']
230 mgr_util
.format_bytes(p
['logical_used'], 6),
233 mgr_util
.format_bytes(p
['subtree_capacity'], 6),
234 '%.4f' % p
['capacity_ratio'],
241 p
['pg_autoscale_mode'],
244 return 0, table
.get_string(), ''
246 @CLIWriteCommand("osd pool set threshold")
247 def set_scaling_threshold(self
, num
: float) -> Tuple
[int, str, str]:
249 set the autoscaler threshold
250 A.K.A. the factor by which the new PG_NUM must vary from the existing PG_NUM
253 return 22, "", "threshold cannot be set less than 1.0"
254 self
.set_module_option("threshold", num
)
255 return 0, "threshold updated", ""
257 def complete_all_progress_events(self
) -> None:
258 for pool_id
in list(self
._event
):
259 ev
= self
._event
[pool_id
]
260 self
.remote('progress', 'complete', ev
.ev_id
)
261 del self
._event
[pool_id
]
263 def set_autoscale_mode_all_pools(self
, status
: str) -> None:
264 osdmap
= self
.get_osdmap()
265 pools
= osdmap
.get_pools_by_name()
266 for pool_name
, _
in pools
.items():
268 'prefix': 'osd pool set',
270 'var': 'pg_autoscale_mode',
273 @CLIWriteCommand("osd pool get noautoscale")
274 def get_noautoscale(self
) -> Tuple
[int, str, str]:
276 Get the noautoscale flag to see if all pools
277 are setting the autoscaler on or off as well
278 as newly created pools in the future.
281 if self
.noautoscale
== None:
282 raise TypeError("noautoscale cannot be None")
283 elif self
.noautoscale
:
284 return 0, "", "noautoscale is on"
286 return 0, "", "noautoscale is off"
288 @CLIWriteCommand("osd pool unset noautoscale")
289 def unset_noautoscale(self
) -> Tuple
[int, str, str]:
291 Unset the noautoscale flag so all pools will
292 have autoscale enabled (including newly created
293 pools in the future).
295 if not self
.noautoscale
:
296 return 0, "", "noautoscale is already unset!"
298 self
.set_module_option("noautoscale", False)
300 'prefix': 'config set',
302 'name': 'osd_pool_default_pg_autoscale_mode',
305 self
.set_autoscale_mode_all_pools("on")
306 return 0, "", "noautoscale is unset, all pools now have autoscale on"
308 @CLIWriteCommand("osd pool set noautoscale")
309 def set_noautoscale(self
) -> Tuple
[int, str, str]:
311 set the noautoscale for all pools (including
312 newly created pools in the future)
313 and complete all on-going progress events
314 regarding PG-autoscaling.
317 return 0, "", "noautoscale is already set!"
319 self
.set_module_option("noautoscale", True)
321 'prefix': 'config set',
323 'name': 'osd_pool_default_pg_autoscale_mode',
326 self
.set_autoscale_mode_all_pools("off")
327 self
.complete_all_progress_events()
328 return 0, "", "noautoscale is set, all pools now have autoscale off"
330 def serve(self
) -> None:
332 while not self
._shutdown
.is_set():
334 self
._update
_progress
_events
()
335 self
._shutdown
.wait(timeout
=self
.sleep_interval
)
337 def shutdown(self
) -> None:
338 self
.log
.info('Stopping pg_autoscaler')
341 def identify_subtrees_and_overlaps(self
,
344 result
: Dict
[int, CrushSubtreeResourceStatus
],
345 overlapped_roots
: Set
[int],
346 roots
: List
[CrushSubtreeResourceStatus
]) -> \
347 Tuple
[List
[CrushSubtreeResourceStatus
],
350 # We identify subtrees and overlapping roots from osdmap
351 for pool_id
, pool
in osdmap
.get_pools().items():
352 crush_rule
= crush
.get_rule_by_id(pool
['crush_rule'])
353 assert crush_rule
is not None
354 cr_name
= crush_rule
['rule_name']
355 root_id
= crush
.get_rule_root(cr_name
)
356 assert root_id
is not None
357 osds
= set(crush
.get_osds_under(root_id
))
359 # Are there overlapping roots?
361 for prev_root_id
, prev
in result
.items():
364 if prev_root_id
!= root_id
:
365 overlapped_roots
.add(prev_root_id
)
366 overlapped_roots
.add(root_id
)
367 self
.log
.warning("pool %s won't scale due to overlapping roots: %s",
368 pool
['pool_name'], overlapped_roots
)
369 self
.log
.warning("Please See: https://docs.ceph.com/en/"
370 "latest/rados/operations/placement-groups"
371 "/#automated-scaling")
374 s
= CrushSubtreeResourceStatus()
377 s
.root_ids
.append(root_id
)
379 s
.pool_ids
.append(pool_id
)
380 s
.pool_names
.append(pool
['pool_name'])
381 s
.pg_current
+= pool
['pg_num_target'] * pool
['size']
382 target_ratio
= pool
['options'].get('target_size_ratio', 0.0)
384 s
.total_target_ratio
+= target_ratio
386 target_bytes
= pool
['options'].get('target_size_bytes', 0)
388 s
.total_target_bytes
+= target_bytes
* osdmap
.pool_raw_used_rate(pool_id
)
389 return roots
, overlapped_roots
391 def get_subtree_resource_status(self
,
393 crush
: CRUSHMap
) -> Tuple
[Dict
[int, CrushSubtreeResourceStatus
],
396 For each CRUSH subtree of interest (i.e. the roots under which
397 we have pools), calculate the current resource usages and targets,
398 such as how many PGs there are, vs. how many PGs we would
401 result
: Dict
[int, CrushSubtreeResourceStatus
] = {}
402 roots
: List
[CrushSubtreeResourceStatus
] = []
403 overlapped_roots
: Set
[int] = set()
404 # identify subtrees and overlapping roots
405 roots
, overlapped_roots
= self
.identify_subtrees_and_overlaps(osdmap
,
406 crush
, result
, overlapped_roots
, roots
)
408 all_stats
= self
.get('osd_stats')
410 assert s
.osds
is not None
411 s
.osd_count
= len(s
.osds
)
412 s
.pg_target
= s
.osd_count
* self
.mon_target_pg_per_osd
413 s
.pg_left
= s
.pg_target
414 s
.pool_count
= len(s
.pool_ids
)
416 for osd_stats
in all_stats
['osd_stats']:
417 if osd_stats
['osd'] in s
.osds
:
418 # Intentionally do not apply the OSD's reweight to
419 # this, because we want to calculate PG counts based
420 # on the physical storage available, not how it is
421 # reweighted right now.
422 capacity
+= osd_stats
['kb'] * 1024
424 s
.capacity
= capacity
425 self
.log
.debug('root_ids %s pools %s with %d osds, pg_target %d',
431 return result
, overlapped_roots
433 def _calc_final_pg_target(
437 root_map
: Dict
[int, CrushSubtreeResourceStatus
],
439 capacity_ratio
: float,
441 even_pools
: Dict
[str, Dict
[str, Any
]],
442 bulk_pools
: Dict
[str, Dict
[str, Any
]],
445 ) -> Union
[Tuple
[float, int, int], Tuple
[None, None, None]]:
447 `profile` determines behaviour of the autoscaler.
448 `first_pass` flag used to determine if this is the first
449 pass where the caller tries to calculate/adjust pools that has
450 used_ratio > even_ratio else this is the second pass,
451 we calculate final_ratio by giving it 1 / pool_count
452 of the root we are currently looking at.
454 if func_pass
== 'first':
455 # first pass to deal with small pools (no bulk flag)
456 # calculating final_pg_target based on capacity ratio
457 # we also keep track of bulk_pools to be used in second pass
459 final_ratio
= capacity_ratio
460 pg_left
= root_map
[root_id
].pg_left
461 assert pg_left
is not None
462 used_pg
= final_ratio
* pg_left
463 root_map
[root_id
].pg_left
-= int(used_pg
)
464 root_map
[root_id
].pool_used
+= 1
465 pool_pg_target
= used_pg
/ p
['size'] * bias
467 bulk_pools
[pool_name
] = p
468 return None, None, None
470 elif func_pass
== 'second':
471 # second pass we calculate the final_pg_target
472 # for pools that have used_ratio > even_ratio
473 # and we keep track of even pools to be used in third pass
474 pool_count
= root_map
[root_id
].pool_count
475 assert pool_count
is not None
476 even_ratio
= 1 / (pool_count
- root_map
[root_id
].pool_used
)
477 used_ratio
= capacity_ratio
479 if used_ratio
> even_ratio
:
480 root_map
[root_id
].pool_used
+= 1
482 even_pools
[pool_name
] = p
483 return None, None, None
485 final_ratio
= max(used_ratio
, even_ratio
)
486 pg_left
= root_map
[root_id
].pg_left
487 assert pg_left
is not None
488 used_pg
= final_ratio
* pg_left
489 root_map
[root_id
].pg_left
-= int(used_pg
)
490 pool_pg_target
= used_pg
/ p
['size'] * bias
493 # third pass we just split the pg_left to all even_pools
494 pool_count
= root_map
[root_id
].pool_count
495 assert pool_count
is not None
496 final_ratio
= 1 / (pool_count
- root_map
[root_id
].pool_used
)
497 pool_pg_target
= (final_ratio
* root_map
[root_id
].pg_left
) / p
['size'] * bias
499 min_pg
= p
.get('options', {}).get('pg_num_min', PG_NUM_MIN
)
500 max_pg
= p
.get('options', {}).get('pg_num_max')
501 final_pg_target
= max(min_pg
, nearest_power_of_two(pool_pg_target
))
502 if max_pg
and max_pg
< final_pg_target
:
503 final_pg_target
= max_pg
504 self
.log
.info("Pool '{0}' root_id {1} using {2} of space, bias {3}, "
505 "pg target {4} quantized to {5} (current {6})".format(
514 return final_ratio
, pool_pg_target
, final_pg_target
516 def _get_pool_pg_targets(
519 pools
: Dict
[str, Dict
[str, Any
]],
521 root_map
: Dict
[int, CrushSubtreeResourceStatus
],
522 pool_stats
: Dict
[int, Dict
[str, int]],
523 ret
: List
[Dict
[str, Any
]],
526 overlapped_roots
: Set
[int],
527 ) -> Tuple
[List
[Dict
[str, Any
]], Dict
[str, Dict
[str, Any
]] , Dict
[str, Dict
[str, Any
]]]:
529 Calculates final_pg_target of each pools and determine if it needs
530 scaling, this depends on the profile of the autoscaler. For scale-down,
531 we start out with a full complement of pgs and only descrease it when other
532 pools needs more pgs due to increased usage. For scale-up, we start out with
533 the minimal amount of pgs and only scale when there is increase in usage.
535 even_pools
: Dict
[str, Dict
[str, Any
]] = {}
536 bulk_pools
: Dict
[str, Dict
[str, Any
]] = {}
537 for pool_name
, p
in pools
.items():
539 if pool_id
not in pool_stats
:
540 # race with pool deletion; skip
543 # FIXME: we assume there is only one take per pool, but that
545 crush_rule
= crush_map
.get_rule_by_id(p
['crush_rule'])
546 assert crush_rule
is not None
547 cr_name
= crush_rule
['rule_name']
548 root_id
= crush_map
.get_rule_root(cr_name
)
549 assert root_id
is not None
550 if root_id
in overlapped_roots
:
552 # with overlapping roots
553 self
.log
.warn("pool %d contains an overlapping root %d"
554 "... skipping scaling", pool_id
, root_id
)
556 capacity
= root_map
[root_id
].capacity
557 assert capacity
is not None
559 self
.log
.debug('skipping empty subtree %s', cr_name
)
562 raw_used_rate
= osdmap
.pool_raw_used_rate(pool_id
)
564 pool_logical_used
= pool_stats
[pool_id
]['stored']
565 bias
= p
['options'].get('pg_autoscale_bias', 1.0)
567 # ratio takes precedence if both are set
568 if p
['options'].get('target_size_ratio', 0.0) == 0.0:
569 target_bytes
= p
['options'].get('target_size_bytes', 0)
571 # What proportion of space are we using?
572 actual_raw_used
= pool_logical_used
* raw_used_rate
573 actual_capacity_ratio
= float(actual_raw_used
) / capacity
575 pool_raw_used
= max(pool_logical_used
, target_bytes
) * raw_used_rate
576 capacity_ratio
= float(pool_raw_used
) / capacity
578 self
.log
.info("effective_target_ratio {0} {1} {2} {3}".format(
579 p
['options'].get('target_size_ratio', 0.0),
580 root_map
[root_id
].total_target_ratio
,
581 root_map
[root_id
].total_target_bytes
,
584 target_ratio
= effective_target_ratio(p
['options'].get('target_size_ratio', 0.0),
585 root_map
[root_id
].total_target_ratio
,
586 root_map
[root_id
].total_target_bytes
,
589 # determine if the pool is a bulk
591 flags
= p
['flags_names'].split(",")
595 capacity_ratio
= max(capacity_ratio
, target_ratio
)
596 final_ratio
, pool_pg_target
, final_pg_target
= self
._calc
_final
_pg
_target
(
597 p
, pool_name
, root_map
, root_id
,
598 capacity_ratio
, bias
, even_pools
,
599 bulk_pools
, func_pass
, bulk
)
601 if final_ratio
is None:
605 if (final_pg_target
> p
['pg_num_target'] * threshold
or
606 final_pg_target
< p
['pg_num_target'] / threshold
) and \
607 final_ratio
>= 0.0 and \
611 assert pool_pg_target
is not None
614 'pool_name': p
['pool_name'],
615 'crush_root_id': root_id
,
616 'pg_autoscale_mode': p
['pg_autoscale_mode'],
617 'pg_num_target': p
['pg_num_target'],
618 'logical_used': pool_logical_used
,
619 'target_bytes': target_bytes
,
620 'raw_used_rate': raw_used_rate
,
621 'subtree_capacity': capacity
,
622 'actual_raw_used': actual_raw_used
,
623 'raw_used': pool_raw_used
,
624 'actual_capacity_ratio': actual_capacity_ratio
,
625 'capacity_ratio': capacity_ratio
,
626 'target_ratio': p
['options'].get('target_size_ratio', 0.0),
627 'effective_target_ratio': target_ratio
,
628 'pg_num_ideal': int(pool_pg_target
),
629 'pg_num_final': final_pg_target
,
630 'would_adjust': adjust
,
631 'bias': p
.get('options', {}).get('pg_autoscale_bias', 1.0),
635 return ret
, bulk_pools
, even_pools
637 def _get_pool_status(
640 pools
: Dict
[str, Dict
[str, Any
]],
641 ) -> Tuple
[List
[Dict
[str, Any
]],
642 Dict
[int, CrushSubtreeResourceStatus
]]:
643 threshold
= self
.threshold
644 assert threshold
>= 1.0
646 crush_map
= osdmap
.get_crush()
647 root_map
, overlapped_roots
= self
.get_subtree_resource_status(osdmap
, crush_map
)
649 pool_stats
= dict([(p
['id'], p
['stats']) for p
in df
['pools']])
651 ret
: List
[Dict
[str, Any
]] = []
653 # Iterate over all pools to determine how they should be sized.
654 # First call of _get_pool_pg_targets() is to find/adjust pools that uses more capacaity than
655 # the even_ratio of other pools and we adjust those first.
656 # Second call make use of the even_pools we keep track of in the first call.
657 # All we need to do is iterate over those and give them 1/pool_count of the
660 ret
, bulk_pools
, _
= self
._get
_pool
_pg
_targets
(osdmap
, pools
, crush_map
, root_map
,
661 pool_stats
, ret
, threshold
, 'first', overlapped_roots
)
663 ret
, _
, even_pools
= self
._get
_pool
_pg
_targets
(osdmap
, bulk_pools
, crush_map
, root_map
,
664 pool_stats
, ret
, threshold
, 'second', overlapped_roots
)
666 ret
, _
, _
= self
._get
_pool
_pg
_targets
(osdmap
, even_pools
, crush_map
, root_map
,
667 pool_stats
, ret
, threshold
, 'third', overlapped_roots
)
669 return (ret
, root_map
)
671 def _update_progress_events(self
) -> None:
674 osdmap
= self
.get_osdmap()
675 pools
= osdmap
.get_pools()
676 for pool_id
in list(self
._event
):
677 ev
= self
._event
[pool_id
]
678 pool_data
= pools
.get(pool_id
)
679 if pool_data
is None or pool_data
['pg_num'] == pool_data
['pg_num_target'] or ev
.pg_num
== ev
.pg_num_target
:
680 # pool is gone or we've reached our target
681 self
.remote('progress', 'complete', ev
.ev_id
)
682 del self
._event
[pool_id
]
684 ev
.update(self
, (ev
.pg_num
- pool_data
['pg_num']) / (ev
.pg_num
- ev
.pg_num_target
))
686 def _maybe_adjust(self
) -> None:
689 self
.log
.info('_maybe_adjust')
690 osdmap
= self
.get_osdmap()
691 if osdmap
.get_require_osd_release() < 'nautilus':
693 pools
= osdmap
.get_pools_by_name()
694 self
.log
.debug("pool: {0}".format(json
.dumps(pools
, indent
=4,
696 ps
, root_map
= self
._get
_pool
_status
(osdmap
, pools
)
698 # Anyone in 'warn', set the health message for them and then
699 # drop them from consideration.
703 health_checks
: Dict
[str, Dict
[str, Union
[int, str, List
[str]]]] = {}
705 total_bytes
= dict([(r
, 0) for r
in iter(root_map
)])
706 total_target_bytes
= dict([(r
, 0.0) for r
in iter(root_map
)])
707 target_bytes_pools
: Dict
[int, List
[int]] = dict([(r
, []) for r
in iter(root_map
)])
710 pool_id
= p
['pool_id']
711 pool_opts
= pools
[p
['pool_name']]['options']
712 if pool_opts
.get('target_size_ratio', 0) > 0 and pool_opts
.get('target_size_bytes', 0) > 0:
713 bytes_and_ratio
.append(
714 'Pool %s has target_size_bytes and target_size_ratio set' % p
['pool_name'])
715 total_bytes
[p
['crush_root_id']] += max(
716 p
['actual_raw_used'],
717 p
['target_bytes'] * p
['raw_used_rate'])
718 if p
['target_bytes'] > 0:
719 total_target_bytes
[p
['crush_root_id']] += p
['target_bytes'] * p
['raw_used_rate']
720 target_bytes_pools
[p
['crush_root_id']].append(p
['pool_name'])
721 if not p
['would_adjust']:
723 if p
['pg_autoscale_mode'] == 'warn':
724 msg
= 'Pool %s has %d placement groups, should have %d' % (
728 if p
['pg_num_final'] > p
['pg_num_target']:
733 if p
['pg_autoscale_mode'] == 'on':
734 # Note that setting pg_num actually sets pg_num_target (see
736 r
= self
.mon_command({
737 'prefix': 'osd pool set',
738 'pool': p
['pool_name'],
740 'val': str(p
['pg_num_final'])
743 # create new event or update existing one to reflect
744 # progress from current state to the new pg_num_target
745 pool_data
= pools
[p
['pool_name']]
746 pg_num
= pool_data
['pg_num']
747 new_target
= p
['pg_num_final']
748 if pool_id
in self
._event
:
749 self
._event
[pool_id
].reset(pg_num
, new_target
)
751 self
._event
[pool_id
] = PgAdjustmentProgress(pool_id
, pg_num
, new_target
)
752 self
._event
[pool_id
].update(self
, 0.0)
755 # FIXME: this is a serious and unexpected thing,
756 # we should expose it as a cluster log error once
757 # the hook for doing that from ceph-mgr modules is
759 self
.log
.error("pg_num adjustment on {0} to {1} failed: {2}"
760 .format(p
['pool_name'],
761 p
['pg_num_final'], r
))
764 summary
= "{0} pools have too few placement groups".format(
766 health_checks
['POOL_TOO_FEW_PGS'] = {
767 'severity': 'warning',
769 'count': len(too_few
),
773 summary
= "{0} pools have too many placement groups".format(
775 health_checks
['POOL_TOO_MANY_PGS'] = {
776 'severity': 'warning',
778 'count': len(too_many
),
782 too_much_target_bytes
= []
783 for root_id
, total
in total_bytes
.items():
784 total_target
= int(total_target_bytes
[root_id
])
785 capacity
= root_map
[root_id
].capacity
786 assert capacity
is not None
787 if total_target
> 0 and total
> capacity
and capacity
:
788 too_much_target_bytes
.append(
789 'Pools %s overcommit available storage by %.03fx due to '
790 'target_size_bytes %s on pools %s' % (
791 root_map
[root_id
].pool_names
,
793 mgr_util
.format_bytes(total_target
, 5, colored
=False),
794 target_bytes_pools
[root_id
]
797 elif total_target
> capacity
and capacity
:
798 too_much_target_bytes
.append(
799 'Pools %s overcommit available storage by %.03fx due to '
800 'collective target_size_bytes of %s' % (
801 root_map
[root_id
].pool_names
,
803 mgr_util
.format_bytes(total_target
, 5, colored
=False),
806 if too_much_target_bytes
:
807 health_checks
['POOL_TARGET_SIZE_BYTES_OVERCOMMITTED'] = {
808 'severity': 'warning',
809 'summary': "%d subtrees have overcommitted pool target_size_bytes" % len(too_much_target_bytes
),
810 'count': len(too_much_target_bytes
),
811 'detail': too_much_target_bytes
,
815 health_checks
['POOL_HAS_TARGET_SIZE_BYTES_AND_RATIO'] = {
816 'severity': 'warning',
817 'summary': "%d pools have both target_size_bytes and target_size_ratio set" % len(bytes_and_ratio
),
818 'count': len(bytes_and_ratio
),
819 'detail': bytes_and_ratio
,
822 self
.set_health_checks(health_checks
)