history: add support for auxiliary fields
Allow other components to register custom fields tracked and reported in job history. Signed-off-by: Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
parent
b6896c7c0a
commit
1dfbffb422
|
@ -6,7 +6,12 @@ from __future__ import annotations
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
from asyncio import Lock
|
from asyncio import Lock
|
||||||
from ..common import JobEvent, RequestType
|
from ..common import (
|
||||||
|
JobEvent,
|
||||||
|
RequestType,
|
||||||
|
HistoryFieldData,
|
||||||
|
FieldTracker
|
||||||
|
)
|
||||||
|
|
||||||
# Annotation imports
|
# Annotation imports
|
||||||
from typing import (
|
from typing import (
|
||||||
|
@ -15,18 +20,30 @@ from typing import (
|
||||||
Union,
|
Union,
|
||||||
Optional,
|
Optional,
|
||||||
Dict,
|
Dict,
|
||||||
List,
|
List
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..confighelper import ConfigHelper
|
from ..confighelper import ConfigHelper
|
||||||
from ..common import WebRequest
|
from ..common import WebRequest
|
||||||
from .database import MoonrakerDatabase as DBComp
|
from .database import MoonrakerDatabase as DBComp
|
||||||
from .job_state import JobState
|
from .job_state import JobState
|
||||||
from .file_manager.file_manager import FileManager
|
from .file_manager.file_manager import FileManager
|
||||||
|
Totals = Dict[str, Union[float, int]]
|
||||||
|
AuxTotals = List[Dict[str, Any]]
|
||||||
|
|
||||||
|
|
||||||
HIST_NAMESPACE = "history"
|
HIST_NAMESPACE = "history"
|
||||||
HIST_VERSION = 1
|
HIST_VERSION = 1
|
||||||
MAX_JOBS = 10000
|
MAX_JOBS = 10000
|
||||||
|
BASE_TOTALS = {
|
||||||
|
"total_jobs": 0,
|
||||||
|
"total_time": 0.,
|
||||||
|
"total_print_time": 0.,
|
||||||
|
"total_filament_used": 0.,
|
||||||
|
"longest_job": 0.,
|
||||||
|
"longest_print": 0.
|
||||||
|
}
|
||||||
|
|
||||||
class History:
|
class History:
|
||||||
def __init__(self, config: ConfigHelper) -> None:
|
def __init__(self, config: ConfigHelper) -> None:
|
||||||
|
@ -34,17 +51,13 @@ class History:
|
||||||
self.file_manager: FileManager = self.server.lookup_component(
|
self.file_manager: FileManager = self.server.lookup_component(
|
||||||
'file_manager')
|
'file_manager')
|
||||||
self.request_lock = Lock()
|
self.request_lock = Lock()
|
||||||
|
FieldTracker.class_init(self)
|
||||||
|
self.auxiliary_fields: List[HistoryFieldData] = []
|
||||||
database: DBComp = self.server.lookup_component("database")
|
database: DBComp = self.server.lookup_component("database")
|
||||||
self.job_totals: Dict[str, float] = database.get_item(
|
hist_info: Dict[str, Any]
|
||||||
"moonraker", "history.job_totals",
|
hist_info = database.get_item("moonraker", "history", {}).result()
|
||||||
{
|
self.job_totals: Totals = hist_info.get("job_totals", dict(BASE_TOTALS))
|
||||||
'total_jobs': 0,
|
self.aux_totals: AuxTotals = hist_info.get("aux_totals", [])
|
||||||
'total_time': 0.,
|
|
||||||
'total_print_time': 0.,
|
|
||||||
'total_filament_used': 0.,
|
|
||||||
'longest_job': 0.,
|
|
||||||
'longest_print': 0.
|
|
||||||
}).result()
|
|
||||||
|
|
||||||
self.server.register_event_handler(
|
self.server.register_event_handler(
|
||||||
"server:klippy_disconnect", self._handle_disconnect)
|
"server:klippy_disconnect", self._handle_disconnect)
|
||||||
|
@ -75,6 +88,7 @@ class History:
|
||||||
|
|
||||||
self.current_job: Optional[PrinterJob] = None
|
self.current_job: Optional[PrinterJob] = None
|
||||||
self.current_job_id: Optional[str] = None
|
self.current_job_id: Optional[str] = None
|
||||||
|
self.job_paused: bool = False
|
||||||
self.next_job_id: int = 0
|
self.next_job_id: int = 0
|
||||||
self.cached_job_ids = self.history_ns.keys().result()
|
self.cached_job_ids = self.history_ns.keys().result()
|
||||||
if self.cached_job_ids:
|
if self.cached_job_ids:
|
||||||
|
@ -189,30 +203,30 @@ class History:
|
||||||
|
|
||||||
return {"count": count, "jobs": jobs}
|
return {"count": count, "jobs": jobs}
|
||||||
|
|
||||||
async def _handle_job_totals(self,
|
async def _handle_job_totals(
|
||||||
web_request: WebRequest
|
self, web_request: WebRequest
|
||||||
) -> Dict[str, Dict[str, float]]:
|
) -> Dict[str, Union[Totals, AuxTotals]]:
|
||||||
return {'job_totals': self.job_totals}
|
return {
|
||||||
|
"job_totals": self.job_totals,
|
||||||
async def _handle_job_total_reset(self,
|
"auxiliary_totals": self.aux_totals
|
||||||
web_request: WebRequest,
|
|
||||||
) -> Dict[str, Dict[str, float]]:
|
|
||||||
if self.current_job is not None:
|
|
||||||
raise self.server.error(
|
|
||||||
"Job in progress, cannot reset totals")
|
|
||||||
last_totals = dict(self.job_totals)
|
|
||||||
self.job_totals = {
|
|
||||||
'total_jobs': 0,
|
|
||||||
'total_time': 0.,
|
|
||||||
'total_print_time': 0.,
|
|
||||||
'total_filament_used': 0.,
|
|
||||||
'longest_job': 0.,
|
|
||||||
'longest_print': 0.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async def _handle_job_total_reset(
|
||||||
|
self, web_request: WebRequest
|
||||||
|
) -> Dict[str, Union[Totals, AuxTotals]]:
|
||||||
|
if self.current_job is not None:
|
||||||
|
raise self.server.error("Job in progress, cannot reset totals")
|
||||||
|
last_totals = self.job_totals
|
||||||
|
self.job_totals = dict(BASE_TOTALS)
|
||||||
|
last_aux_totals = self.aux_totals
|
||||||
|
self._update_aux_totals(reset=True)
|
||||||
database: DBComp = self.server.lookup_component("database")
|
database: DBComp = self.server.lookup_component("database")
|
||||||
await database.insert_item(
|
await database.insert_item("moonraker", "history.job_totals", self.job_totals)
|
||||||
"moonraker", "history.job_totals", self.job_totals)
|
await database.insert_item("moonraker", "history.aux_totals", self.aux_totals)
|
||||||
return {'last_totals': last_totals}
|
return {
|
||||||
|
"last_totals": last_totals,
|
||||||
|
"last_auxiliary_totals": last_aux_totals
|
||||||
|
}
|
||||||
|
|
||||||
def _on_job_state_changed(
|
def _on_job_state_changed(
|
||||||
self,
|
self,
|
||||||
|
@ -220,6 +234,7 @@ class History:
|
||||||
prev_stats: Dict[str, Any],
|
prev_stats: Dict[str, Any],
|
||||||
new_stats: Dict[str, Any]
|
new_stats: Dict[str, Any]
|
||||||
) -> None:
|
) -> None:
|
||||||
|
self.job_paused = job_event == JobEvent.PAUSED
|
||||||
if job_event == JobEvent.STARTED:
|
if job_event == JobEvent.STARTED:
|
||||||
if self.current_job is not None:
|
if self.current_job is not None:
|
||||||
# Finish with the previous state
|
# Finish with the previous state
|
||||||
|
@ -251,6 +266,9 @@ class History:
|
||||||
self.current_job = job
|
self.current_job = job
|
||||||
self.current_job_id = job_id
|
self.current_job_id = job_id
|
||||||
self.grab_job_metadata()
|
self.grab_job_metadata()
|
||||||
|
for field in self.auxiliary_fields:
|
||||||
|
field.tracker.reset()
|
||||||
|
self.current_job.set_aux_data(self.auxiliary_fields)
|
||||||
self.history_ns[job_id] = job.get_stats()
|
self.history_ns[job_id] = job.get_stats()
|
||||||
self.cached_job_ids.append(job_id)
|
self.cached_job_ids.append(job_id)
|
||||||
self.next_job_id += 1
|
self.next_job_id += 1
|
||||||
|
@ -281,6 +299,7 @@ class History:
|
||||||
self.current_job.finish(status, pstats)
|
self.current_job.finish(status, pstats)
|
||||||
# Regrab metadata incase metadata wasn't parsed yet due to file upload
|
# Regrab metadata incase metadata wasn't parsed yet due to file upload
|
||||||
self.grab_job_metadata()
|
self.grab_job_metadata()
|
||||||
|
self.current_job.set_aux_data(self.auxiliary_fields)
|
||||||
self.save_current_job()
|
self.save_current_job()
|
||||||
self._update_job_totals()
|
self._update_job_totals()
|
||||||
logging.debug(
|
logging.debug(
|
||||||
|
@ -332,17 +351,30 @@ class History:
|
||||||
if self.current_job is None:
|
if self.current_job is None:
|
||||||
return
|
return
|
||||||
job = self.current_job
|
job = self.current_job
|
||||||
self.job_totals['total_jobs'] += 1
|
self._accumulate_total("total_jobs", 1)
|
||||||
self.job_totals['total_time'] += job.get('total_duration')
|
self._accumulate_total("total_time", job.total_duration)
|
||||||
self.job_totals['total_print_time'] += job.get('print_duration')
|
self._accumulate_total("total_print_time", job.print_duration)
|
||||||
self.job_totals['total_filament_used'] += job.get('filament_used')
|
self._accumulate_total("total_filament_used", job.filament_used)
|
||||||
self.job_totals['longest_job'] = max(
|
self._maximize_total("longest_job", job.total_duration)
|
||||||
self.job_totals['longest_job'], job.get('total_duration'))
|
self._maximize_total("longest_print", job.print_duration)
|
||||||
self.job_totals['longest_print'] = max(
|
self._update_aux_totals()
|
||||||
self.job_totals['longest_print'], job.get('print_duration'))
|
|
||||||
database: DBComp = self.server.lookup_component("database")
|
database: DBComp = self.server.lookup_component("database")
|
||||||
database.insert_item(
|
database.insert_item("moonraker", "history.job_totals", self.job_totals)
|
||||||
"moonraker", "history.job_totals", self.job_totals)
|
database.insert_item("moonraker", "history.aux_totals", self.aux_totals)
|
||||||
|
|
||||||
|
def _accumulate_total(self, field: str, val: Union[int, float]) -> None:
|
||||||
|
self.job_totals[field] += val
|
||||||
|
|
||||||
|
def _maximize_total(self, field: str, val: Union[int, float]) -> None:
|
||||||
|
self.job_totals[field] = max(self.job_totals[field], val)
|
||||||
|
|
||||||
|
def _update_aux_totals(self, reset: bool = False) -> None:
|
||||||
|
last_totals = self.aux_totals
|
||||||
|
self.aux_totals = [
|
||||||
|
field.get_totals(last_totals, reset)
|
||||||
|
for field in self.auxiliary_fields
|
||||||
|
if field.has_totals()
|
||||||
|
]
|
||||||
|
|
||||||
def send_history_event(self, evt_action: str) -> None:
|
def send_history_event(self, evt_action: str) -> None:
|
||||||
if self.current_job is None or self.current_job_id is None:
|
if self.current_job is None or self.current_job_id is None:
|
||||||
|
@ -363,6 +395,20 @@ class History:
|
||||||
)
|
)
|
||||||
return job
|
return job
|
||||||
|
|
||||||
|
def register_auxiliary_field(self, new_field: HistoryFieldData) -> None:
|
||||||
|
for field in self.auxiliary_fields:
|
||||||
|
if field == new_field:
|
||||||
|
raise self.server.error(
|
||||||
|
f"Field {field.name} already registered by "
|
||||||
|
f"provider {field.provider}."
|
||||||
|
)
|
||||||
|
self.auxiliary_fields.append(new_field)
|
||||||
|
|
||||||
|
def tracking_enabled(self, check_paused: bool) -> bool:
|
||||||
|
if self.current_job is None:
|
||||||
|
return False
|
||||||
|
return not self.job_paused if check_paused else True
|
||||||
|
|
||||||
def on_exit(self) -> None:
|
def on_exit(self) -> None:
|
||||||
jstate: JobState = self.server.lookup_component("job_state")
|
jstate: JobState = self.server.lookup_component("job_state")
|
||||||
last_ps = jstate.get_last_stats()
|
last_ps = jstate.get_last_stats()
|
||||||
|
@ -378,6 +424,7 @@ class PrinterJob:
|
||||||
self.status: str = "in_progress"
|
self.status: str = "in_progress"
|
||||||
self.start_time = time.time()
|
self.start_time = time.time()
|
||||||
self.total_duration: float = 0.
|
self.total_duration: float = 0.
|
||||||
|
self.auxiliary_data: List[Dict[str, Any]] = []
|
||||||
self.update_from_ps(data)
|
self.update_from_ps(data)
|
||||||
|
|
||||||
def finish(self,
|
def finish(self,
|
||||||
|
@ -401,10 +448,14 @@ class PrinterJob:
|
||||||
return
|
return
|
||||||
setattr(self, name, val)
|
setattr(self, name, val)
|
||||||
|
|
||||||
|
def set_aux_data(self, fields: List[HistoryFieldData]) -> None:
|
||||||
|
self.auxiliary_data = [field.as_dict() for field in fields]
|
||||||
|
|
||||||
def update_from_ps(self, data: Dict[str, Any]) -> None:
|
def update_from_ps(self, data: Dict[str, Any]) -> None:
|
||||||
for i in data:
|
for i in data:
|
||||||
if hasattr(self, i):
|
if hasattr(self, i):
|
||||||
setattr(self, i, data[i])
|
setattr(self, i, data[i])
|
||||||
|
|
||||||
|
|
||||||
def load_component(config: ConfigHelper) -> History:
|
def load_component(config: ConfigHelper) -> History:
|
||||||
return History(config)
|
return History(config)
|
||||||
|
|
Loading…
Reference in New Issue