self.osd_count = None # Number of OSDs
self.pg_target = None # Ideal full-capacity PG count?
self.pg_current = 0 # How many PGs already?
+ self.pg_left = 0
self.capacity = None # Total capacity of OSDs in subtree
self.pool_ids = []
self.pool_names = []
+ self.pool_count = None
+ self.pool_used = 0
self.total_target_ratio = 0.0
self.total_target_bytes = 0 # including replication / EC overhead
for s in roots:
s.osd_count = len(s.osds)
s.pg_target = s.osd_count * self.mon_target_pg_per_osd
-
+ s.pg_left = s.pg_target
+ s.pool_count = len(s.pool_ids)
capacity = 0.0
for osd_stats in all_stats['osd_stats']:
if osd_stats['osd'] in s.osds:
return result, pool_root
- def _get_pool_status(
- self,
- osdmap,
- pools,
- threshold=3.0,
+ def _calc_final_pg_target(
+ self,
+ p,
+ pool_name,
+ root_map,
+ root_id,
+ capacity_ratio,
+ even_pools,
+ bias,
+ is_used,
):
- assert threshold >= 2.0
-
- crush_map = osdmap.get_crush()
-
- root_map, pool_root = self.get_subtree_resource_status(osdmap, crush_map)
+ """
+ is_used flag used to determine if this is the first
+ pass where the caller tries to calculate/adjust pools that has
+ used_ratio > even_ratio else this is the second pass,
+ we calculate final_ratio by giving it 1 / pool_count
+ of the root we are looking.
+ """
+ if is_used:
+ even_ratio = 1 / root_map[root_id].pool_count
+ used_ratio = capacity_ratio
- df = self.get('df')
- pool_stats = dict([(p['id'], p['stats']) for p in df['pools']])
+ if used_ratio > even_ratio:
+ root_map[root_id].pool_used += 1
+ else:
+ # keep track of even_pools to be used in second pass
+ # of the caller function
+ even_pools[pool_name] = p
+ return None, None, None
- ret = []
+ final_ratio = max(used_ratio, even_ratio)
+ used_pg = final_ratio * root_map[root_id].pg_target
+ root_map[root_id].pg_left -= used_pg
+ pool_pg_target = used_pg / p['size'] * bias
- # iterate over all pools to determine how they should be sized
+ else:
+ final_ratio = 1 / (root_map[root_id].pool_count - root_map[root_id].pool_used)
+ pool_pg_target = (final_ratio * root_map[root_id].pg_left) / p['size'] * bias
+
+ final_pg_target = max(p.get('options', {}).get('pg_num_min', PG_NUM_MIN),
+ nearest_power_of_two(pool_pg_target))
+
+ self.log.info("Pool '{0}' root_id {1} using {2} of space, bias {3}, "
+ "pg target {4} quantized to {5} (current {6})".format(
+ p['pool_name'],
+ root_id,
+ capacity_ratio,
+ bias,
+ pool_pg_target,
+ final_pg_target,
+ p['pg_num_target']
+ ))
+
+ return final_ratio, pool_pg_target, final_pg_target
+
+ def _calc_pool_targets(
+ self,
+ osdmap,
+ pools,
+ crush_map,
+ root_map,
+ pool_root,
+ pool_stats,
+ ret,
+ threshold,
+ is_used,
+ ):
+ """
+ Calculates final_pg_target of each pools and determine if it needs
+ scaling by starting out with a full complement of pgs and only
+ descreasing it when other pools need more due to increased usage.
+ """
+ even_pools = {}
for pool_name, p in pools.items():
pool_id = p['pool']
if pool_id not in pool_stats:
# may not be true.
cr_name = crush_map.get_rule_by_id(p['crush_rule'])['rule_name']
root_id = int(crush_map.get_rule_root(cr_name))
+
pool_root[pool_name] = root_id
capacity = root_map[root_id].capacity
root_map[root_id].total_target_ratio,
root_map[root_id].total_target_bytes,
capacity))
+
target_ratio = effective_target_ratio(p['options'].get('target_size_ratio', 0.0),
root_map[root_id].total_target_ratio,
root_map[root_id].total_target_bytes,
capacity)
- final_ratio = max(capacity_ratio, target_ratio)
+ capacity_ratio = max(capacity_ratio, target_ratio)
+ final_ratio, pool_pg_target, final_pg_target = self._calc_final_pg_target(p,
+ pool_name, root_map, root_id, capacity_ratio, even_pools, bias, is_used)
- # So what proportion of pg allowance should we be using?
- pool_pg_target = (final_ratio * root_map[root_id].pg_target) / p['size'] * bias
-
- final_pg_target = max(p['options'].get('pg_num_min', PG_NUM_MIN),
- nearest_power_of_two(pool_pg_target))
-
- self.log.info("Pool '{0}' root_id {1} using {2} of space, bias {3}, "
- "pg target {4} quantized to {5} (current {6})".format(
- p['pool_name'],
- root_id,
- final_ratio,
- bias,
- pool_pg_target,
- final_pg_target,
- p['pg_num_target']
- ))
+ if final_ratio == None:
+ continue
adjust = False
if (final_pg_target > p['pg_num_target'] * threshold or \
'bias': p.get('options', {}).get('pg_autoscale_bias', 1.0),
});
+ return ret, even_pools
+
+
+ def _get_pool_status(
+ self,
+ osdmap,
+ pools,
+ threshold=3.0,
+ ):
+ assert threshold >= 2.0
+
+ crush_map = osdmap.get_crush()
+ root_map, pool_root = self.get_subtree_resource_status(osdmap, crush_map)
+ df = self.get('df')
+ pool_stats = dict([(p['id'], p['stats']) for p in df['pools']])
+
+ ret = []
+ # Iterate over all pools to determine how they should be sized.
+ # First call is to find/adjust pools that uses more capacaity than
+ # the even_ratio of other pools and we adjust those first.
+ # Second call make use of the even_pools we keep track of in the first call.
+ # All we need to do is iterate over those and give them 1/pool_count of the
+ # total pgs.
+ ret, even_pools = self._calc_pool_targets(osdmap, pools, crush_map, root_map, pool_root,
+ pool_stats, ret, threshold, True)
+
+ ret, _ = self._calc_pool_targets(osdmap, even_pools, crush_map, root_map, pool_root,
+ pool_stats, ret, threshold, False)
+
return (ret, root_map, pool_root)
def _update_progress_events(self):
--- /dev/null
+#python unit test
+import unittest
+from tests import mock
+import pytest
+import json
+from pg_autoscaler import module
+
+class RootMapItem:
+
+ def __init__(self, pool_count, pg_target, pg_left):
+
+ self.pool_count = pool_count
+ self.pg_target = pg_target
+ self.pg_left = pg_left
+ self.pool_used = 0
+
+class TestPgAutoscaler(object):
+
+ def setup(self):
+ # a bunch of attributes for testing
+ self.autoscaler = module.PgAutoscaler('module_name', 0, 0)
+
+ def helper_test(self, pools, root_map, bias):
+
+ even_pools = {}
+ for pool_name, p in pools.items():
+ final_ratio, pool_pg_target, final_pg_target = self.autoscaler._calc_final_pg_target(p, pool_name, root_map, p['root_id'], p['capacity_ratio'], even_pools, bias, True)
+
+ if final_ratio == None:
+ continue
+
+ assert p['expected_final_pg_target'] == final_pg_target
+ assert p['expected_final_ratio'] == final_ratio
+ assert not p['even_pools'] and pool_name not in even_pools
+
+ for pool_name, p in even_pools.items():
+ final_ratio, pool_pg_target, final_pg_target = self.autoscaler._calc_final_pg_target(p, pool_name, root_map, p['root_id'], p['capacity_ratio'], even_pools, bias, False)
+
+ assert p['expected_final_pg_target'] == final_pg_target
+ assert p['expected_final_ratio'] == final_ratio
+ assert p['even_pools'] and pool_name in even_pools
+
+ def test_all_even_pools(self):
+ pools = {
+
+ "test0":{
+
+ "pool": 0,
+ "pool_name": "test0",
+ "pg_num_target": 32,
+ "capacity_ratio": 0.2,
+ "root_id":"0",
+ "expected_final_pg_target": 128,
+ "expected_final_ratio": 0.25,
+ "even_pools": True,
+ "size": 1,
+ },
+
+ "test1":{
+
+ "pool": 1,
+ "pool_name": "test1",
+ "pg_num_target": 32,
+ "capacity_ratio": 0.2,
+ "root_id":"0",
+ "expected_final_pg_target": 128,
+ "expected_final_ratio": 0.25,
+ "even_pools": True,
+ "size": 1,
+ },
+
+ "test2":{
+
+ "pool": 2,
+ "pool_name": "test2",
+ "pg_num_target": 32,
+ "capacity_ratio": 0.2,
+ "root_id":"0",
+ "expected_final_pg_target": 128,
+ "expected_final_ratio": 0.25,
+ "even_pools": True,
+ "size": 1,
+ },
+
+ "test3":{
+
+ "pool": 3,
+ "pool_name": "test3",
+ "pg_num_target": 32,
+ "capacity_ratio": 0.1,
+ "root_id": "0",
+ "expected_final_pg_target": 128,
+ "expected_final_ratio": 0.25,
+ "even_pools": True,
+ "size": 1,
+ },
+
+ }
+
+ root_map = {
+
+ "0": RootMapItem(4, 400, 400),
+ "1": RootMapItem(4, 400, 400),
+
+ }
+
+ bias = 1
+ self.helper_test(pools, root_map, bias)
+
+ def test_uneven_pools(self):
+ pools = {
+
+ "test0":{
+
+ "pool": 0,
+ "pool_name": "test0",
+ "pg_num_target": 32,
+ "capacity_ratio": 0.1,
+ "root_id":"0",
+ "expected_final_pg_target": 64,
+ "expected_final_ratio": 1/3,
+ "even_pools": True,
+ "size": 1,
+ },
+
+ "test1":{
+
+ "pool": 1,
+ "pool_name": "test1",
+ "pg_num_target": 32,
+ "capacity_ratio": 0.5,
+ "root_id":"0",
+ "expected_final_pg_target": 256,
+ "expected_final_ratio": 0.5,
+ "even_pools": False,
+ "size": 1,
+ },
+
+ "test2":{
+
+ "pool": 2,
+ "pool_name": "test2",
+ "pg_num_target": 32,
+ "capacity_ratio": 0.1,
+ "root_id":"0",
+ "expected_final_pg_target": 64,
+ "expected_final_ratio": 1/3,
+ "even_pools": True,
+ "size": 1,
+ },
+
+ "test3":{
+
+ "pool": 3,
+ "pool_name": "test3",
+ "pg_num_target": 32,
+ "capacity_ratio": 0.1,
+ "root_id": "0",
+ "expected_final_pg_target": 64,
+ "expected_final_ratio": 1/3,
+ "even_pools": True,
+ "size": 1,
+ },
+
+ }
+
+ root_map = {
+
+ "0": RootMapItem(4, 400, 400),
+ "1": RootMapItem(4, 400, 400),
+
+ }
+
+ bias = 1
+ self.helper_test(pools, root_map, bias)
+
+ def test_uneven_pools_with_diff_roots(self):
+ pools = {
+
+ "test0":{
+
+ "pool": 0,
+ "pool_name": "test0",
+ "pg_num_target": 32,
+ "capacity_ratio": 0.4,
+ "root_id":"0",
+ "expected_final_pg_target": 2048,
+ "expected_final_ratio": 0.4,
+ "even_pools": False,
+ "size": 1,
+ },
+
+ "test1":{
+
+ "pool": 1,
+ "pool_name": "test1",
+ "pg_num_target": 32,
+ "capacity_ratio": 0.6,
+ "root_id":"1",
+ "expected_final_pg_target": 2048,
+ "expected_final_ratio": 0.6,
+ "even_pools": False,
+ "size": 1,
+ },
+
+ "test2":{
+
+ "pool": 2,
+ "pool_name": "test2",
+ "pg_num_target": 32,
+ "capacity_ratio": 0.5,
+ "root_id":"0",
+ "expected_final_pg_target": 2048,
+ "expected_final_ratio": 0.5,
+ "even_pools": False,
+ "size": 1,
+ },
+
+ "test3":{
+
+ "pool": 3,
+ "pool_name": "test3",
+ "pg_num_target": 32,
+ "capacity_ratio": 0.1,
+ "root_id": "0",
+ "expected_final_pg_target": 512,
+ "expected_final_ratio": 1,
+ "even_pools": True,
+ "size": 1,
+ },
+
+ "test4":{
+
+ "pool": 4,
+ "pool_name": "test4",
+ "pg_num_target": 32,
+ "capacity_ratio": 0.4,
+ "root_id": "1",
+ "expected_final_pg_target": 2048,
+ "expected_final_ratio": 1,
+ "even_pools": True,
+ "size": 1,
+ },
+
+ }
+
+ root_map = {
+
+ "0": RootMapItem(3, 5000, 5000),
+ "1": RootMapItem(2, 5000, 5000),
+
+ }
+
+ bias = 1
+ self.helper_test(pools, root_map, bias)