Skip to content

Instantly share code, notes, and snippets.

@alram
Last active October 26, 2020 12:57
Show Gist options
  • Save alram/c6b1129a4c9100ab5184197d1455a6bd to your computer and use it in GitHub Desktop.
Save alram/c6b1129a4c9100ab5184197d1455a6bd to your computer and use it in GitHub Desktop.
Calculate how much data will move before applying a CRUSHmap change
#!/usr/bin/env python
import ast
import json
import os
import subprocess
import argparse
import sys
FNULL = open(os.devnull, 'w')
# assume the osdmap test output
# is the same lenght and order...
# if add support for PG increase
# that's gonna break
def diff_output(original, new, pools):
number_of_osd_remap = 0
osd_data_movement = 0
results = {}
pg_data, pg_objects = get_pg_info()
for i in range(len(original)):
orig_i = original[i]
new_i = new[i]
if orig_i[0].isdigit():
pg_id = orig_i.split('\t')[0]
pool_id = pg_id.split('.')[0]
pool_name = pools[pool_id]['pool_name']
if not pool_name in results:
results[pool_name] = {}
results[pool_name]['osd_remap_counter'] = 0
results[pool_name]['osd_bytes_movement'] = 0
results[pool_name]['osd_objects_movement'] = 0
original_mappings = ast.literal_eval(orig_i.split('\t')[1])
new_mappings = ast.literal_eval(new_i.split('\t')[1])
intersection = list(set(original_mappings).intersection(set(new_mappings)))
osd_movement_for_this_pg = int(pools[pool_id]['pool_size']) - len(intersection)
osd_data_movement_for_this_pg = int(osd_movement_for_this_pg) * int(pg_data[pg_id])
osd_object_movement_for_this_pg = int(osd_movement_for_this_pg) * int(pg_objects[pg_id])
results[pool_name]['osd_remap_counter'] += osd_movement_for_this_pg
results[pool_name]['osd_bytes_movement'] += int(osd_data_movement_for_this_pg)
results[pool_name]['osd_objects_movement'] += int(osd_object_movement_for_this_pg)
elif orig_i.startswith('#osd'):
break
return results
def get_pools_info(osdmap_path):
pools = {}
args = ['osdmaptool', '--print', osdmap_path]
osdmap_out = subprocess.check_output(args, stderr=FNULL).split('\n')
for line in osdmap_out:
if line.startswith('pool'):
pool_id = line.split()[1]
pool_size = line.split()[5]
pool_name = line.split()[2].replace("'","")
pools[pool_id] = {}
pools[pool_id]['pool_size'] = pool_size
pools[pool_id]['pool_name'] = pool_name
elif line.startswith('max_osd'):
break
return pools
def get_osd_map(osdmap_path):
args = ['sudo', 'ceph', 'osd', 'getmap', '-o', osdmap_path]
subprocess.call(args, stdout=FNULL, stderr=subprocess.STDOUT)
def get_pg_info():
pg_data = {}
pg_objects = {}
args = ['sudo', 'ceph', 'pg', 'dump']
pgmap = subprocess.check_output(args, stderr=FNULL).split('\n')
for line in pgmap:
if line[0].isdigit():
pg_id = line.split('\t')[0]
pg_bytes = line.split('\t')[6]
pg_obj = line.split('\t')[1]
pg_data[pg_id] = pg_bytes
pg_objects[pg_id] = pg_obj
elif line.startswith('pool'):
break
return pg_data, pg_objects
def osdmaptool_test_map_pgs_dump(original_osdmap_path, crushmap):
new_osdmap_path = original_osdmap_path + '.new'
get_osd_map(original_osdmap_path)
args = ['osdmaptool', '--test-map-pgs-dump', original_osdmap_path]
original_osdmaptool_output = subprocess.check_output(args, stderr=FNULL).split('\n')
args = ['cp', original_osdmap_path, new_osdmap_path]
subprocess.call(args, stdout=FNULL, stderr=subprocess.STDOUT)
args = ['osdmaptool', '--import-crush', crushmap, new_osdmap_path]
subprocess.call(args, stdout=FNULL, stderr=subprocess.STDOUT)
args = ['osdmaptool', '--test-map-pgs-dump', new_osdmap_path]
new_osdmaptool_output = subprocess.check_output(args, stderr=FNULL).split('\n')
pools = get_pools_info(original_osdmap_path)
results = diff_output(original_osdmaptool_output, new_osdmaptool_output, pools)
return results
def dump_plain_output(results):
sys.stdout.write("%-20s %-20s %-20s %-20s\n" % ("POOL", "REMAPPED OSDs", "BYTES REBALANCE", "OBJECTS REBALANCE"))
for pool in results:
sys.stdout.write("%-20s %-20s %-20s %-20s\n" % (
pool,
results[pool]['osd_remap_counter'],
results[pool]['osd_bytes_movement'],
results[pool]['osd_objects_movement']
))
def cleanup(osdmap):
FNULL.close()
new_osdmap = osdmap + '.new'
os.remove(new_osdmap)
def parse_args():
parser = argparse.ArgumentParser(description='Ceph CRUSH change data movement calculator.')
parser.add_argument(
'--osdmap-file',
help="Where to save the original osdmap. Temp one will be <location>.new. Default: /tmp/osdmap",
default="/tmp/osdmap",
dest="osdmap_path"
)
parser.add_argument(
'--crushmap-file',
help="CRUSHmap to run the movement test against.",
required=True,
dest="new_crushmap"
)
parser.add_argument(
'--format',
help="Output format. Default: plain",
choices=['json', 'plain'],
dest="format",
default="plain"
)
args = parser.parse_args()
return args
if __name__ == '__main__':
ctx = parse_args()
results = osdmaptool_test_map_pgs_dump(ctx.osdmap_path, ctx.new_crushmap)
cleanup(ctx.osdmap_path)
if ctx.format == 'json':
print json.dumps(results)
elif ctx.format == 'plain':
dump_plain_output(results)
@alram
Copy link
Author

alram commented Feb 15, 2017

Just fixed an issue where if pool_id was more than 1 digit we truncated it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment