Skip to content

Commit 6617803

Browse files
author
Andrii Sultanov
committed
python3: Resurrect metrics.py helper script
This script was previously deleted in #3949, update it to Python 3 and the new API methods (VIF.get_metrics and the like were removed years ago), get rid of globals and remove the sanitycheck library usage. Install it alongside other libexec python scripts. The script prints out RRD metrics in the following format: ``` $ ./metrics.py ------------ Host Metrics ------------ name_label : 'lcy2-dt29' metrics : {'last_updated': <DateTime '20250107T14:48:01Z' at 0x7f13e3a264a8>, 'live': True, 'memory_free': '22100557824', 'memory_total': '34172760064', 'other_config': {}, 'uuid': '14cbb5db-63d8-f631-7179-6011cc578305'} host_cpus : [{'family': '6', 'features': '', 'flags': 'fpu de tsc msr pae mce cx8 apic sep mca cmov pat clflush acpi mmx ' 'fxsr sse sse2 ss ht syscall nx rdtscp lm constant_tsc rep_good ' 'nopl nonstop_tsc cpuid pni pclmulqdq monitor est ssse3 fma cx16 ' 'sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand hypervisor ' 'lahf_lm abm 3dnowprefetch cpuid_fault ssbd ibrs ibpb stibp ' 'fsgsbase bmi1 avx2 bmi2 erms rdseed adx clflushopt xsaveopt xsavec ' 'xgetbv1 arch_capabilities', 'host': 'OpaqueRef:5cdd5f91-b5e2-ba4f-09df-0f4ef9b77ef4', 'model': '158', 'modelname': 'Intel(R) Xeon(R) CPU E3-1230 v6 @ 3.50GHz', 'number': '7', 'other_config': {}, 'speed': '3503', 'stepping': '9', 'utilisation': 0.0, 'uuid': '99af3b01-15c3-a40b-16bb-2f5f47bf2528', 'vendor': 'GenuineIntel'}, ... ------------ ----------------------- Virtual Machine Metrics ----------------------- name_label : 'CentOS Stream 9 (1)' metrics : {'VCPUs_CPU': {}, 'VCPUs_flags': {}, 'VCPUs_number': '1', 'VCPUs_params': {}, 'VCPUs_utilisation': {'0': 0.0}, 'current_domain_type': 'hvm', 'hvm': True, 'install_time': <DateTime '20250108T09:01:39Z' at 0x7f13e3a34908>, 'last_updated': <DateTime '19700101T00:00:00Z' at 0x7f13e3cc6390>, 'memory_actual': '4294955008', 'nested_virt': False, 'nomigrate': False, 'other_config': {}, 'start_time': <DateTime '20250108T09:01:47Z' at 0x7f13e3a348d0>, 'state': [], 'uuid': '40cb2d92-f633-382e-38a5-276cae495834'} guest_metrics : {'PV_drivers_detected': True, 'PV_drivers_up_to_date': True, 'PV_drivers_version': {}, 'can_use_hotplug_vbd': 'unspecified', 'can_use_hotplug_vif': 'unspecified', 'disks': {}, 'last_updated': <DateTime '20250108T09:03:00Z' at 0x7f13e3a26320>, 'live': True, 'memory': {}, 'netbios_name': {}, 'networks': {}, 'os_version': {}, 'other': {'feature-poweroff': '1', 'feature-reboot': '1', 'feature-suspend': '1', 'feature-vcpu-hotplug': '1', 'has-vendor-device': '0', 'platform-feature-multiprocessor-suspend': '1', 'platform-feature-xs_reset_watches': '1'}, 'other_config': {}, 'uuid': '6829a282-7f29-1749-d148-eeda54bbecd4'} -- name_label : 'Control domain on host: lcy2-dt29' metrics : {'VCPUs_CPU': {}, 'VCPUs_flags': {}, 'VCPUs_number': '8', 'VCPUs_params': {}, 'VCPUs_utilisation': {'1': 0.0, '2': 0.0, '3': 0.0, '4': 0.0, '5': 0.0, '6': 0.0, '7': 0.0, '8': 0.0}, 'current_domain_type': 'pv', 'hvm': False, 'install_time': <DateTime '19700101T00:00:00Z' at 0x7f13e3a34ba8>, 'last_updated': <DateTime '19700101T00:00:00Z' at 0x7f13e3a34be0>, 'memory_actual': '2785017856', 'nested_virt': False, 'nomigrate': False, 'other_config': {}, 'start_time': <DateTime '19700101T00:00:00Z' at 0x7f13e3a34b70>, 'state': [], 'uuid': 'f7385e35-f114-c165-d66c-e9b01ec47ce9'} guest_metrics : 'NULL' -- name_label : 'Windows 10 (64-bit) (1)' metrics : {'VCPUs_CPU': {}, 'VCPUs_flags': {}, 'VCPUs_number': '2', 'VCPUs_params': {}, 'VCPUs_utilisation': {'0': 0.0}, 'current_domain_type': 'hvm', 'hvm': True, 'install_time': <DateTime '20250108T09:01:24Z' at 0x7f13e3a34da0>, 'last_updated': <DateTime '19700101T00:00:00Z' at 0x7f13e3a264e0>, 'memory_actual': '4297043968', 'nested_virt': False, 'nomigrate': False, 'other_config': {}, 'start_time': <DateTime '20250108T09:07:47Z' at 0x7f13e3a34d68>, 'state': [], 'uuid': '548a4d19-5db3-bce7-1a18-ab9affc2e9ee'} guest_metrics : {'PV_drivers_detected': True, 'PV_drivers_up_to_date': True, 'PV_drivers_version': {}, 'can_use_hotplug_vbd': 'yes', 'can_use_hotplug_vif': 'yes', 'disks': {}, 'last_updated': <DateTime '20250108T09:11:57Z' at 0x7f13e3a26400>, 'live': True, 'memory': {}, 'netbios_name': {}, 'networks': {'0/ip': '10.71.58.127', '0/ipv4/0': '10.71.58.127', '0/ipv6/0': 'fe80:0000:0000:0000:70ad:88ff:febb:643d'}, 'os_version': {}, 'other': {'data-cant-suspend-reason': '{"error":{"class":"GenericError","desc":"State ' 'blocked by non-migratable device ' '\'0000:00:07.0/nvme\'","data":{}},"id":"qmp-000013-4"}', 'feature-balloon': '1', 'feature-laptop-slate-mode': '1', 'feature-poweroff': '1', 'feature-reboot': '1', 'feature-s3': '1', 'feature-s4': '1', 'feature-shutdown': '1', 'has-vendor-device': '1', 'platform-feature-multiprocessor-suspend': '1', 'platform-feature-xs_reset_watches': '1'}, 'other_config': {}, 'uuid': 'dcd51a5f-3017-a7ec-3d35-7648366b887e'} ----------------------- ----------- VIF metrics ----------- name_label : 'VIF connecting "NPRI bond of 0 1" to "CentOS Stream 9 (1)"' metrics : {'vif_0_rx': 0.0, 'vif_0_rx_errors': 0.0, 'vif_0_tx': 0.0, 'vif_0_tx_errors': 0.0} -- name_label : 'VIF connecting "NPRI bond of 0 1" to "Windows 10 (64-bit) (1)"' metrics : {'vif_0_rx': 0.0, 'vif_0_rx_errors': 0.0, 'vif_0_tx': 0.0, 'vif_0_tx_errors': 0.0} ----------- ----------- VBD Metrics ----------- name_label : ('VBD connecting "CentOS Stream 9 (1)" to ' '"CentOS-Stream-8-x86_64-latest-boot.iso"') metrics : {'vbd_xvdd_avgqu_sz': 0.0, 'vbd_xvdd_inflight': 0.0, 'vbd_xvdd_io_throughput_read': 0.0, 'vbd_xvdd_io_throughput_total': 0.0, 'vbd_xvdd_io_throughput_write': 0.0, 'vbd_xvdd_iops_read': 0.0, 'vbd_xvdd_iops_total': 0.0, 'vbd_xvdd_iops_write': 0.0, 'vbd_xvdd_iowait': 0.0, 'vbd_xvdd_latency': 0.0, 'vbd_xvdd_read': 0.0, 'vbd_xvdd_read_latency': 0.0, 'vbd_xvdd_write': 0.0, 'vbd_xvdd_write_latency': 0.0} -- name_label : 'VBD connecting "Windows 10 (64-bit) (1)" to "win10-x64_uefi.iso"' metrics : {'vbd_xvdd_avgqu_sz': 0.0, 'vbd_xvdd_inflight': 0.0, 'vbd_xvdd_io_throughput_read': 0.0, 'vbd_xvdd_io_throughput_total': 0.0, 'vbd_xvdd_io_throughput_write': 0.0, 'vbd_xvdd_iops_read': 0.0, 'vbd_xvdd_iops_total': 0.0, 'vbd_xvdd_iops_write': 0.0, 'vbd_xvdd_iowait': 0.0, 'vbd_xvdd_latency': 0.0, 'vbd_xvdd_read': 0.0, 'vbd_xvdd_read_latency': 0.0, 'vbd_xvdd_write': 0.0, 'vbd_xvdd_write_latency': 0.0} -- name_label : 'VBD connecting "Windows 10 (64-bit) (1)" to "Windows 10 (64-bit) (1) 0"' metrics : {'vbd_xvda_avgqu_sz': 0.0, 'vbd_xvda_inflight': 0.0, 'vbd_xvda_io_throughput_read': 0.0, 'vbd_xvda_io_throughput_total': 0.0, 'vbd_xvda_io_throughput_write': 0.0, 'vbd_xvda_iops_read': 0.0, 'vbd_xvda_iops_total': 0.0, 'vbd_xvda_iops_write': 0.0, 'vbd_xvda_iowait': 0.0, 'vbd_xvda_latency': 0.0, 'vbd_xvda_read': 0.0, 'vbd_xvda_read_latency': 0.0, 'vbd_xvda_write': 0.0, 'vbd_xvda_write_latency': 0.0} -- name_label : 'VBD connecting "CentOS Stream 9 (1)" to "CentOS Stream 9 (1) 0"' metrics : {'vbd_xvda_avgqu_sz': 0.0, 'vbd_xvda_inflight': 0.0, 'vbd_xvda_io_throughput_read': 0.0, 'vbd_xvda_io_throughput_total': 0.0, 'vbd_xvda_io_throughput_write': 0.0, 'vbd_xvda_iops_read': 0.0, 'vbd_xvda_iops_total': 0.0, 'vbd_xvda_iops_write': 0.0, 'vbd_xvda_iowait': 0.0, 'vbd_xvda_latency': 0.0, 'vbd_xvda_read': 0.0, 'vbd_xvda_read_latency': 0.0, 'vbd_xvda_write': 0.0, 'vbd_xvda_write_latency': 0.0} ----------- --------------- Network Metrics --------------- name_label : 'NPRI bond of 0 1' --------------- ----------- PIF Metrics ----------- name_label : 'bond0 on lcy2-dt29' metrics : {'pif_bond0_rx': 0.0, 'pif_bond0_rx_errors': 0.0, 'pif_bond0_tx': 0.0, 'pif_bond0_tx_errors': 0.0} ----------- Active Objects host_metrics ['lcy2-dt29'] vm_metrics ['CentOS Stream 9 (1)', 'Control domain on host: lcy2-dt29', 'Windows 10 (64-bit) (1)'] vif_metrics ['VIF connecting "NPRI bond of 0 1" to "CentOS Stream 9 (1)"', 'VIF connecting "NPRI bond of 0 1" to "Windows 10 (64-bit) (1)"'] vbd_metrics ['VBD connecting "CentOS Stream 9 (1)" to "CentOS-Stream-8-x86_64-latest-boot.iso"', 'VBD connecting "Windows 10 (64-bit) (1)" to "win10-x64_uefi.iso"', 'VBD connecting "Windows 10 (64-bit) (1)" to "Windows 10 (64-bit) (1) 0"', 'VBD connecting "CentOS Stream 9 (1)" to "CentOS Stream 9 (1) 0"'] network_metrics ['NPRI bond of 0 1'] pif_metrics ['bond0 on lcy2-dt29'] ``` Signed-off-by: Andrii Sultanov <[email protected]>
1 parent eed4377 commit 6617803

File tree

2 files changed

+186
-0
lines changed

2 files changed

+186
-0
lines changed

python3/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ 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
2627
$(IPROG) libexec/host-display $(DESTDIR)$(LIBEXECDIR)
2728
$(IPROG) libexec/link-vms-by-sr.py $(DESTDIR)$(LIBEXECDIR)
2829
$(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)