new classes based on json descriptor
This commit is contained in:
@ -1,223 +1,255 @@
|
||||
# this class file is for the cosmostat service
|
||||
import subprocess
|
||||
from LinkedList import *
|
||||
import json
|
||||
from typing import Dict, Any, List
|
||||
|
||||
# Global Class Vars
|
||||
global_max_length = 500
|
||||
debug_output = False
|
||||
|
||||
# import the component descriptor
|
||||
# this outlines how the component class works
|
||||
# each type of component has a "type"
|
||||
try:
|
||||
with open("component_descriptors.json", encoding="utf-8") as f:
|
||||
component_class_tree: List[Dict] = json.load(f)
|
||||
except FileNotFoundError as exc:
|
||||
raise RuntimeError("Descriptor file not found") from exc
|
||||
|
||||
component_types = [{"name": entry["name"], "multi_check": entry["multi_check"] == "True"} for entry in component_class_tree]
|
||||
|
||||
class Component:
|
||||
##########################################################################################
|
||||
# Base class for all system components. All instantiated objects need a child class
|
||||
# Class data:
|
||||
### name - name of the type of component, declared in the parent class
|
||||
### status
|
||||
### model_string - string with device info, declared in parent class
|
||||
### metric_name - name of the value being measured
|
||||
### current_value
|
||||
### historical_data - This will be a linked list used to generate a json when calling get_historical_data
|
||||
### for this to work, the function using these classes needs to update the values periodically
|
||||
#### historical_data = [
|
||||
#### {
|
||||
#### "timestamp": timestamp, # seconds since epoch
|
||||
#### "value": value
|
||||
#### },
|
||||
#### {
|
||||
#### "timestamp": timestamp,
|
||||
#### "value": value
|
||||
#### }
|
||||
#### ]
|
||||
|
||||
def __init__(self, name: str, model_string: str = None):
|
||||
# fail instantiation if critical data is missing
|
||||
if self.model_string is None:
|
||||
raise TypeError("Error - missing component model_string")
|
||||
if self.metric_name is None:
|
||||
raise TypeError("Error - missing component metric_name")
|
||||
if self.metric_value_command is None:
|
||||
raise TypeError("Error - missing component metric_value_command")
|
||||
if self.type is None:
|
||||
raise TypeError("Error - missing component type")
|
||||
if self.has_temp is None:
|
||||
raise TypeError("Error - missing temp data check")
|
||||
|
||||
# set up history list
|
||||
self.history_max_length = global_max_length
|
||||
self.historical_data = ValueHistory(self.history_max_length)
|
||||
self.history_start = self.historical_data.get_first_timestamp()
|
||||
self.update_value()
|
||||
if self.current_value is None:
|
||||
raise TypeError("Error - failed to read value")
|
||||
|
||||
# if temp data exists, handle it
|
||||
if self.has_temp:
|
||||
self.temp_history_data = ValueHistory(self.history_max_length)
|
||||
self.temp_history_start = self.temp_history_data.get_first_timestamp()
|
||||
self.current_temp = self.temp_history_data.get_current_value()
|
||||
else:
|
||||
self.temp_history_data = None
|
||||
|
||||
# instantiate other shared class variables
|
||||
def __init__(self, name: str, comp_type: str ):
|
||||
self.name = name
|
||||
self.current_value = self.historical_data.get_current_value()
|
||||
if self.has_temp:
|
||||
self.current_temp = self.temp_history_data.get_current_value()
|
||||
else:
|
||||
self.current_temp = None
|
||||
self.comment = f"This is a {self.type}, so we are measuring {self.metric_name}, currently at {self.current_value}"
|
||||
|
||||
# if nothing failed, the object is ready
|
||||
self.status = "ready"
|
||||
self.type = comp_type
|
||||
for component in component_class_tree:
|
||||
if component["name"] == self.type:
|
||||
COMPONENT_DESCRIPTORS = component
|
||||
# Load component type descriptor from class tree
|
||||
# COMPONENT_DESCRIPTORS = {d['type']: d for d in component_class_tree}
|
||||
descriptor = COMPONENT_DESCRIPTORS
|
||||
self._descriptor = descriptor
|
||||
if descriptor is None:
|
||||
raise ValueError(
|
||||
f"Component type '{comp_type}' is not defined in the "
|
||||
f"component descriptor tree."
|
||||
)
|
||||
# store static properties
|
||||
self.multi_check = self.is_multi()
|
||||
self._properties: Dict[str, str] = {}
|
||||
for key, command in descriptor.get('properties', {}).items():
|
||||
self._properties[key] = run_command(command, True)
|
||||
# build the description string
|
||||
self._description_template: str | None = descriptor.get("description")
|
||||
self.description = self._description_template.format(**self._properties)
|
||||
# initialize metrics
|
||||
self._metrics: Dict[str, str] = {}
|
||||
self.update_metrics()
|
||||
|
||||
def __str__(self):
|
||||
return (f"{self.__class__.__name__}: {self.name} "
|
||||
f"{self.model_string}")
|
||||
|
||||
def __del__(self):
|
||||
print(f"Deleting {self.type} component - {self.model_string}")
|
||||
self_string = (f"Component name: {self.name}, type: {self.type} - "
|
||||
f"{self.description}")
|
||||
return self_string
|
||||
|
||||
def __repr__(self):
|
||||
self_string = (f"Component name: {self.name}, type {self.type} - "
|
||||
f"{self.description}")
|
||||
return self_string
|
||||
|
||||
def get_info_key(self):
|
||||
def update_metrics(self):
|
||||
for key, command in self._descriptor.get('metrics', {}).items():
|
||||
self._metrics[key] = run_command(command, True)
|
||||
|
||||
# complex data type return
|
||||
def get_metrics(self, type = None):
|
||||
these_metrics = []
|
||||
if type == None:
|
||||
for name, value in self._metrics:
|
||||
these_metrics.append({"name": name, "value": value})
|
||||
else:
|
||||
for name, value in self._metrics:
|
||||
if name == type:
|
||||
these_metrics.append({"name": name, "value": value})
|
||||
result = {
|
||||
"name": self.name,
|
||||
"type": self.type,
|
||||
"model_string": self.model_string,
|
||||
"metric_name": self.metric_name
|
||||
"metrics": these_metrics
|
||||
}
|
||||
return result
|
||||
|
||||
def get_summary_key(self):
|
||||
# complex data type return
|
||||
def get_properties(self, type = None):
|
||||
these_properties = []
|
||||
if type == None:
|
||||
for name, value in self._properties.items():
|
||||
these_properties.append({"name": name, "value": value})
|
||||
else:
|
||||
for name, value in self._properties.items():
|
||||
if name == type:
|
||||
these_properties.append({"name": name, "value": value})
|
||||
result = {
|
||||
"name": self.name,
|
||||
"type": self.type,
|
||||
"current_value": self.current_value,
|
||||
"metric_name": self.metric_name,
|
||||
"model_string": self.model_string
|
||||
"properties": these_properties
|
||||
}
|
||||
return result
|
||||
|
||||
def update_value(self):
|
||||
#try:
|
||||
self.current_value = run_command(self.metric_value_command, True)
|
||||
self.historical_data.add(self.current_value)
|
||||
#except:
|
||||
|
||||
def update_temp_value(self):
|
||||
if has_temp:
|
||||
#try:
|
||||
self.current_temp = run_command(self.temp_value_command, True)
|
||||
self.temp_history_data.add(self.current_value)
|
||||
#except:
|
||||
else:
|
||||
return None
|
||||
# this gets the value of a specified property, type required
|
||||
def get_property(self, type):
|
||||
return self._properties[type]
|
||||
|
||||
|
||||
def get_history(self, count: int = global_max_length):
|
||||
if self.has_temp:
|
||||
result = {
|
||||
"value_metric": self.metric_name,
|
||||
"history_count": count,
|
||||
"history_data": self.historical_data.get_history(count), # reminder this is a LinkedList get_history
|
||||
"history_temp_data": self.temp_history_data.get_history(count)
|
||||
}
|
||||
else:
|
||||
result = {
|
||||
"value_metric": self.metric_name,
|
||||
"history_count": count,
|
||||
"history_data": self.historical_data.get_history(count) # same reminder here
|
||||
}
|
||||
# returns array of dicts for redis
|
||||
def get_metrics_keys(self):
|
||||
result = []
|
||||
for name, value in self._metrics.items():
|
||||
this_metric = {
|
||||
"name": self.name,
|
||||
"type": name,
|
||||
"metric": value
|
||||
}
|
||||
result.append(this_metric)
|
||||
return result
|
||||
|
||||
def get_properties_keys(self):
|
||||
result = []
|
||||
for name, value in self._properties.items():
|
||||
this_property = {
|
||||
"name": self.name,
|
||||
"property": name,
|
||||
"value": value
|
||||
}
|
||||
result.append(this_property)
|
||||
return result
|
||||
|
||||
# full data return
|
||||
def get_description(self):
|
||||
these_properties = []
|
||||
for name, value in self._metrics.items():
|
||||
these_properties.append({"name": name, "value": value})
|
||||
these_metrics = []
|
||||
for name, value in self._metrics.items():
|
||||
these_metrics.append({"name": name, "value": value})
|
||||
result = {
|
||||
"name": self.name,
|
||||
"type": self.type,
|
||||
"properties": these_properties,
|
||||
"metrics": these_metrics
|
||||
}
|
||||
return result
|
||||
|
||||
def is_multi(self):
|
||||
for component_type in component_types:
|
||||
if self.type == component_type["name"]:
|
||||
return component_type["multi_check"]
|
||||
return False
|
||||
|
||||
|
||||
############################################################
|
||||
# Component Class Types
|
||||
# There needs to be one of these for each monitored thing
|
||||
############################################################
|
||||
# Need to add:
|
||||
### temperatures
|
||||
### network + VPN
|
||||
### storage + ZFS
|
||||
### video cards
|
||||
### virtual machines
|
||||
|
||||
# CPU component class.
|
||||
class CPU(Component):
|
||||
|
||||
def __init__(self, name: str, is_virtual: bool = False):
|
||||
# Declare component type
|
||||
self.type = "CPU"
|
||||
# deal with temp later
|
||||
self.has_temp = False
|
||||
# no temp if VM
|
||||
#self.has_temp = not is_virtual
|
||||
#self.temp_value_command = "acpi -V | jc --acpi -p | jq '.[] | select(.type==\"Thermal\") | .temperature '"
|
||||
self.model_string = self.get_model_string()
|
||||
|
||||
# Initialize value
|
||||
self.metric_name = "1m_load"
|
||||
self.metric_value_command = "cat /proc/loadavg | awk '{print $1}'"
|
||||
self.current_value = run_command(self.metric_value_command, True)
|
||||
|
||||
# Complete instantiation
|
||||
super().__init__(name, self.model_string)
|
||||
|
||||
def get_model_string(self):
|
||||
# Get CPU Info
|
||||
model_string_command = "lscpu --json | jq -r '.lscpu[] | select(.field==\"Model name:\") | .data'"
|
||||
return run_command(model_string_command, True)
|
||||
|
||||
# RAM component class.
|
||||
class RAM(Component):
|
||||
|
||||
def __init__(self, name: str):
|
||||
# Declare component type
|
||||
self.type = "RAM"
|
||||
self.has_temp = False
|
||||
self.model_string = self.get_model_string()
|
||||
|
||||
# Initialize Value
|
||||
self.metric_name = "used_capacity_mb"
|
||||
self.metric_value_command = "free -m | grep Mem | awk '{print $3}'"
|
||||
self.current_value = run_command(self.metric_value_command, True)
|
||||
|
||||
# Complete instantiation
|
||||
super().__init__(name, self.model_string)
|
||||
|
||||
def get_model_string(self):
|
||||
# Check total system RAM
|
||||
bytes_total_command = "sudo lshw -json -c memory | jq -r '.[] | select(.description==\"System Memory\").size' "
|
||||
bytes_total = float(run_command(bytes_total_command, True))
|
||||
gb_total = round(bytes_total / 1073741824, 2)
|
||||
return f"Total Capacity: {gb_total}GB"
|
||||
|
||||
############################################################
|
||||
# System Class
|
||||
# A system is build from components
|
||||
############################################################
|
||||
|
||||
class System:
|
||||
# system variable declarations
|
||||
# keys to add: model and serial number
|
||||
static_key_variables = [
|
||||
{"name": "hostname", "command": "hostname"},
|
||||
{"name": "virt_string", "command": "systemd-detect-virt"}
|
||||
]
|
||||
dynamic_key_variables = [
|
||||
{"name": "uptime", "command": "uptime -p"},
|
||||
{"name": "timestamp", "command": "date '+%D %r'"},
|
||||
]
|
||||
# add components based on the class tree
|
||||
# component_types = [{"name": entry["name"], "multi_check": entry["multi_check"] == "True"} for entry in component_class_tree]
|
||||
|
||||
# instantiate new system
|
||||
def __init__(self, name: str):
|
||||
# the system needs a name
|
||||
self.name = name
|
||||
# system is built of other component objects
|
||||
if debug_output:
|
||||
print(f"System initializing, name {self.name}")
|
||||
# system contains an array of component objects
|
||||
self.components = []
|
||||
# other system properties
|
||||
self.sysvars = {}
|
||||
# either i do it here or i do it twice
|
||||
self.sysvars["is_virtual"] = self.check_for_virtual()
|
||||
# Let's build a system
|
||||
self.add_component(CPU("CPU", self.sysvars["is_virtual"]))
|
||||
self.add_component(RAM("RAM"))
|
||||
# initialize system properties and metrics dicts
|
||||
self._properties: Dict[str, str] = {}
|
||||
self._metrics: Dict[str, str] = {}
|
||||
# load static keys
|
||||
for static_key in self.static_key_variables:
|
||||
command = static_key["command"]
|
||||
result = run_command(command, True)
|
||||
if debug_output:
|
||||
print(f"Static key [{static_key["name"]}] - command [{command}] - output [{result}]")
|
||||
self._properties[static_key["name"]] = result
|
||||
# initialize live keys
|
||||
self.update_live_keys()
|
||||
# initialze components
|
||||
self.load_components()
|
||||
|
||||
# let's build system values
|
||||
self.check_values()
|
||||
# update only system dynamic keys
|
||||
def update_live_keys(self):
|
||||
for live_key in self.dynamic_key_variables:
|
||||
if live_key['command'] is not None:
|
||||
command = live_key['command']
|
||||
result = run_command(command, True)
|
||||
self._metrics[live_key['name']] = result
|
||||
if debug_output:
|
||||
print(f"Command {live_key["name"]} - [{command}] Result - [{result}]")
|
||||
|
||||
# update all dynamic keys, including components
|
||||
def update_system_state(self):
|
||||
self.update_live_keys()
|
||||
for component in self.components:
|
||||
component.update_metrics()
|
||||
|
||||
# check for components
|
||||
def load_components(self):
|
||||
for component in component_types:
|
||||
component_name = component["name"]
|
||||
multi_check = component["multi_check"]
|
||||
if multi_check:
|
||||
print("placeholder...")
|
||||
else:
|
||||
if debug_output:
|
||||
print(f"Creating component {component["name"]}")
|
||||
self.add_components(Component(component_name, component_name))
|
||||
|
||||
# Add a component to the system
|
||||
def add_component(self, component: Component):
|
||||
def add_components(self, component: Component):
|
||||
if debug_output:
|
||||
print(f"Component description: {component.description}")
|
||||
self.components.append(component)
|
||||
|
||||
# Get all components, optionally filtered by type
|
||||
def get_components(self, component_type: type = None):
|
||||
if component_type is None:
|
||||
return self.components
|
||||
return [c for c in self.components if isinstance(c, component_type)]
|
||||
|
||||
else:
|
||||
result = []
|
||||
for component in self.components:
|
||||
if component.type == component_type:
|
||||
result.append(component)
|
||||
if component.is_multi():
|
||||
return result
|
||||
else:
|
||||
return result[0]
|
||||
|
||||
def get_component_strings(self, component_type: type = None):
|
||||
if component_type is None:
|
||||
result = []
|
||||
for component in self.components:
|
||||
result.append(component.description)
|
||||
return result
|
||||
else:
|
||||
result = []
|
||||
for component in self.components:
|
||||
if component.type == component_type:
|
||||
result.append(component.description)
|
||||
if component.is_multi():
|
||||
return result
|
||||
else:
|
||||
return result[0]
|
||||
|
||||
# get component count
|
||||
def get_component_count(self):
|
||||
result = int(len(self.components))
|
||||
@ -225,64 +257,93 @@ class System:
|
||||
|
||||
def __str__(self):
|
||||
components_str = "\n".join(f" - {c}" for c in self.components)
|
||||
return f"System: {self.name}\n{components_str}"
|
||||
|
||||
# update metrics for all components
|
||||
def update_values(self):
|
||||
self.check_values()
|
||||
for component in self.components:
|
||||
component.update_value()
|
||||
|
||||
def check_for_virtual(self):
|
||||
check_if_vm_command = "systemd-detect-virt"
|
||||
check_if_vm = run_command(check_if_vm_command, True)
|
||||
if check_if_vm != "none":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def check_uptime(self):
|
||||
check_uptime_command = "uptime -p"
|
||||
system_uptime = run_command(check_uptime_command, True)
|
||||
return system_uptime
|
||||
|
||||
def check_timestamp(self):
|
||||
check_timestamp_command = "date '+%D %r'"
|
||||
system_timestamp = run_command(check_timestamp_command, True)
|
||||
return system_timestamp
|
||||
|
||||
def check_values(self):
|
||||
self.sysvars["uptime"] = self.check_uptime()
|
||||
self.sysvars["name"] = self.name
|
||||
self.sysvars["component_count"] = self.get_component_count()
|
||||
self.sysvars["timestamp"] = self.check_timestamp()
|
||||
|
||||
def get_sysvars(self):
|
||||
result = {}
|
||||
for sysvar in self.sysvars:
|
||||
result[f"{sysvar}"] = self.sysvars[f"{sysvar}"]
|
||||
return result
|
||||
return f"System hostname: {self.name}\nComponent Count: {self.get_component_count()}\n{components_str}"
|
||||
|
||||
# return both static and dynamic data
|
||||
def get_sysvars_summary_keys(self):
|
||||
result = []
|
||||
for sysvar in self.sysvars:
|
||||
system_type_string = f"sysvar['{sysvar}']"
|
||||
for name, value in self._properties.items():
|
||||
thisvar = {
|
||||
"type": "System Class Variable",
|
||||
"current_value": sysvar,
|
||||
"metric_name": system_type_string,
|
||||
"model_string": self.sysvars[sysvar]
|
||||
"name": "System Class Property",
|
||||
"type": name,
|
||||
"value": value
|
||||
}
|
||||
result.append(thisvar)
|
||||
for name, value in self._metrics.items():
|
||||
thisvar = {
|
||||
"name": "System Class Metric",
|
||||
"type": name,
|
||||
"value": value
|
||||
}
|
||||
result.append(thisvar)
|
||||
return result
|
||||
|
||||
# return list of all live metrics from system and properties
|
||||
def get_live_metrics(self):
|
||||
result = []
|
||||
for component_metric in self.get_component_metrics():
|
||||
result.append(component_metric)
|
||||
for system_metric in self.get_system_metrics():
|
||||
result.append(system_metric)
|
||||
return result
|
||||
|
||||
# return array of all component metrics
|
||||
def get_component_metrics(self):
|
||||
result = []
|
||||
for component in self.components:
|
||||
for metric in component.get_metrics_keys():
|
||||
result.append(metric)
|
||||
return result
|
||||
|
||||
# return array of all component metrics
|
||||
def get_component_properties(self):
|
||||
result = []
|
||||
for component in self.components:
|
||||
for metric in component.get_properties_keys():
|
||||
result.append(metric)
|
||||
return result
|
||||
|
||||
# return array of all system metrics
|
||||
def get_system_metrics(self):
|
||||
result = []
|
||||
for name, value in self._metrics.items():
|
||||
thisvar = {
|
||||
"name": "System",
|
||||
"type": name,
|
||||
"metric": value
|
||||
}
|
||||
result.append(thisvar)
|
||||
# add component count
|
||||
result.append({
|
||||
"name": "System",
|
||||
"type": "component_count",
|
||||
"metric": self.get_component_count()
|
||||
})
|
||||
return result
|
||||
|
||||
def get_system_properties(self):
|
||||
result = []
|
||||
for name, value in self._properties.items():
|
||||
if name == "virt_string":
|
||||
thisvar = {
|
||||
"name": "System",
|
||||
"property": name,
|
||||
"value": value == "none"
|
||||
}
|
||||
else:
|
||||
thisvar = {
|
||||
"name": "System",
|
||||
"property": name,
|
||||
"value": value
|
||||
}
|
||||
result.append(thisvar)
|
||||
return result
|
||||
|
||||
|
||||
############################################################
|
||||
# Helper Functions
|
||||
############################################################
|
||||
|
||||
|
||||
# subroutine to run a command, return stdout as array unless zero_only then return [0]
|
||||
def run_command(cmd, zero_only=False):
|
||||
# Run the command and capture the output
|
||||
|
||||
Reference in New Issue
Block a user