Skip to content

Commit e02427b

Browse files
authored
Resurrect RRD scripts (#6295)
Resurrects two previously deleted scripts: `metrics.py` and `metricsgraph.py`. The first one dumps a human-readable list of all host, VM, VIF, PIF, VBD-related metrics for quick troubleshooting. `metricsgraph.py` creates a textual representation of the graph below, representing relationships between and basic characteristics/metrics of objects: ![image](https://github.com/user-attachments/assets/abbccb5d-c7b8-4ca2-a9f7-2303eaf980e4) These are both installed alongside other Python libexec tools. I haven't altered what the scripts do themselves beyond bringing Python and API usage up to date, if anyone feels like these tools could be changed in some way to better serve their purpose (quick diagnostics for support/potentially part of the bugtool), please let me know.
2 parents aad6753 + 7269b09 commit e02427b

File tree

4 files changed

+400
-0
lines changed

4 files changed

+400
-0
lines changed

python3/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ install:
2323
$(IDATA) dnf_plugins/accesstoken.py $(DESTDIR)$(SITE3_DIR)/$(DNF_PLUGIN_DIR)/
2424
$(IDATA) dnf_plugins/ptoken.py $(DESTDIR)$(SITE3_DIR)/$(DNF_PLUGIN_DIR)/
2525

26+
$(IPROG) libexec/metrics.py $(DESTDIR)$(OPTDIR)/debug
27+
$(IPROG) libexec/metricsgraph.py $(DESTDIR)$(OPTDIR)/debug
2628
$(IPROG) libexec/host-display $(DESTDIR)$(LIBEXECDIR)
2729
$(IPROG) libexec/link-vms-by-sr.py $(DESTDIR)$(LIBEXECDIR)
2830
$(IPROG) libexec/usb_reset.py $(DESTDIR)$(LIBEXECDIR)

python3/libexec/metrics.py

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
#!/usr/bin/python3
2+
3+
import atexit
4+
import contextlib
5+
from pprint import pprint, pformat
6+
7+
import XenAPI
8+
9+
10+
# given a list of dictionaries, print selected keys in order from each one, nicely formatted with a title
11+
def dictionary_list_partial_print(title, dictionary_list, keys):
12+
bar = "-" * len(title)
13+
print(bar, "\n", title, "\n", bar)
14+
print(
15+
"\n--\n".join(
16+
[
17+
"\n".join(["%s : %s" % (k, pformat(d[k])) for k in keys])
18+
for d in dictionary_list
19+
]
20+
)
21+
)
22+
print(bar)
23+
24+
25+
# x, 'VM', 'guest_metrics' -> guest_metrics_record of the VM x
26+
# catch the NULL if the record doesn't exist for some reason, and return the string 'NULL'
27+
def fetch_metrics_record(sx, object_reference, type_string, metrics_name):
28+
record_reference = sx.__getattr__(type_string).__getattr__("get_" + metrics_name)(
29+
object_reference
30+
)
31+
if record_reference == "OpaqueRef:NULL":
32+
return "NULL"
33+
else:
34+
return sx.__getattr__(f"{type_string}_{metrics_name}").get_record(
35+
record_reference
36+
)
37+
38+
39+
def fetch_rrd_records(sx, object_reference, type_string, data_owner):
40+
obj_class = sx.__getattr__(type_string)
41+
owner_class = sx.__getattr__(data_owner)
42+
belongs_to = obj_class.__getattr__(f"get_{data_owner}")(object_reference)
43+
device_number = obj_class.__getattr__("get_device")(object_reference)
44+
related_data_sources = [
45+
x
46+
for x in owner_class.get_data_sources(belongs_to)
47+
if x["name_label"].startswith(f"{type_string.lower()}_{device_number}")
48+
]
49+
related_data_sources = {x["name_label"]: x["value"] for x in related_data_sources}
50+
return related_data_sources
51+
52+
53+
# the names of the vbds are a little more complicated, because there is the possiblility that a VBD connects
54+
# a VM to a CD drive, which may be empty, and thus not have a VDI to represent it.
55+
def get_vbd_name(sx, vbd):
56+
if sx.VBD.get_type(vbd) == "CD" and sx.VBD.get_empty(vbd) == True:
57+
device_name = "empty cd drive"
58+
else:
59+
device_name = sx.VDI.get_name_label(sx.VBD.get_VDI(vbd))
60+
return f'VBD connecting "{sx.VM.get_name_label(sx.VBD.get_VM(vbd))}" to "{device_name}"'
61+
62+
63+
def main():
64+
session = XenAPI.xapi_local()
65+
66+
def logout():
67+
with contextlib.suppress(Exception):
68+
session.xenapi.session.logout()
69+
70+
atexit.register(logout)
71+
72+
session.xenapi.login_with_password("", "", "1.0", "metrics-script")
73+
sx = session.xenapi
74+
75+
# first, we'll find all the hosts, and get the information we care about from each
76+
hosts = sx.host.get_all()
77+
host_metrics = [
78+
{
79+
"name_label": sx.host.get_name_label(x),
80+
"metrics": sx.host_metrics.get_record(sx.host.get_metrics(x)),
81+
"host_cpus": [sx.host_cpu.get_record(x) for x in sx.host.get_host_CPUs(x)],
82+
}
83+
for x in hosts
84+
]
85+
86+
# and print out the interesting bits
87+
dictionary_list_partial_print(
88+
"Host Metrics", host_metrics, ["name_label", "metrics", "host_cpus"]
89+
)
90+
91+
# find all the virtual machines which are resident on the hosts
92+
resident_vms = set()
93+
for host in hosts:
94+
resident_vms.update(sx.host.get_resident_VMs(host))
95+
96+
# get and print their info
97+
vm_metrics = [
98+
{
99+
"name_label": sx.VM.get_name_label(x),
100+
"metrics": fetch_metrics_record(sx, x, "VM", "metrics"),
101+
"guest_metrics": fetch_metrics_record(sx, x, "VM", "guest_metrics"),
102+
}
103+
for x in resident_vms
104+
]
105+
106+
dictionary_list_partial_print(
107+
"Virtual Machine Metrics",
108+
vm_metrics,
109+
["name_label", "metrics", "guest_metrics"],
110+
)
111+
112+
# from the list of resident VMs we can find all the active VIFs and VBDs
113+
# however these don't have useful names, so we have to make them up
114+
active_vifs = [
115+
vif for vif in sx.VIF.get_all() if sx.VIF.get_VM(vif) in resident_vms
116+
]
117+
118+
vif_metrics = [
119+
{
120+
"name_label": f'VIF connecting "{sx.network.get_name_label(sx.VIF.get_network(x))}" '
121+
f'to "{sx.VM.get_name_label(sx.VIF.get_VM(x))}"',
122+
"metrics": fetch_rrd_records(sx, x, "VIF", "VM"),
123+
}
124+
for x in active_vifs
125+
]
126+
127+
dictionary_list_partial_print("VIF metrics", vif_metrics, ["name_label", "metrics"])
128+
129+
active_vbds = [
130+
vbd for vbd in sx.VBD.get_all() if sx.VBD.get_VM(vbd) in resident_vms
131+
]
132+
133+
vbd_metrics = [
134+
{
135+
"name_label": get_vbd_name(sx, x),
136+
"metrics": fetch_rrd_records(sx, x, "VBD", "VM"),
137+
}
138+
for x in active_vbds
139+
]
140+
141+
dictionary_list_partial_print("VBD Metrics", vbd_metrics, ["name_label", "metrics"])
142+
143+
# from the VIFs we can find the active networks, which don't actually have any metrics
144+
active_networks = set()
145+
for vif in active_vifs:
146+
active_networks.add(sx.VIF.get_network(vif))
147+
148+
network_metrics = [
149+
{"name_label": sx.network.get_name_label(x)} for x in active_networks
150+
]
151+
dictionary_list_partial_print("Network Metrics", network_metrics, ["name_label"])
152+
153+
# and from the active networks we can get all the relevant pifs
154+
active_pifs = set()
155+
for network in active_networks:
156+
active_pifs.update(sx.network.get_PIFs(network))
157+
158+
pif_metrics = [
159+
{
160+
"name_label": f"{sx.PIF.get_device(x)} on "
161+
f"{sx.host.get_name_label(sx.PIF.get_host(x))}",
162+
"metrics": fetch_rrd_records(sx, x, "PIF", "host"),
163+
}
164+
for x in active_pifs
165+
]
166+
167+
dictionary_list_partial_print("PIF Metrics", pif_metrics, ["name_label", "metrics"])
168+
169+
# finish off by printing out a concise list of all the active objects
170+
# awkward duplication instead of iterating over locals()[name] is so that
171+
# pytype does not complain
172+
print("Active Objects")
173+
for name, lst in [
174+
("host_metrics", host_metrics),
175+
("vm_metrics", vm_metrics),
176+
("vif_metrics", vif_metrics),
177+
("vbd_metrics", vbd_metrics),
178+
("network_metrics", network_metrics),
179+
("pif_metrics", pif_metrics),
180+
]:
181+
print(name, [(y["name_label"]) for y in lst])
182+
183+
184+
if __name__ == "__main__":
185+
main()

0 commit comments

Comments
 (0)