diff options
Diffstat (limited to 'src/tools/ceph-lazy')
-rw-r--r-- | src/tools/ceph-lazy/bash_completion.d/ceph-lazy | 27 | ||||
-rwxr-xr-x | src/tools/ceph-lazy/ceph-lazy | 709 |
2 files changed, 736 insertions, 0 deletions
diff --git a/src/tools/ceph-lazy/bash_completion.d/ceph-lazy b/src/tools/ceph-lazy/bash_completion.d/ceph-lazy new file mode 100644 index 000000000..4429def42 --- /dev/null +++ b/src/tools/ceph-lazy/bash_completion.d/ceph-lazy @@ -0,0 +1,27 @@ +_ceph-lazy() +{ + local cur prev all_opts commands + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + commands="host-get-osd host-get-nodes host-osd-usage host-all-usage pg-get-host pg-most-write pg-less-write pg-most-write-kb pg-less-write-kb pg-most-read pg-less-read pg-most-read-kb pg-less-read-kb pg-empty rbd-prefix rbd-count rbd-host rbd-osd rbd-size rbd-all-size osd-most-used osd-less-used osd-get-ppg osd-get-pg object-get-host" + + all_opts="$commands -d -h" + + + +# If first option is -d keep completing without -d & -h + if [[ ${prev} == "-d" && ${#COMP_WORDS[@]} -eq 3 ]] ; then + COMPREPLY=( $(compgen -W "${commands}" -- ${cur}) ) + return 0 +# Do completion for first args + elif [[ ${#COMP_WORDS[@]} -eq 2 ]]; then + COMPREPLY=( $(compgen -W "${all_opts}" -- ${cur}) ) + return 0 +# Else do nothing + else + return 0 + fi +} +complete -F _ceph-lazy ceph-lazy diff --git a/src/tools/ceph-lazy/ceph-lazy b/src/tools/ceph-lazy/ceph-lazy new file mode 100755 index 000000000..39a331921 --- /dev/null +++ b/src/tools/ceph-lazy/ceph-lazy @@ -0,0 +1,709 @@ +#!/usr/bin/env bash +# +# ceph-lazy : Be efficient, be lazy ! +# +# Author: Gregory Charot <gcharot@redhat.com> +# +# This is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# + +# Specify exta option for ceph like the username/keyring/etc. Can also be done with CEPH_ARGS global variable +#CEPH_OPT="-n client.username" +VERSION="1.1.2" + +# +# Print info message to stderr +# + +function echoinfo() { + printf "INFO: %s\n" "$*" >&2; +} + + +# +# Print error message to stderr +# + +function echoerr() { + printf "ERROR: %s\n" "$*" >&2; +} + + +function help() { + >&2 echo "Usage : ceph-lazy [-d | -h] [command] [parameters] + +Ceph complex querying tool - Version $VERSION + +OPTIONS +======== + -d Activate debug mode + -h Print help + +COMMANDS +========= + + Host + ----- + host-get-osd hostname List all OSD IDs attached to a particular node. + host-get-nodes List all storage nodes. + host-osd-usage hostname Show total OSD space usage of a particular node (-d for details). + host-all-usage Show total OSD space usage of each nodes (-d for details) + + Placement groups + ----------------- + pg-get-host pgid Find PG storage hosts (first is primary) + pg-most-write Find most written PG (nb operations) + pg-less-write Find less written PG (nb operations) + pg-most-write-kb Find most written PG (data written) + pg-less-write-kb Find less written PG (data written) + pg-most-read Find most read PG (nb operations) + pg-less-read Find less read PG (nb operations) + pg-most-read-kb Find most read PG (data read) + pg-less-read-kb Find less read PG (data read) + pg-empty Find empty PGs (no stored object) + + RBD + ---- + rbd-prefix pool_name image_name Return RBD image prefix + rbd-count pool_name image_name Count number of objects in a RBD image + rbd-host pool_name image_name Find RBD primary storage hosts + rbd-osd pool_name image_name Find RBD primary OSDs + rbd-size pool_name image_name Print RBD image real size + rbd-all-size pool_name Print all RBD images size (Top first) + + OSD + ---- + osd-most-used Show the most used OSD (capacity) + osd-less-used Show the less used OSD (capacity) + osd-get-ppg osd_id Show all primaries PGS hosted on a OSD + osd-get-pg osd_id Show all PGS hosted on a OSD + + Objects + -------- + object-get-host pool_name object_id Find object storage hosts (first is primary) + " + +} + +# +# Check dependencies +# +function check_requirements() +{ + + # List of command dependencies + local bin_dep="ceph rados rbd osdmaptool jq" + + for cmd in $bin_dep; do + [ $DEBUG -eq 1 ] && echoinfo "Checking for $cmd..." + $cmd --version >/dev/null 2>&1 || { echoerr "$cmd cannot be found... Aborting."; return 1; } + done + + CEPH="ceph $CEPH_OPT" + + [ $DEBUG -eq 1 ] && echoinfo "Checking Ceph connectivity & basic permissions..." + + if ! $CEPH -s &> /dev/null; then + echoerr "Cannot connect to cluster, please check your username & permissions" + echoerr "Command $CEPH -s failed" + return 1 + fi + + JQ="jq -M --raw-output" +} + +# +# Print the host that hosts a specific PG +# +function find_host_from_pg() { + + if [ $# -eq 1 ]; then + local PGID=$1 + else + echoerr "This command requires one argument" + help + exit 1 + fi + + [ $DEBUG -eq 1 ] && echoinfo "PG $PGID has been found at (first is primary) : " + + for osd in $($CEPH pg $PGID query | $JQ -cr .up[]); do + echo -n "OSD:osd.$osd | Host:" + $CEPH osd find $osd --format json 2> /dev/null | $JQ .crush_location.host + done +} + + +# +# Print the host that hosts a specific object +# +function find_host_from_object() { + + if [ $# -eq 2 ]; then + local pool=$1 + local objid=$2 + else + echoerr "This command requires two arguments" + help + exit 1 + fi + + local pgid=$($CEPH osd map $pool $objid --format json 2> /dev/null | $JQ -cr .pgid) + + [ $DEBUG -eq 1 ] && echoinfo $objid found into PG $pgid + + while read host; do + echo "PG:$pgid | $host" + done < <(find_host_from_pg $pgid) +} + + +# +# Print all primary pgs hosted by an OSD +# +function find_prim_pg_from_osd() { + + if [ $# -eq 1 ]; then + local posd=$1 + else + echoerr "This command requires one argument" + help + exit 1 + fi + + [ $DEBUG -eq 1 ] && echoinfo "Looking for primary PGs belonging to OSD $posd" + $CEPH pg dump pgs --format json 2>/dev/null | $JQ --argjson posd $posd '.[] | select(.acting_primary==$posd).pgid' +} + + +# +# Print all pgs (primary & secondary) hosted by an OSD +# +function find_all_pg_from_osd() { + + if [ $# -eq 1 ]; then + local osd=$1 + else + echoerr "This command requires one argument" + help + exit 1 + fi + + [ $DEBUG -eq 1 ] && echoinfo "Looking for all PGs mapped to OSD $osd" + $CEPH pg dump pgs --format json 2> /dev/null | $JQ -M --argjson osd $osd '.[] | select(.up[]==$osd).pgid' +} + + +# +# Check if a given image exists +# +function check_rbd_exists(){ + + pool=$1 + rbd=$2 + + if ! rbd info -p $pool $rbd &> /dev/null; then + echoerr "Unable to find image $pool/$rbd" + exit 1 + fi +} + + +# +# Return RBD prefix from image name +# +function get_rbd_prefix() { + + if [ $# -eq 2 ]; then + local pool=$1 + local rbd=$2 + else + echoerr "This command requires two arguments" + help + exit 1 + fi + + check_rbd_exists $pool $rbd + + local prefix=$(rbd --image $rbd -p $pool info --format json 2> /dev/null | jq --raw-output .block_name_prefix) + if [ -z $prefix ]; then + echoerr "Unable to find RBD Prefix for image $pool/$rbd" + exit 1 + else + echo $prefix + fi + +} + + +# +# Count number of object in a RBD image +# +function count_rbd_object() { + + if [ $# -eq 2 ]; then + local pool=$1 + local rbd=$2 + else + echoerr "This command requires two arguments" + help + exit 1 + fi + + check_rbd_exists $pool $rbd + + local rbd_prefix=$(get_rbd_prefix $pool $rbd) + + [ $DEBUG -eq 1 ] && echoinfo "RBD image $pool/$rbd has prefix $rbd_prefix; now couning objects..." + + local nb_obj=$(rados -p $pool ls | grep $rbd_prefix | wc -l) + + [ $DEBUG -eq 1 ] && echoinfo "RBD image $pool/$rbd has $nb_obj objects" + echo $nb_obj +} + + +# +# Find primary storage host for a given RBD image +# +function find_prim_host_from_rbd() { + + if [ $# -eq 2 ]; then + local pool=$1 + local rbd=$2 + else + echoerr "This command requires two arguments" + help + exit 1 + fi + + check_rbd_exists $pool $rbd + + local osd="null" + local osdmap_t=$(mktemp) + local osdtree_t=$(mktemp) + # Get RBD image prefix + local rbd_prefix=$(get_rbd_prefix $pool $rbd) +# Exit if we received an empty prefix + [ -z $rbd_prefix ] && exit 1 + +# Get pool ID from pool name + local pool_id=$(ceph osd lspools -f json | $JQ -M --arg pool $pool '.[]|select(.poolname==$pool).poolnum') + + [ $DEBUG -eq 1 ] && echoinfo "RBD image $pool/$rbd has prefix $rbd_prefix; now finding primary host..." + + [ $DEBUG -eq 1 ] && echoinfo "Dumping OSD map to $osdmap_t" + if ! $CEPH osd getmap > $osdmap_t 2> /dev/null; then + echoerr "Failed to retrieve OSD map" + exit 1 + fi + + [ $DEBUG -eq 1 ] && echoinfo "Dumping OSD tree to $osdtree_t" + + if ! $CEPH osd tree --format json > $osdtree_t; then + echoerr "Failed to retrieve OSD tree" + exit 1 + fi + + [ $DEBUG -eq 1 ] && echoinfo "Looking for hosts..." + +# For each object in the RBD image + for obj in $(rados -p $pool ls | grep $rbd_prefix); + do +# Map object to osd. osdmaptoot does not support json output so using dirty sed. + osd=$(osdmaptool --test-map-object $obj --pool $pool_id $osdmap_t 2>/dev/null | sed -r 's/.*\[([[:digit:]]+),.*/\1/' | grep -v osdmaptool) +# Map osd to host + $JQ --argjson osd $osd '.nodes[] | select(.type=="host") | select(.children[] == $osd).name' $osdtree_t + done | sort -u + +# Cleaning files + rm -f $osdtree_t $osdmap_t +} + + +# +# Find primary OSDs for a given RBD image +# +function find_prim_osd_from_rbd() { + + if [ $# -eq 2 ]; then + local pool=$1 + local rbd=$2 + else + echoerr "This command requires two arguments" + help + exit 1 + fi + + check_rbd_exists $pool $rbd + + local osd="null" + local osdmap_t=$(mktemp) + local osdtree_t=$(mktemp) + # Get RBD image prefix + local rbd_prefix=$(get_rbd_prefix $pool $rbd) + +# Exit if we received an empty prefix + [ -z $rbd_prefix ] && exit 1 + + [ $DEBUG -eq 1 ] && echoinfo "RBD image $pool/$rbd has prefix $rbd_prefix; now finding primary OSDs..." + + [ $DEBUG -eq 1 ] && echoinfo "Dumping OSD map to $osdmap_t" + if ! $CEPH osd getmap > $osdmap_t; then + echoerr "Failed to retrieve OSD map" + exit 1 + fi + +# For each object in the RBD image + for obj in $(rados -p $pool ls | grep $rbd_prefix); + do +# Map object to osd. osdmaptoot does not support json output so using dirty sed. + osd=$(osdmaptool --test-map-object $obj $osdmap_t 2>/dev/null | sed -r 's/.*\[([[:digit:]]+),.*/\1/' | grep -v osdmaptool) + echo "osd.${osd}" + done | sort -u + +# Cleaning files + rm -f $osdmap_t +} + + +# +# Print RBD image real size - Source http://ceph.com/planet/real-size-of-a-ceph-rbd-image/ +# + +function print_rbd_real_size { + + if [ $# -eq 2 ]; then + local pool=$1 + local rbd=$2 + else + echoerr "This command requires two arguments" + help + exit 1 + fi + + [ $DEBUG -eq 1 ] && echoinfo "Checking if RBD image exists..." + + check_rbd_exists $pool $rbd + + rbd diff $pool/$rbd | awk '{ SUM += $2 } END { print SUM/1024/1024 " MB" }' + +} + + +# +# Print all RBD image real sizes - Top first +# + +function list_all_rbd_real_size { + + if [ $# -eq 1 ]; then + local pool=$1 + else + echoerr "This command requires one argument" + help + exit 1 + fi + + [ $DEBUG -eq 1 ] && echoinfo "Looking for RBD images in pool $pool" + + while read rbd; do + [ $DEBUG -eq 1 ] && echoinfo "Inspecting image $rbd" + rbd diff $pool/$rbd | awk -v rbd="$rbd" '{ SUM += $2 } END { print SUM/1024/1024 " MB - " rbd }' + done < <(rbd -p $pool ls) | sort -rV +} + + +# +# Print OSDs belonging to a particular storage host +# + +function list_osd_from_host() { + + if [ $# -eq 1 ]; then + local host=$1 + else + echoerr "This command requires one argument" + help + exit 1 + fi + + $CEPH osd tree --format json-pretty 2> /dev/null | $JQ --arg host $host '.nodes[] | select(.type=="host") | select(.name == $host).children[]' | sort -V + +} + + +# +# List all OSD nodes +# + +function list_all_nodes() { + + + $CEPH osd tree --format json | $JQ -M --raw-output '.nodes[] | select(.type=="host") | .name' | sort -V + +} + + +# +# Print Total OSD usage of a particular storage host +# + +function show_host_osd_usage() { + + if [ $# -eq 1 ]; then + local host=$1 + else + echoerr "This command requires one argument" + help + exit 1 + fi + + local pgmap_t=$(mktemp) + + local osd_used_kb=0 + local total_used_kb=0 + + local total_available_kb=0 + local osd_available_kb=0 + + local total_size_kb=0 + local osd_size_kb=0 + local nb_osd=0 + + [ $DEBUG -eq 1 ] && echoinfo "Dumping PG map..." + if ! $CEPH pg dump osds --format json 2>/dev/null > $pgmap_t; then + echoerr "Failed to retrieve PG map" + exit 1 + fi + + [ $DEBUG -eq 1 ] && echoinfo "Looking for all OSDs on host $host..." + + for osd in $(list_osd_from_host $host); do + + osd_used_kb=$($JQ --argjson osd $osd '.[] | select(.osd == $osd).kb_used' $pgmap_t) + osd_available_kb=$($JQ --argjson osd $osd '.[] | select(.osd == $osd).kb_avail' $pgmap_t) + osd_size_kb=$($JQ --argjson osd $osd '.[] | select(.osd == $osd).kb' $pgmap_t) + + [ $DEBUG -eq 1 ] && echoinfo "OSD:$osd | Size:$(echo "scale=1;$osd_size_kb/1024/1024" | bc -l)GB | Used:$(echo "scale=1;$osd_used_kb /1024/1024" | bc -l)GB | Available:$(echo "scale=1;$osd_available_kb/1024/1024" | bc -l)GB" + + let "total_used_kb=total_used_kb+osd_used_kb" + let "total_available_kb=total_available_kb+osd_available_kb" + let "total_size_kb=total_size_kb+osd_size_kb" + let "nb_osd++" + + done + + echo "Host:$host | OSDs:$nb_osd | Total_Size:$(echo "scale=1;$total_size_kb/1024/1024" | bc -l)GB | Total_Used:$(echo "scale=1;$total_used_kb /1024/1024" | bc -l)GB | Total_Available:$(echo "scale=1;$total_available_kb/1024/1024" | bc -l)GB" + + rm -f $pgmap_t +} + + +# +# Print Total OSD usage of all nodes +# + +function list_all_nodes_osd_usage() { + + + for host in $(list_all_nodes); do + + [ $DEBUG -eq 1 ] && echoinfo "Looking at node $host..." + + show_host_osd_usage $host + done + +} + + +# +# Find most used (space) OSD +# + +function find_most_used_osd() { + + local osd=$($CEPH pg dump osds --format json 2> /dev/null| $JQ 'max_by(.kb_used) | .osd') + local host=$($CEPH osd find $osd 2> /dev/null | $JQ .crush_location.host) + + echo "OSD:osd.${osd} | host:$host" +} + + +# +# Find less used (space) OSD +# + +function find_less_used_osd() { + + local osd=$($CEPH pg dump osds --format json 2> /dev/null| $JQ 'min_by(.kb_used) | .osd') + local host=$($CEPH osd find $osd 2> /dev/null | $JQ .crush_location.host) + + echo "OSD:osd.${osd} | host:$host" +} + + +# +# Query PG stats +# + +function pg_stat_query() { + + if [ $# -eq 1 ]; then + local query_type=$1 + else + echoerr "This command requires one argument" + help + exit 1 + fi + + local pgmap_t=$(mktemp) + + [ $DEBUG -eq 1 ] && echoinfo "Dumping PG map..." + if ! $CEPH pg dump pgs --format json 2>/dev/null > $pgmap_t; then + echoerr "Failed to retrieve PG map" + exit 1 + fi + + local pgid=$($JQ --arg query_type $query_type "$query_type" $pgmap_t) + [ $DEBUG -eq 1 ] && echoinfo "Found PGID $pgid" + + local osd=$($JQ --arg pgid $pgid '.[] | select(.pgid == $pgid).acting_primary' $pgmap_t) + [ $DEBUG -eq 1 ] && echoinfo "Found OSD $osd" + + local host=$($CEPH osd find $osd --format json 2> /dev/null | $JQ .crush_location.host) + [ $DEBUG -eq 1 ] && echoinfo "Found host $host" + + echo "PG:$pgid | OSD:osd.$osd | Host:$host" + + rm -f $pgmap_t +} + + +# +# Find empty pgs (no object stored) +# + +function find_empty_pg() { + + $CEPH pg dump pgs --format json 2>/dev/null | $JQ '.[] | select(.stat_sum.num_objects == 0).pgid' + +} + + +# +# MAIN +# + + +# Print help if no argument is given +if [ $# -eq 0 ]; then + help + exit 1 +fi + +# Activate debug mode if -d is specified as first parameter +if [ "$1" = "-d" ]; then + echoinfo "Debug mode activated" + DEBUG=1 + shift +else + DEBUG=0 +fi + + +# Check if all requirements are met +check_requirements || exit 1 + + +# Call proper function +case $1 in + "-h") + help + exit 0 + ;; + "host-get-osd") + list_osd_from_host $2 + ;; + "host-get-nodes") + list_all_nodes + ;; + "host-osd-usage") + show_host_osd_usage $2 + ;; + "host-all-usage") + list_all_nodes_osd_usage + ;; + "pg-get-host") + find_host_from_pg $2 + ;; + "pg-most-write") + pg_stat_query "max_by(.stat_sum.num_write).pgid" + ;; + "pg-less-write") + pg_stat_query "min_by(.stat_sum.num_write).pgid" + ;; + "pg-most-write-kb") + pg_stat_query "max_by(.stat_sum.num_write_kb).pgid" + ;; + "pg-less-write-kb") + pg_stat_query "min_by(.stat_sum.num_write_kb).pgid" + ;; + "pg-most-read") + pg_stat_query "max_by(.stat_sum.num_read).pgid" + ;; + "pg-less-read") + pg_stat_query "min_by(.stat_sum.num_read).pgid" + ;; + "pg-most-read-kb") + pg_stat_query "max_by(.stat_sum.num_read_kb).pgid" + ;; + "pg-less-read-kb") + pg_stat_query "min_by(.stat_sum.num_read_kb).pgid" + ;; + "rbd-prefix") + get_rbd_prefix $2 $3 + ;; + "rbd-count") + count_rbd_object $2 $3 + ;; + "rbd-host") + find_prim_host_from_rbd $2 $3 + ;; + "rbd-osd") + find_prim_osd_from_rbd $2 $3 + ;; + "rbd-size") + print_rbd_real_size $2 $3 + ;; + "rbd-all-size") + list_all_rbd_real_size $2 + ;; + "osd-most-used") + find_most_used_osd + ;; + "osd-less-used") + find_less_used_osd + ;; + "osd-get-ppg") + find_prim_pg_from_osd $2 + ;; + "osd-get-pg") + find_all_pg_from_osd $2 + ;; + "pg-empty") + find_empty_pg + ;; + "object-get-host") + find_host_from_object $2 $3 + ;; + *) + echoerr "Unknown command : $1" + help + exit 1 + ;; +esac + |