#!/usr/bin/python #============================================================================== # ==================== # ceph_show_osd_detail # ==================== # # Takes XML output from three "ceph ..." commands, # parses it, stores it and then uses it to produce # a table like this: # # Host OSD in/out up/down Weight pgs Utilization Variance Size Available Used # ceph 1 in up 1.862 243 37.294 0.931984 1862.1G 1167.7G 694.5G # ceph 11 in up 1.862 244 43.0173 1.07501 1862.1G 1061.1G 801.0G # ceph 13 in up 0.931 100 34.7688 0.86888 931.1G 607.3G 323.7G # hp 12 in up 1.81898 172 44.2795 1.10655 1819.6G 1013.9G 805.7G # hp 5 in up 1.862 153 36.6461 0.915793 1862.1G 1179.7G 682.4G # kvm 2 in up 1.765 150 42.3598 1.05858 1765.7G 1017.7G 747.9G # kvm 3 in up 0.594986 57 48.7207 1.21754 595.9G 305.6G 290.3G # kvm 4 in up 0.925995 100 38.7892 0.969349 926.1G 566.8G 359.2G # node1 0 in up 1.81 235 37.6359 0.94053 1810.6G 1129.2G 681.5G # node1 8 in up 1.862 233 39.8959 0.997006 1862.1G 1119.2G 742.9G # node2 10 in up 0.931 131 43.2716 1.08137 931.1G 528.2G 402.9G # node2 9 in up 1.80899 261 43.0945 1.07694 1809.6G 1029.8G 779.9G # pi 14 in up 0.931 140 38.2286 0.95534 931.1G 575.1G 355.9G # scruffy 6 in up 1.85699 228 40.149 1.00333 1857.1G 1111.5G 745.6G # scruffy 7 in up 1.862 241 35.7382 0.893105 1862.1G 1196.6G 665.5G # # The "ceph ..." commands are: # # b = ceph_support.RunCommand (["ceph", "-f", "xml", "osd", "df"]) # b = ceph_support.RunCommand (["ceph", "-f", "xml", "osd", "tree"]) # b = ceph_support.RunCommand (["ceph", "-f", "xml", "osd", "dump"]) # Copyright (C) 2017, 2018 Peter Linich #============================================================================== # ======= # License # ======= # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # #============================================================================== # ======= # History # ======= # 03jan2018 Updated Usage() to display version option # 02jan2018 Added VERSION string, GPL license and "-V" option to display # version numbers # 28dec2017 Added command-line option to cause output to be sorted by # ascending OSD number rather than the default of host name/OSD # 24dec2017 Moved utility functions to "ceph_support.py" # 24dec2017 Python translation done # 20dec2017 Python translation started VERSION = "1.0 (28dec2017)" #============================================================================== # ====================== # Array content examples # ====================== # dfparsed[.df.0.nodes.0.item.0.crush_weight.0] = 0.568985 # dfparsed[.df.0.nodes.0.item.0.depth.0] = 3 # dfparsed[.df.0.nodes.0.item.0.id.0] = 114 # dfparsed[.df.0.nodes.0.item.0.kb.0] = 597396480 # dfparsed[.df.0.nodes.0.item.0.kb_avail.0] = 592057712 # dfparsed[.df.0.nodes.0.item.0.kb_used.0] = 5338768 # dfparsed[.df.0.nodes.0.item.0.name.0] = osd.114 # dfparsed[.df.0.nodes.0.item.0.pgs.0] = 21 # dfparsed[.df.0.nodes.0.item.0.reweight.0] = 1 # dfparsed[.df.0.nodes.0.item.0.type.0] = osd # dfparsed[.df.0.nodes.0.item.0.type_id.0] = 0 # dfparsed[.df.0.nodes.0.item.0.utilization.0] = 0.893672 # dfparsed[.df.0.nodes.0.item.0.var.0] = 0.396115 # treeparsed[.tree.0.nodes.0.item.100.children.0.child.0] = 96 # treeparsed[.tree.0.nodes.0.item.100.id.0] = -97 # treeparsed[.tree.0.nodes.0.item.100.name.0] = tabla08 # treeparsed[.tree.0.nodes.0.item.100.type.0] = host # treeparsed[.tree.0.nodes.0.item.100.type_id.0] = 1 # treeparsed[.tree.0.nodes.0.item.101.crush_weight.0] = 0.596985 # treeparsed[.tree.0.nodes.0.item.101.depth.0] = 3 # treeparsed[.tree.0.nodes.0.item.101.exists.0] = 1 # treeparsed[.tree.0.nodes.0.item.101.id.0] = 96 # treeparsed[.tree.0.nodes.0.item.101.name.0] = osd.96 # treeparsed[.tree.0.nodes.0.item.101.primary_affinity.0] = 1 # treeparsed[.tree.0.nodes.0.item.101.reweight.0] = 1 # treeparsed[.tree.0.nodes.0.item.101.status.0] = up # treeparsed[.tree.0.nodes.0.item.101.type.0] = osd # treeparsed[.tree.0.nodes.0.item.101.type_id.0] = 0 # dumpparsed[.osdmap.0.osds.0.osd_info.93.cluster_addr.0] = 129.94.209.5:6801/2497 # dumpparsed[.osdmap.0.osds.0.osd_info.93.down_at.0] = 6856 # dumpparsed[.osdmap.0.osds.0.osd_info.93.heartbeat_back_addr.0] = 129.94.209.5:6802/2497 # dumpparsed[.osdmap.0.osds.0.osd_info.93.heartbeat_front_addr.0] = 129.94.209.5:6803/2497 # dumpparsed[.osdmap.0.osds.0.osd_info.93.in.0] = 1 # dumpparsed[.osdmap.0.osds.0.osd_info.93.last_clean_begin.0] = 4479 # dumpparsed[.osdmap.0.osds.0.osd_info.93.last_clean_end.0] = 6855 # dumpparsed[.osdmap.0.osds.0.osd_info.93.lost_at.0] = 0 # dumpparsed[.osdmap.0.osds.0.osd_info.93.osd.0] = 93 # dumpparsed[.osdmap.0.osds.0.osd_info.93.primary_affinity.0] = 1 # dumpparsed[.osdmap.0.osds.0.osd_info.93.public_addr.0] = 129.94.209.5:6800/2497 # dumpparsed[.osdmap.0.osds.0.osd_info.93.state.0.state.0] = exists # dumpparsed[.osdmap.0.osds.0.osd_info.93.state.0.state.1] = up # dumpparsed[.osdmap.0.osds.0.osd_info.93.up.0] = 1 # dumpparsed[.osdmap.0.osds.0.osd_info.93.up_from.0] = 6869 # dumpparsed[.osdmap.0.osds.0.osd_info.93.up_thru.0] = 7523 # dumpparsed[.osdmap.0.osds.0.osd_info.93.uuid.0] = a4539002-6273-4b94-a019-edb51d6d9eb7 # dumpparsed[.osdmap.0.osds.0.osd_info.93.weight.0] = 1 #============================================================================== # ================= # Modules/libraries # ================= import sys import ceph_support as cs #============================================================================== # ======================================== # Usage() - output a helpful usage message # ======================================== def Usage (exitcode = 1): if exitcode == 0: fd = sys.stdout else: fd = sys.stderr print >> fd, "" print >> fd, "Bad invocation" print >> fd, "" print >> fd, "Outputs OSD information" print >> fd, "" print >> fd, "Options:" print >> fd, "" print >> fd, " -s Sort by OSD number instead of host name" print >> fd, " -V Show version(s)" print >> fd, " -h This message" print >> fd, " -? This message" print >> fd, "" exit (exitcode) #============================================================================== # ====== # main() # ====== # ---------------------------- # Process command-line options # ---------------------------- sort_by_osd = False n = len (sys.argv) if n == 1: pass elif n == 2: a = sys.argv[1] if a == "-s": sort_by_osd = True elif a == "-h": Usage (0) elif a == "-?": Usage (0) elif a == "-V": print "Version = " + VERSION print "ceph_support version = " + cs.VERSION exit (0) else: Usage () else: Usage () # -------------------------------------------------- # Run the "ceph osd df" command, parse it into an # array for ease of user later and create a reverse- # lookup table to go from OSD number to array index # -------------------------------------------------- b = cs.RunCommand (["ceph", "-f", "xml", "osd", "df"]) dfparsed = cs.ParseXML (b) dfreverse = {} i = 0 while True: j = str (i) key = key = ".df.0.nodes.0.item." + j + ".type.0" if key not in dfparsed: break if dfparsed[key] == "osd": osdnum = dfparsed[".df.0.nodes.0.item." + j + ".id.0"] dfreverse[osdnum] = j i += 1 # -------------------------------------------------- # Run the "ceph osd tree" command, parse it into an # array for ease of user later and create a reverse- # lookup table for it, too # -------------------------------------------------- b = cs.RunCommand (["ceph", "-f", "xml", "osd", "tree"]) treeparsed = cs.ParseXML (b) treereverse = {} i = 0 while True: j = str (i) key = ".tree.0.nodes.0.item." + j + ".id.0" if key not in treeparsed: break v = treeparsed[key] treereverse[v] = j i += 1 # -------------------------------------------------- # Run the "ceph osd dump" command, parse it into an # array for ease of user later and create a reverse- # lookup table for it, as well # -------------------------------------------------- b = cs.RunCommand (["ceph", "-f", "xml", "osd", "dump"]) dumpparsed = cs.ParseXML (b) dumpreverse = {} i = 0 while True: j = str (i) key = ".osdmap.0.osds.0.osd_info." + j + ".osd.0" if key not in dumpparsed: break v = dumpparsed[key] dumpreverse[v] = j i += 1 # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ # Iterate through all hosts in the tree and # extract the list of OSD children associated # with each as well as other miscellaneous # data we'll want to display. Store the # resulting OSD lists in the "host" # dictionary keyed by host name # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ i = 0 host = {} osdcrushweight = {} while True: j = str (i) key = ".tree.0.nodes.0.item." + j + ".type.0" if key not in treeparsed: break if treeparsed[key] == "host": hostnamekey = ".tree.0.nodes.0.item." + j + ".name.0" hostname = treeparsed[hostnamekey] if hostname not in host: host[hostname] = [] c = 0 childkeyroot = ".tree.0.nodes.0.item." + j + ".children.0.child." while True: d = str (c) childkey = childkeyroot + d if childkey not in treeparsed: break p = treereverse[treeparsed[childkey]] typekey = ".tree.0.nodes.0.item." + p + ".type.0" if treeparsed[typekey] == "osd": osdnumkey = ".tree.0.nodes.0.item." + p + ".id.0" osdnum = treeparsed[osdnumkey] osdcrushweight[osdnum] = treeparsed[".tree.0.nodes.0.item." + p + ".crush_weight.0"] host[hostname].append (osdnum) c += 1 i += 1 # ----------------------------------------------- # Generate a list of host/OSD pairs (in pairlist) # in the order we'll display them which will # either be sorted-by-OSD number or by host name # ----------------------------------------------- pairlist = [] if sort_by_osd: temp = [] for hostname in host: for osdnum in host[hostname]: temp.append ((hostname, osdnum)) pairlist = sorted (temp, key = lambda temp: int (temp[1])) else: for hostname in sorted (host): for osdnum in sorted (host[hostname]): pairlist.append ((hostname, osdnum)) # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ # Create a list of rows containing, firstly, the column # headings and then the host and OSD data such as OSD # size, weight, status, etc. # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ rows = [["Host", "OSD", "in/out", "up/down", "Weight", "pgs", "Utilization", "Variance", "Size", "Available", "Used"]] for t in pairlist: hostname = t[0] osdnum = t[1] j = dumpreverse[osdnum] if dumpparsed[".osdmap.0.osds.0.osd_info." + j + ".up.0"] != "0": up_down = "up" else: up_down = "down" if dumpparsed[".osdmap.0.osds.0.osd_info." + j + ".in.0"] != "0": in_out = "in" else: in_out = "out" j = dfreverse[osdnum] pgs = dfparsed[".df.0.nodes.0.item." + j + ".pgs.0"] util = dfparsed[".df.0.nodes.0.item." + j + ".utilization.0"] var = dfparsed[".df.0.nodes.0.item." + j + ".var.0"] size = cs.SizeStr (dfparsed[".df.0.nodes.0.item." + j + ".kb.0"]) avail = cs.SizeStr (dfparsed[".df.0.nodes.0.item." + j + ".kb_avail.0"]) used = cs.SizeStr (dfparsed[".df.0.nodes.0.item." + j + ".kb_used.0"]) r = [hostname, osdnum, in_out, up_down, osdcrushweight[osdnum], pgs, util, var, size, avail, used] rows.append (r) # ---------------------------------------------- # Print out the rows as a neatly-formatted table # ---------------------------------------------- cs.PrintColumnised (rows) #============================================================================== # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4