|
| 1 | +#!/usr/bin/python3 |
| 2 | + |
| 3 | +# program to run through all the interesting metrics (i.e. those that are related in some way to a resident VM) |
| 4 | +# and output a graph suitable for graphviz. |
| 5 | +# note that graphviz comments are /*C-style*/ |
| 6 | +# the fundamental type here is a 'record dictionary', indexing object records by their opaque refs. |
| 7 | + |
| 8 | +import atexit |
| 9 | +import contextlib |
| 10 | +from pprint import pprint, pformat |
| 11 | + |
| 12 | +import XenAPI |
| 13 | + |
| 14 | + |
| 15 | +# given a set of object references of type 'type_string', return a |
| 16 | +# dictionary linking the references to the associated records |
| 17 | +def get_records(sx, object_set, type_string): |
| 18 | + dic = {} |
| 19 | + for x in object_set: |
| 20 | + dic[x] = sx.__getattr__(type_string).get_record(x) |
| 21 | + return dic |
| 22 | + |
| 23 | + |
| 24 | +# given a record dictionary, print out the 'name_labels' of each entry if it exists, and the reference otherwise |
| 25 | +def print_names(dictionary, title): |
| 26 | + print("/*") |
| 27 | + print( |
| 28 | + title, |
| 29 | + ":", |
| 30 | + ", ".join([dictionary[x].get("name_label", x) for x in dictionary.keys()]), |
| 31 | + ) |
| 32 | + print("*/") |
| 33 | + |
| 34 | + |
| 35 | +# for example, the record dictionary of VMs contains a key VIFs, which is a list of the associated VIFs |
| 36 | +# this function will take say, the dictionary of all the resident VMs, and return the set of all the VIFs |
| 37 | +# associated with them |
| 38 | +def set_from_key_in_dictionary_of_records(record_dict, key_name, key_is_list=True): |
| 39 | + s = set() |
| 40 | + for v in record_dict.values(): |
| 41 | + key = v[key_name] |
| 42 | + if key_is_list: |
| 43 | + s.update(key) |
| 44 | + else: |
| 45 | + s.add(key) |
| 46 | + return s |
| 47 | + |
| 48 | + |
| 49 | +# and this composition function will, say, given the VM dictionary, and the key name VIFs, return the |
| 50 | +# dictionary of all associated VIFs |
| 51 | +def chase_key(sx, record_dictionary, type_name, key_name, key_is_list=True): |
| 52 | + new_set = set_from_key_in_dictionary_of_records( |
| 53 | + record_dictionary, key_name, key_is_list |
| 54 | + ) |
| 55 | + return get_records(sx, new_set, type_name) |
| 56 | + |
| 57 | + |
| 58 | +# The metrics records hold a lot of data. The following functions take a reference/record pair of a particular type |
| 59 | +# get the associated metrics, and return a few interesting quantities we want to see on the graphs |
| 60 | +def host_data(sx, ref, record): |
| 61 | + data = {} |
| 62 | + data["name"] = record["name_label"] |
| 63 | + metrics = sx.host_metrics.get_record(sx.host.get_metrics(ref)) |
| 64 | + cpu_utilisations = [ |
| 65 | + sx.host_cpu.get_utilisation(x) for x in sx.host.get_host_CPUs(ref) |
| 66 | + ] |
| 67 | + data["label"] = {} |
| 68 | + data["label"]["cpus"] = " ".join(["%.2f" % x for x in cpu_utilisations]) |
| 69 | + data["label"]["memory"] = "%.2f" % ( |
| 70 | + float(metrics["memory_free"]) / float(metrics["memory_total"]) |
| 71 | + ) |
| 72 | + return data |
| 73 | + |
| 74 | + |
| 75 | +def vm_data(sx, ref, record): |
| 76 | + data = {} |
| 77 | + data["name"] = record["name_label"] |
| 78 | + metrics = sx.VM_metrics.get_record(sx.VM.get_metrics(ref)) |
| 79 | + data["label"] = {} |
| 80 | + data["label"]["cpus"] = " ".join( |
| 81 | + ["%.2f" % x for x in metrics["VCPUs_utilisation"].values()] |
| 82 | + ) |
| 83 | + data["label"]["memory"] = "%sM" % ( |
| 84 | + float(metrics["memory_actual"]) / float(1024 * 1024) |
| 85 | + ) |
| 86 | + return data |
| 87 | + |
| 88 | + |
| 89 | +# vifs vbds and pifs all work the same way, but we need a type variable for the xapi bindings dispatcher |
| 90 | +def io_data(sx, ref, record, type_name, data_owner, suffixes): |
| 91 | + data = {} |
| 92 | + data["name"] = type_name.lower() |
| 93 | + obj_class = sx.__getattr__(type_name) |
| 94 | + owner_class = sx.__getattr__(data_owner) |
| 95 | + belongs_to = obj_class.__getattr__(f"get_{data_owner}")(ref) |
| 96 | + device_number = obj_class.__getattr__("get_device")(ref) |
| 97 | + metric_name = f"{type_name.lower()}_{device_number}" |
| 98 | + metrics = [ |
| 99 | + x |
| 100 | + for x in owner_class.get_data_sources(belongs_to) |
| 101 | + if x["name_label"].startswith(metric_name) |
| 102 | + ] |
| 103 | + metrics = {x["name_label"]: x["value"] for x in metrics} |
| 104 | + |
| 105 | + data["label"] = {} |
| 106 | + data["label"]["read"] = "%.2fk" % (metrics[f"{metric_name}_{suffixes[0]}"]) |
| 107 | + data["label"]["write"] = "%.2fk" % (metrics[f"{metric_name}_{suffixes[1]}"]) |
| 108 | + return data |
| 109 | + |
| 110 | + |
| 111 | +# these functions use the object lists constructed and metric analysis functions defined above |
| 112 | +# to print out the node and edge definitions required by graphviz |
| 113 | +def print_nodes(record_dictionary): |
| 114 | + for x in record_dictionary.keys(): |
| 115 | + print('"%s" [label="%s"];' % (x, record_dictionary[x].get("name_label", "?"))) |
| 116 | + |
| 117 | + |
| 118 | +# calls the metric analysis functions to output nodes labelled with their metrics as well as their names |
| 119 | +def print_metric_nodes(sx, dic, metricfn): |
| 120 | + for ref, record in dic.items(): |
| 121 | + d = metricfn(sx, ref, record) |
| 122 | + label = ( |
| 123 | + d["name"] |
| 124 | + + "\\n" |
| 125 | + + "\\n".join(["%s=%s" % (k, v) for k, v in d["label"].items()]) |
| 126 | + ) |
| 127 | + print('"%s" [label="%s"];' % (ref, label)) |
| 128 | + |
| 129 | + |
| 130 | +# prints out the connecting edges, in similar manner to the key-chasing above when we first got the objects |
| 131 | +def print_edges(record_dictionary, key, key_is_list=True): |
| 132 | + for k, v in record_dictionary.items(): |
| 133 | + if key_is_list: |
| 134 | + for x in v[key]: |
| 135 | + print('"%s" -> "%s";' % (k, x)) |
| 136 | + else: |
| 137 | + print('"%s" -> "%s";' % (k, v[key])) |
| 138 | + |
| 139 | + |
| 140 | +def main(): |
| 141 | + session = XenAPI.xapi_local() |
| 142 | + |
| 143 | + def logout(): |
| 144 | + with contextlib.suppress(Exception): |
| 145 | + session.xenapi.session.logout() |
| 146 | + |
| 147 | + atexit.register(logout) |
| 148 | + |
| 149 | + session.xenapi.login_with_password("", "", "1.0", "newmetricsgraph-script") |
| 150 | + sx = session.xenapi |
| 151 | + |
| 152 | + # find all the hosts |
| 153 | + host_dic = get_records(sx, set(sx.host.get_all()), "host") |
| 154 | + |
| 155 | + # chase the chain of types through hosts->VMs->VIFs->networks->PIFs and hosts->VMs->VBDs |
| 156 | + resident_vms_dic = chase_key(sx, host_dic, "VM", "resident_VMs") |
| 157 | + active_vifs_dic = chase_key(sx, resident_vms_dic, "VIF", "VIFs") |
| 158 | + active_vbds_dic = chase_key(sx, resident_vms_dic, "VBD", "VBDs") |
| 159 | + active_networks_dic = chase_key(sx, active_vifs_dic, "network", "network", False) |
| 160 | + active_pifs_dic = chase_key(sx, active_networks_dic, "PIF", "PIFs") |
| 161 | + |
| 162 | + # print out the objects we found as a graphviz comment |
| 163 | + print_names(host_dic, "hosts") |
| 164 | + print_names(resident_vms_dic, "resident VMs") |
| 165 | + print_names(active_vifs_dic, "active VIFs") |
| 166 | + print_names(active_vbds_dic, "active VBDs") |
| 167 | + print_names(active_networks_dic, "active networks") |
| 168 | + print_names(active_pifs_dic, "active PIFs") |
| 169 | + |
| 170 | + # We've now got all the objects we need, so we now need to get their metrics data and output it as a graphviz file |
| 171 | + |
| 172 | + print('digraph active_objects {') |
| 173 | + print('node [shape="rect"];') |
| 174 | + |
| 175 | + print_metric_nodes(sx, host_dic, host_data) |
| 176 | + print_metric_nodes(sx, resident_vms_dic, vm_data) |
| 177 | + print_metric_nodes( |
| 178 | + sx, |
| 179 | + active_vifs_dic, |
| 180 | + lambda sx, ref, record: io_data(sx, ref, record, "VIF", "VM", ["rx", "tx"]), |
| 181 | + ) |
| 182 | + print_metric_nodes( |
| 183 | + sx, |
| 184 | + active_vbds_dic, |
| 185 | + lambda sx, ref, record: io_data( |
| 186 | + sx, ref, record, "VBD", "VM", ["read", "write"] |
| 187 | + ), |
| 188 | + ) |
| 189 | + print_metric_nodes( |
| 190 | + sx, |
| 191 | + active_pifs_dic, |
| 192 | + lambda sx, ref, record: io_data(sx, ref, record, "PIF", "host", ["rx", "tx"]), |
| 193 | + ) |
| 194 | + |
| 195 | + print_nodes(active_networks_dic) |
| 196 | + |
| 197 | + print_edges(host_dic, "resident_VMs") |
| 198 | + print_edges(resident_vms_dic, "VIFs") |
| 199 | + print_edges(resident_vms_dic, "VBDs") |
| 200 | + print_edges(active_vifs_dic, "network", False) |
| 201 | + print_edges(active_networks_dic, "PIFs") |
| 202 | + |
| 203 | + print("}") |
| 204 | + |
| 205 | + |
| 206 | +if __name__ == "__main__": |
| 207 | + main() |
0 commit comments