From 092370d6c22f177b28efed8dff5a0737f93b0079 Mon Sep 17 00:00:00 2001 From: Geoffrey Yu Date: Fri, 29 Mar 2024 23:17:11 -0400 Subject: [PATCH] Render current system status and transitioning blueprint (#484) * Return next blueprint and current status * Display next provisioning * Display added tables --- src/brad/planner/abstract.py | 3 +++ src/brad/ui/manager_impl.py | 26 ++++++++++++++++++- src/brad/ui/models.py | 9 +++++++ ui/src/App.jsx | 5 +++- ui/src/components/BlueprintView.jsx | 12 +++++++++ ui/src/components/Header.jsx | 20 +++++++++++---- ui/src/components/PhysDbView.jsx | 33 +++++++++++++++++++++++++ ui/src/components/styles/Header.css | 13 ++++++++++ ui/src/components/styles/PhysDbView.css | 4 +++ 9 files changed, 118 insertions(+), 7 deletions(-) diff --git a/src/brad/planner/abstract.py b/src/brad/planner/abstract.py index a82af7a1..f2edf09c 100644 --- a/src/brad/planner/abstract.py +++ b/src/brad/planner/abstract.py @@ -179,3 +179,6 @@ async def _notify_new_blueprint( for callback in self._callbacks: tasks.append(asyncio.create_task(callback(blueprint, score, trigger))) await asyncio.gather(*tasks) + + def replan_in_progress(self) -> bool: + return self._replan_in_progress diff --git a/src/brad/ui/manager_impl.py b/src/brad/ui/manager_impl.py index 2a65170e..93eef308 100644 --- a/src/brad/ui/manager_impl.py +++ b/src/brad/ui/manager_impl.py @@ -12,6 +12,7 @@ from brad.blueprint import Blueprint from brad.blueprint.table import Table from brad.blueprint.manager import BlueprintManager +from brad.planner.abstract import BlueprintPlanner from brad.config.engine import Engine from brad.config.file import ConfigFile from brad.daemon.monitor import Monitor @@ -24,6 +25,7 @@ DisplayableVirtualEngine, VirtualInfrastructure, DisplayableTable, + Status, ) from brad.daemon.front_end_metrics import FrontEndMetric from brad.daemon.system_event_logger import SystemEventLogger, SystemEventRecord @@ -43,6 +45,7 @@ def __init__( self.monitor = monitor self.blueprint_mgr = blueprint_mgr self.system_event_logger = system_event_logger + self.planner: Optional[BlueprintPlanner] = None async def serve_forever(self) -> None: global manager # pylint: disable=global-statement @@ -169,7 +172,20 @@ def get_system_state(filter_tables_for_demo: bool = False) -> SystemState: if t.name in txn_tables: t.is_writer = True virtual_infra = VirtualInfrastructure(engines=[vdbe1, vdbe2]) - system_state = SystemState(virtual_infra=virtual_infra, blueprint=dbp) + + status = _determine_current_status(manager) + if status is Status.Transitioning: + next_blueprint = manager.blueprint_mgr.get_transition_metadata().next_blueprint + assert next_blueprint is not None + next_dbp = DisplayableBlueprint.from_blueprint(next_blueprint) + else: + next_dbp = None + system_state = SystemState( + status=status, + virtual_infra=virtual_infra, + blueprint=dbp, + next_blueprint=next_dbp, + ) _add_reverse_mapping_temp(system_state) return system_state @@ -222,6 +238,14 @@ def _add_reverse_mapping_temp(system_state: SystemState) -> None: table.mapped_to.append(veng_name) +def _determine_current_status(manager_impl: UiManagerImpl) -> Status: + if manager_impl.planner is not None and manager_impl.planner.replan_in_progress(): + return Status.Planning + if manager_impl.blueprint_mgr.get_transition_metadata().next_blueprint is not None: + return Status.Transitioning + return Status.Running + + @app.get("/api/1/system_events") def get_system_events() -> List[SystemEventRecord]: assert manager is not None diff --git a/src/brad/ui/models.py b/src/brad/ui/models.py index 9764beb0..aeb7b319 100644 --- a/src/brad/ui/models.py +++ b/src/brad/ui/models.py @@ -1,3 +1,4 @@ +import enum from typing import List, Dict, Optional from pydantic import BaseModel, AwareDatetime @@ -95,6 +96,14 @@ class VirtualInfrastructure(BaseModel): engines: List[DisplayableVirtualEngine] +class Status(enum.Enum): + Running = "running" + Planning = "planning" + Transitioning = "transitioning" + + class SystemState(BaseModel): + status: Status virtual_infra: VirtualInfrastructure blueprint: DisplayableBlueprint + next_blueprint: Optional[DisplayableBlueprint] diff --git a/ui/src/App.jsx b/ui/src/App.jsx index 6d299ed3..4e4c2060 100644 --- a/ui/src/App.jsx +++ b/ui/src/App.jsx @@ -12,8 +12,10 @@ const REFRESH_INTERVAL_MS = 30 * 1000; function App() { const [systemState, setSystemState] = useState({ + status: "running", blueprint: null, virtual_infra: null, + next_blueprint: null, }); const [highlight, setHighlight] = useState({ hoverEngine: null, @@ -115,7 +117,7 @@ function App() { return ( <> -
+

Data Infrastructure

@@ -129,6 +131,7 @@ function App() { /> ))}
diff --git a/ui/src/components/Header.jsx b/ui/src/components/Header.jsx index ec7be3a1..21832f51 100644 --- a/ui/src/components/Header.jsx +++ b/ui/src/components/Header.jsx @@ -1,28 +1,38 @@ import "./styles/Header.css"; import bradLogo from "../assets/brad_logo.png"; +function statusToDisplay(status) { + if (status === "transitioning") { + return "Transitioning..."; + } else if (status === "planning") { + return "Running planner..."; + } else { + return "Running"; + } +} + function StatusText({ status, schema }) { if (!!schema) { return (
- {status} ({schema}) + {statusToDisplay(status)} ({schema})
); } else { - return
{status}
; + return
{statusToDisplay(status)}
; } } function StatusIndicator({ status, schema }) { return (
-
+
); } -function Header() { +function Header({ status }) { return (
@@ -34,7 +44,7 @@ function Header() { BRAD Dashboard
- +
); diff --git a/ui/src/components/PhysDbView.jsx b/ui/src/components/PhysDbView.jsx index 406ef9d3..509106a6 100644 --- a/ui/src/components/PhysDbView.jsx +++ b/ui/src/components/PhysDbView.jsx @@ -7,6 +7,20 @@ import { sortTablesToHoist, } from "../highlight"; +function addedTables(tables, nextEngine) { + if (nextEngine == null) return []; + const added = []; + const currTableSet = new Set(); + for (const currTable of tables) { + currTableSet.add(currTable.name); + } + for (const table of nextEngine.tables) { + if (currTableSet.has(table.name)) continue; + added.push(table); + } + return added; +} + function PhysDbView({ name, provisioning, @@ -14,9 +28,11 @@ function PhysDbView({ highlight, onTableHoverEnter, onTableHoverExit, + nextEngine, }) { const physDbName = name; const sortedTables = sortTablesToHoist(highlight, physDbName, false, tables); + const addedTablesList = addedTables(tables, nextEngine); return (
{name}
{provisioning}
+ {nextEngine && ( +
+ {nextEngine.provisioning ? "→ " : ""} + {nextEngine.provisioning} +
+ )}
{sortedTables.map(({ name, is_writer, mapped_to }) => ( ))} + {addedTablesList.map(({ name, is_writer }) => ( + {}} + onTableHoverExit={() => {}} + /> + ))}
); diff --git a/ui/src/components/styles/Header.css b/ui/src/components/styles/Header.css index 46938c99..a423516a 100644 --- a/ui/src/components/styles/Header.css +++ b/ui/src/components/styles/Header.css @@ -62,6 +62,19 @@ height: 12px; border-radius: 50%; margin: -2px 15px 0 0; +} + +.header-status-icon.running { background-color: #51a965; box-shadow: 0 0 5px #51a965; } + +.header-status-icon.transitioning { + background-color: #ec8405; + box-shadow: 0 0 5px #ec8405; +} + +.header-status-icon.planning { + background-color: #517aa9; + box-shadow: 0 0 5px #517aa9; +} diff --git a/ui/src/components/styles/PhysDbView.css b/ui/src/components/styles/PhysDbView.css index 43969642..01d82790 100644 --- a/ui/src/components/styles/PhysDbView.css +++ b/ui/src/components/styles/PhysDbView.css @@ -13,6 +13,10 @@ height: 30px; } +.physdb-view-prov.transition { + color: #888; +} + .physdb-view.dim .db-cylinder, .physdb-view.dim .physdb-view-prov { opacity: 0.3;