sts-sponsors team mailing list archive
-
sts-sponsors team
-
Mailing list archive
-
Message #08373
[Merge] ~ack/maas-kpi:aggregate-stats into maas-kpi:master
Alberto Donato has proposed merging ~ack/maas-kpi:aggregate-stats into maas-kpi:master.
Commit message:
rework daily stat script to use dataclasses for stats
Requested reviews:
MAAS Committers (maas-committers)
For more details, see:
https://code.launchpad.net/~ack/maas-kpi/+git/maas-kpi/+merge/442831
--
Your team MAAS Committers is requested to review the proposed merge of ~ack/maas-kpi:aggregate-stats into maas-kpi:master.
diff --git a/maaskpi/dailystats.py b/maaskpi/dailystats.py
index e2bdab2..e034731 100644
--- a/maaskpi/dailystats.py
+++ b/maaskpi/dailystats.py
@@ -4,7 +4,7 @@ import gzip
import json
import re
from collections import defaultdict
-from dataclasses import dataclass, field
+from dataclasses import asdict, dataclass, field, fields
from datetime import date, datetime, timedelta
from pathlib import Path
from urllib.parse import parse_qs, urlparse
@@ -130,41 +130,46 @@ class DailyRequestsSeries(SeriesHelper):
autocommit = False
+@dataclass
+class DeploymentStats:
+ """Stats for a deployment."""
+
+ machines: int = 0
+ devices: int = 0
+ regionrack_controllers: int = 0
+ region_controllers: int = 0
+ rack_controllers: int = 0
+ workload_annotations_machines: int = 0
+ workload_annotations_total: int = 0
+ workload_annotations_unique_keys: int = 0
+ workload_annotations_unique_values: int = 0
+ # These might make sense as a different series, with lxd/virsh being
+ # encoded as a tag. On other hand, it's not something we want to filter on
+ # in Grafana.
+ vm_hosts_lxd_total: int = 0
+ vm_hosts_virsh_total: int = 0
+ vm_hosts_lxd_vms: int = 0
+ vm_hosts_virsh_vms: int = 0
+ vm_hosts_lxd_available_cores: int = 0
+ vm_hosts_virsh_available_cores: int = 0
+ vm_hosts_lxd_available_over_cores: int = 0
+ vm_hosts_virsh_available_over_cores: int = 0
+ vm_hosts_lxd_utilized_cores: int = 0
+ vm_hosts_virsh_utilized_cores: int = 0
+ vm_hosts_lxd_available_memory: int = 0
+ vm_hosts_virsh_available_memory: int = 0
+ vm_hosts_lxd_available_over_memory: int = 0
+ vm_hosts_virsh_available_over_memory: int = 0
+ vm_hosts_lxd_utilized_memory: int = 0
+ vm_hosts_virsh_utilized_memory: int = 0
+ subnets_v4: int = 0
+ subnets_v6: int = 0
+
+
class DailyStatsSeries(SeriesHelper):
class Meta:
series_name = "maas.daily_stats"
- fields = [
- "machines",
- "devices",
- "regionrack_controllers",
- "region_controllers",
- "rack_controllers",
- "workload_annotations_machines",
- "workload_annotations_total",
- "workload_annotations_unique_keys",
- "workload_annotations_unique_values",
- # These might make sense as a different series, with
- # lxd/virsh being encoded as a tag. On other hand, it's not
- # something we want to filter on in Grafana.
- "vm_hosts_lxd_total",
- "vm_hosts_virsh_total",
- "vm_hosts_lxd_vms",
- "vm_hosts_virsh_vms",
- "vm_hosts_lxd_available_cores",
- "vm_hosts_virsh_available_cores",
- "vm_hosts_lxd_available_over_cores",
- "vm_hosts_virsh_available_over_cores",
- "vm_hosts_lxd_utilized_cores",
- "vm_hosts_virsh_utilized_cores",
- "vm_hosts_lxd_available_memory",
- "vm_hosts_virsh_available_memory",
- "vm_hosts_lxd_available_over_memory",
- "vm_hosts_virsh_available_over_memory",
- "vm_hosts_lxd_utilized_memory",
- "vm_hosts_virsh_utilized_memory",
- "subnets_v4",
- "subnets_v6",
- ]
+ fields = [field.name for field in fields(DeploymentStats)]
tags = ["maas_version", "uuid"]
autocommit = False
@@ -185,68 +190,127 @@ def get_date(name):
return datetime.strptime(date_string, "%Y%m%d").date()
-def normalize_data_dict(normalized):
- """Normalize the data dict to make it easier to work with.
+def get_entry_stats(data) -> DeploymentStats:
+ """Return stats for an entry."""
- Older versions of MAAS might not have all the data, or have keys
- with different names. This function adds the missing keys and
- renames the old keys, so that the callsites can work with the latest
- version of the data dict schema.
- """
- if "workload_annotations" not in normalized:
- normalized["workload_annotations"] = {
- "annotated_machines": 0,
- "total_annotations": 0,
- "unique_keys": 0,
- "unique_values": 0,
- }
- if "vm_hosts" not in normalized:
- normalized["vm_hosts"] = {}
- for vm_type in ["lxd", "virsh"]:
- normalized["vm_hosts"][vm_type] = {
- "vm_hosts": 0,
- "vms": 0,
- "available_resources": {
- "cores": 0,
- "memory": 0,
- "local_storage": 0,
- "over_cores": 0,
- "over_memory": 0,
- },
- "utilized_resources": {
- "cores": 0,
- "memory": 0,
- "storage": 0,
- },
- }
- if "network_stats" not in normalized:
- normalized["network_stats"] = {
- "spaces": 0,
- "fabrics": 0,
- "vlans": 0,
- "subnets_v4": 0,
- "subnets_v6": 0,
- }
- if "bmcs" not in normalized:
- normalized["bmcs"] = {
- "auto_detected": {},
- "user_created": {},
- "unknown": {},
- }
- normalized["vm_hosts"]["lxd"]["available_resources"]["over_cores"] = int(
- normalized["vm_hosts"]["lxd"]["available_resources"]["over_cores"]
- )
- normalized["vm_hosts"]["virsh"]["available_resources"]["over_cores"] = int(
- normalized["vm_hosts"]["virsh"]["available_resources"]["over_cores"]
- )
- normalized["vm_hosts"]["lxd"]["available_resources"]["over_memory"] = int(
- normalized["vm_hosts"]["lxd"]["available_resources"]["over_memory"]
- )
- normalized["vm_hosts"]["virsh"]["available_resources"]["over_memory"] = int(
- normalized["vm_hosts"]["virsh"]["available_resources"]["over_memory"]
+ def get(*keys: str) -> int:
+ stats = data
+ for key in keys:
+ try:
+ stats = stats[key]
+ except (KeyError, TypeError):
+ # either the key is not found, or data is not a dict
+ return 0
+ try:
+ return int(stats)
+ except TypeError:
+ return 0
+
+ return DeploymentStats(
+ machines=get("nodes", "machines"),
+ devices=get("nodes", "devices"),
+ regionrack_controllers=get("controllers", "regionracks"),
+ region_controllers=get("controllers", "regions"),
+ rack_controllers=get("controllers", "racks"),
+ vm_hosts_lxd_total=get("vm_hosts", "lxd", "vm_hosts"),
+ vm_hosts_virsh_total=get("vm_hosts", "virsh", "vm_hosts"),
+ vm_hosts_lxd_vms=get("vm_hosts", "lxd", "vms"),
+ vm_hosts_virsh_vms=get("vm_hosts", "virsh", "vms"),
+ vm_hosts_lxd_available_cores=get(
+ "vm_hosts", "lxd", "available_resources", "cores"
+ ),
+ vm_hosts_virsh_available_cores=get(
+ "vm_hosts",
+ "virsh",
+ "available_resources",
+ "cores",
+ ),
+ vm_hosts_lxd_available_over_cores=get(
+ "vm_hosts",
+ "lxd",
+ "available_resources",
+ "over_cores",
+ ),
+ vm_hosts_virsh_available_over_cores=get(
+ "vm_hosts",
+ "virsh",
+ "available_resources",
+ "over_cores",
+ ),
+ vm_hosts_lxd_utilized_cores=get(
+ "vm_hosts",
+ "lxd",
+ "utilized_resources",
+ "cores",
+ ),
+ vm_hosts_virsh_utilized_cores=get(
+ "vm_hosts", "virsh", "utilized_resources", "cores"
+ ),
+ vm_hosts_lxd_available_memory=get(
+ "vm_hosts",
+ "lxd",
+ "available_resources",
+ "memory",
+ ),
+ vm_hosts_virsh_available_memory=get(
+ "vm_hosts",
+ "virsh",
+ "available_resources",
+ "memory",
+ ),
+ vm_hosts_lxd_available_over_memory=get(
+ "vm_hosts",
+ "lxd",
+ "available_resources",
+ "over_memory",
+ ),
+ vm_hosts_virsh_available_over_memory=get(
+ "vm_hosts",
+ "virsh",
+ "available_resources",
+ "over_memory",
+ ),
+ vm_hosts_lxd_utilized_memory=get(
+ "vm_hosts",
+ "lxd",
+ "utilized_resources",
+ "memory",
+ ),
+ vm_hosts_virsh_utilized_memory=get(
+ "vm_hosts",
+ "virsh",
+ "utilized_resources",
+ "memory",
+ ),
+ workload_annotations_machines=get(
+ "workload_annotations",
+ "annotated_machines",
+ ),
+ workload_annotations_total=get(
+ "workload_annotations",
+ "total_annotations",
+ ),
+ workload_annotations_unique_keys=get(
+ "workload_annotations",
+ "unique_keys",
+ ),
+ workload_annotations_unique_values=get(
+ "workload_annotations",
+ "unique_values",
+ ),
+ subnets_v4=get("network_stats", "subnets_v4"),
+ subnets_v6=get("network_stats", "subnets_v6"),
)
- return normalized
+
+def get_bmc_stats(data):
+ """Return and normalize bmc stats."""
+ default = {
+ "auto_detected": {},
+ "user_created": {},
+ "unknown": {},
+ }
+ return data.get("bmcs", default)
class DailyStats:
@@ -287,10 +351,14 @@ class DailyStats:
image_arch = parts[5]
self.image_entries[item.uuid].add((item.version, image_series, image_arch))
query = parse_qs(urlparse(item.path).query)
- if "data" in query:
- self.with_data += 1
- raw_data = base64.b64decode(query["data"][0]).decode()
- self.entries[item.uuid]["data"] = json.loads(json.loads(raw_data))
+ if "data" not in query:
+ return
+
+ self.with_data += 1
+ raw_data = base64.b64decode(query["data"][0]).decode()
+ data = json.loads(json.loads(raw_data))
+ self.entries[item.uuid]["stats"] = get_entry_stats(data)
+ self.entries[item.uuid]["bmc_stats"] = get_bmc_stats(data)
def create_series(self):
"""Create DailyStatsSeries measurements based on the internal data."""
@@ -299,92 +367,31 @@ class DailyStats:
f"without uuids, {self.with_data} with data"
)
timestamp = get_nanosecond_timestamp(self.day)
- for uuid, info in sorted(self.entries.items()):
+ for uuid, info in self.entries.items():
DailyRequestsSeries(
time=timestamp,
maas_version=info["version"],
uuid=uuid,
field_uuid=uuid,
)
-
- if "data" not in info:
- continue
- data = normalize_data_dict(dict(info.pop("data")))
- workload_annotations = data["workload_annotations"]
- nodes = data["nodes"]
- controllers = data["controllers"]
- vm_hosts = data["vm_hosts"]
- network = data["network_stats"]
- for creation_method, bmcs in data["bmcs"].items():
- for driver_name, count in bmcs.items():
- DailyPowerDriverSeries(
- time=timestamp,
- maas_version=info["version"],
- uuid=uuid,
- count=count,
- power_driver_name=driver_name,
- power_driver_creation=creation_method,
- )
- DailyStatsSeries(
- time=timestamp,
- maas_version=info["version"],
- uuid=uuid,
- machines=nodes["machines"],
- devices=nodes["devices"],
- regionrack_controllers=controllers["regionracks"],
- region_controllers=controllers["regions"],
- rack_controllers=controllers["racks"],
- vm_hosts_lxd_total=vm_hosts["lxd"]["vm_hosts"],
- vm_hosts_virsh_total=vm_hosts["virsh"]["vm_hosts"],
- vm_hosts_lxd_vms=vm_hosts["lxd"]["vms"],
- vm_hosts_virsh_vms=vm_hosts["virsh"]["vms"],
- vm_hosts_lxd_available_cores=vm_hosts["lxd"]["available_resources"][
- "cores"
- ],
- vm_hosts_virsh_available_cores=vm_hosts["virsh"]["available_resources"][
- "cores"
- ],
- vm_hosts_lxd_available_over_cores=vm_hosts["lxd"][
- "available_resources"
- ]["over_cores"],
- vm_hosts_virsh_available_over_cores=vm_hosts["virsh"][
- "available_resources"
- ]["over_cores"],
- vm_hosts_lxd_utilized_cores=vm_hosts["lxd"]["utilized_resources"][
- "cores"
- ],
- vm_hosts_virsh_utilized_cores=vm_hosts["virsh"]["utilized_resources"][
- "cores"
- ],
- vm_hosts_lxd_available_memory=vm_hosts["lxd"]["available_resources"][
- "memory"
- ],
- vm_hosts_virsh_available_memory=vm_hosts["virsh"][
- "available_resources"
- ]["memory"],
- vm_hosts_lxd_available_over_memory=vm_hosts["lxd"][
- "available_resources"
- ]["over_memory"],
- vm_hosts_virsh_available_over_memory=vm_hosts["virsh"][
- "available_resources"
- ]["over_memory"],
- vm_hosts_lxd_utilized_memory=vm_hosts["lxd"]["utilized_resources"][
- "memory"
- ],
- vm_hosts_virsh_utilized_memory=vm_hosts["virsh"]["utilized_resources"][
- "memory"
- ],
- workload_annotations_machines=workload_annotations[
- "annotated_machines"
- ],
- workload_annotations_total=workload_annotations["total_annotations"],
- workload_annotations_unique_keys=workload_annotations["unique_keys"],
- workload_annotations_unique_values=workload_annotations[
- "unique_values"
- ],
- subnets_v4=network["subnets_v4"],
- subnets_v6=network["subnets_v6"],
- )
+ if bmc_stats := info.get("bmc_stats"):
+ for creation_method, bmcs in bmc_stats.items():
+ for driver_name, count in bmcs.items():
+ DailyPowerDriverSeries(
+ time=timestamp,
+ maas_version=info["version"],
+ uuid=uuid,
+ count=count,
+ power_driver_name=driver_name,
+ power_driver_creation=creation_method,
+ )
+ if stats := info.get("stats"):
+ DailyStatsSeries(
+ time=timestamp,
+ maas_version=info["version"],
+ uuid=uuid,
+ **asdict(stats),
+ )
for uuid, image_requests in sorted(self.image_entries.items()):
for maas_version, series, architecture in image_requests:
@@ -583,16 +590,16 @@ class DailyStatsCollector(Collector):
log_files = LogFiles(swift, days, cache_dir)
log_files.init()
if not log_files.hosts:
- print("No hosts!")
+ print("No logs hosts found!")
for host_name, host in log_files.hosts.items():
- print(f"{host_name}: {len(host.file_names)}")
+ print(f"Collected {len(host.file_names)} logs files from {host_name}")
# Ignore the first day, since it's most likely incomplete.
log_files.get_next_day()
day_stats = None
while True:
items = log_files.get_next_day()
- print(f"{log_files.current_day}: {len(items)}")
+ print(f"Stats for {log_files.current_day}: {len(items)}")
if not items:
break
if day_stats is not None:
Follow ups