diff --git a/packages/ott-vis-datasource/tsconfig.json b/packages/ott-vis-datasource/tsconfig.json index 3b79176e5..3e04c4bff 100644 --- a/packages/ott-vis-datasource/tsconfig.json +++ b/packages/ott-vis-datasource/tsconfig.json @@ -2,6 +2,8 @@ "extends": "./.config/tsconfig.json", "compilerOptions": { "strict": true, - "typeRoots": ["../../node_modules/@types"] + "typeRoots": ["../../node_modules/@types"], + "esModuleInterop": true, + "jsx": "react" } } diff --git a/packages/ott-vis/src/aggregate.spec.ts b/packages/ott-vis/src/aggregate.spec.ts new file mode 100644 index 000000000..f996bf562 --- /dev/null +++ b/packages/ott-vis/src/aggregate.spec.ts @@ -0,0 +1,96 @@ +import { SystemState } from "types"; +import { aggMonolithRooms, countRoomClients } from "./aggregate"; + +const sampleSystemState: SystemState = [ + { + id: "154d9d41-128c-45ab-83d8-28661882c9e3", + region: "ewr", + monoliths: [ + { + id: "2bd5e4a7-14f6-4da4-bedd-72946864a7bf", + region: "ewr", + rooms: [ + { name: "foo", clients: 2 }, + { name: "bar", clients: 0 }, + ], + }, + { + id: "419580cb-f576-4314-8162-45340c94bae1", + region: "ewr", + rooms: [{ name: "baz", clients: 3 }], + }, + { + id: "0c85b46e-d343-46a3-ae4f-5f2aa1a8bdac", + region: "cdg", + rooms: [{ name: "qux", clients: 0 }], + }, + ], + }, + { + id: "c91d183c-980e-4160-b196-43658148f469", + region: "ewr", + monoliths: [ + { + id: "2bd5e4a7-14f6-4da4-bedd-72946864a7bf", + region: "ewr", + rooms: [ + { name: "foo", clients: 1 }, + { name: "bar", clients: 2 }, + ], + }, + { + id: "419580cb-f576-4314-8162-45340c94bae1", + region: "ewr", + rooms: [{ name: "baz", clients: 0 }], + }, + { + id: "0c85b46e-d343-46a3-ae4f-5f2aa1a8bdac", + region: "cdg", + rooms: [{ name: "qux", clients: 0 }], + }, + ], + }, + { + id: "5a2e3b2d-f27b-4e3d-9b59-c921442f7ff0", + region: "cdg", + monoliths: [ + { + id: "2bd5e4a7-14f6-4da4-bedd-72946864a7bf", + region: "ewr", + rooms: [ + { name: "foo", clients: 0 }, + { name: "bar", clients: 0 }, + ], + }, + { + id: "419580cb-f576-4314-8162-45340c94bae1", + region: "ewr", + rooms: [{ name: "baz", clients: 0 }], + }, + { + id: "0c85b46e-d343-46a3-ae4f-5f2aa1a8bdac", + region: "cdg", + rooms: [{ name: "qux", clients: 4 }], + }, + ], + }, +]; + +describe("aggregation helpers", () => { + it("counts room clients", () => { + expect(countRoomClients(sampleSystemState)).toEqual({ + foo: 3, + bar: 2, + baz: 3, + qux: 4, + }); + }); + + it("aggregates monolith rooms", () => { + expect(aggMonolithRooms(sampleSystemState)).toEqual({ + "2bd5e4a7-14f6-4da4-bedd-72946864a7bf": ["foo", "bar"], + "419580cb-f576-4314-8162-45340c94bae1": ["baz"], + "0c85b46e-d343-46a3-ae4f-5f2aa1a8bdac": ["qux"], + }); + }); +}); diff --git a/packages/ott-vis/src/aggregate.ts b/packages/ott-vis/src/aggregate.ts new file mode 100644 index 000000000..5346ed98a --- /dev/null +++ b/packages/ott-vis/src/aggregate.ts @@ -0,0 +1,36 @@ +import { SystemState } from "types"; + +/** + * Builds a map of room names to the number of clients in each room from the room state. + * @param state + */ +export function countRoomClients(state: SystemState): Record { + const roomClients: Record = {}; + for (const balancer of state) { + for (const monolith of balancer.monoliths) { + for (const room of monolith.rooms) { + roomClients[room.name] = (roomClients[room.name] ?? 0) + room.clients; + } + } + } + return roomClients; +} + +/** + * Collect a map of monolith ids to room names. + * @param state + * @returns + */ +export function aggMonolithRooms(state: SystemState): Record { + const roomClients: Record> = {}; + for (const balancer of state) { + for (const monolith of balancer.monoliths) { + for (const room of monolith.rooms) { + const s = roomClients[monolith.id] ?? new Set(); + s.add(room.name); + roomClients[monolith.id] = s; + } + } + } + return Object.fromEntries(Object.entries(roomClients).map(([k, v]) => [k, Array.from(v)])); +} diff --git a/packages/ott-vis/src/components/CorePanel.tsx b/packages/ott-vis/src/components/CorePanel.tsx index f53d30dd4..060d05ac4 100644 --- a/packages/ott-vis/src/components/CorePanel.tsx +++ b/packages/ott-vis/src/components/CorePanel.tsx @@ -1,6 +1,6 @@ import React from "react"; import { PanelProps } from "@grafana/data"; -import type { CoreOptions } from "types"; +import type { CoreOptions, SystemState } from "types"; import { css, cx } from "@emotion/css"; import { useStyles2 } from "@grafana/ui"; import GlobalView from "./views/GlobalView"; @@ -28,14 +28,89 @@ const getStyles = () => { }; }; +const sampleSystemState: SystemState = [ + { + id: "154d9d41-128c-45ab-83d8-28661882c9e3", + region: "ewr", + monoliths: [ + { + id: "2bd5e4a7-14f6-4da4-bedd-72946864a7bf", + region: "ewr", + rooms: [ + { name: "foo", clients: 2 }, + { name: "bar", clients: 0 }, + ], + }, + { + id: "419580cb-f576-4314-8162-45340c94bae1", + region: "ewr", + rooms: [{ name: "baz", clients: 3 }], + }, + { + id: "0c85b46e-d343-46a3-ae4f-5f2aa1a8bdac", + region: "cdg", + rooms: [{ name: "qux", clients: 0 }], + }, + ], + }, + { + id: "c91d183c-980e-4160-b196-43658148f469", + region: "ewr", + monoliths: [ + { + id: "2bd5e4a7-14f6-4da4-bedd-72946864a7bf", + region: "ewr", + rooms: [ + { name: "foo", clients: 1 }, + { name: "bar", clients: 2 }, + ], + }, + { + id: "419580cb-f576-4314-8162-45340c94bae1", + region: "ewr", + rooms: [{ name: "baz", clients: 0 }], + }, + { + id: "0c85b46e-d343-46a3-ae4f-5f2aa1a8bdac", + region: "cdg", + rooms: [{ name: "qux", clients: 0 }], + }, + ], + }, + { + id: "5a2e3b2d-f27b-4e3d-9b59-c921442f7ff0", + region: "cdg", + monoliths: [ + { + id: "2bd5e4a7-14f6-4da4-bedd-72946864a7bf", + region: "ewr", + rooms: [ + { name: "foo", clients: 0 }, + { name: "bar", clients: 0 }, + ], + }, + { + id: "419580cb-f576-4314-8162-45340c94bae1", + region: "ewr", + rooms: [{ name: "baz", clients: 0 }], + }, + { + id: "0c85b46e-d343-46a3-ae4f-5f2aa1a8bdac", + region: "cdg", + rooms: [{ name: "qux", clients: 4 }], + }, + ], + }, +]; + export const CorePanel: React.FC = ({ options, data, width, height }) => { const styles = useStyles2(getStyles); let view; if (options.view === "global") { - view = ; + view = ; } else if (options.view === "region") { - view = ; + view = ; } else { view =
Invalid view
; } diff --git a/packages/ott-vis/src/components/views/GlobalView.tsx b/packages/ott-vis/src/components/views/GlobalView.tsx index 63d2fed9d..709e1436d 100644 --- a/packages/ott-vis/src/components/views/GlobalView.tsx +++ b/packages/ott-vis/src/components/views/GlobalView.tsx @@ -1,6 +1,7 @@ import React from "react"; import ForceGraph, { Link, Node } from "components/ForceGraph"; import type { SystemState } from "types"; +import { aggMonolithRooms, countRoomClients } from "aggregate"; interface Props { systemState: SystemState; @@ -8,190 +9,92 @@ interface Props { height: number; } -export const GlobalView: React.FC = ({ width, height }) => { +function buildGraph(state: SystemState): [Node[], Link[]] { + const nodes: Node[] = []; + const links: Link[] = []; + const core: Node = { + id: "core", + radius: 15, + x: 0, + y: 0, + group: "core", + color: "Purple", // TODO: use color from grafana theme/panel options + }; + nodes.push(core); + + const roomCounts = countRoomClients(state); + const monolithRooms = aggMonolithRooms(state); + + const monoliths: Node[] = []; + for (const [monolith, rooms] of Object.entries(monolithRooms)) { + const monolithNode: Node = { + id: monolith, + radius: 10, + x: 0, + y: 0, + group: "monolith", + color: "Red", // TODO: use color from grafana theme/panel options + }; + nodes.push(monolithNode); + monoliths.push(monolithNode); + + links.push({ + source: core.id, + target: monolith, + value: 10, + }); + + for (const room of rooms) { + const roomNode: Node = { + id: room, + radius: 7, + x: 0, + y: 0, + group: "Room", + color: "Blue", // TODO: use color from grafana theme/panel options + }; + nodes.push(roomNode); + + links.push({ + source: monolith, + target: room, + value: 5, + }); + + const clients = roomCounts[room]; + if (clients) { + for (let i = 0; i < clients; i++) { + const clientNode: Node = { + id: `${room}-client-${i}`, + radius: 4, + x: 0, + y: 0, + group: "Client", + color: "Blue", // TODO: use color from grafana theme/panel options + }; + nodes.push(clientNode); + + links.push({ + source: room, + target: clientNode.id, + value: 3, + }); + } + } + } + } + + return [nodes, links]; +} + +export const GlobalView: React.FC = ({ systemState, width, height }) => { + const [nodes, links] = buildGraph(systemState); + const data = { nodes, links }; return (
- +
); }; -const sample_data = { - nodes: [ - { - id: "B1", - group: "Balancer", - color: "Purple", - radius: 15, - }, - { - id: "M1", - group: "Monolith", - color: "Red", - radius: 10, - }, - { - id: "M2", - group: "Monolith", - color: "Red", - radius: 10, - }, - { - id: "R1", - group: "Room", - color: "Blue", - radius: 7, - }, - { - id: "R2", - group: "Room", - color: "Blue", - radius: 7, - }, - { - id: "R3", - group: "Room", - color: "Blue", - radius: 7, - }, - { - id: "R4", - group: "Room", - color: "Blue", - radius: 7, - }, - { - id: "C1", - group: "Client", - color: "Blue", - radius: 4, - }, - { - id: "C2", - group: "Client", - color: "Blue", - radius: 4, - }, - { - id: "C3", - group: "Client", - color: "Blue", - radius: 4, - }, - { - id: "C4", - group: "Client", - color: "Blue", - radius: 4, - }, - { - id: "C5", - group: "Client", - color: "Blue", - radius: 4, - }, - { - id: "C6", - group: "Client", - color: "Blue", - radius: 4, - }, - { - id: "C7", - group: "Client", - color: "Blue", - radius: 4, - }, - { - id: "C8", - group: "Client", - color: "Blue", - radius: 4, - }, - { - id: "C9", - group: "Client", - color: "Blue", - radius: 4, - }, - ] as Node[], - links: [ - { - source: "B1", - target: "M1", - value: 10, - }, - { - source: "B1", - target: "M2", - value: 10, - }, - { - source: "M1", - target: "R1", - value: 5, - }, - { - source: "M1", - target: "R2", - value: 5, - }, - { - source: "M2", - target: "R3", - value: 5, - }, - { - source: "M2", - target: "R4", - value: 5, - }, - { - source: "R1", - target: "C1", - value: 5, - }, - { - source: "R1", - target: "C2", - value: 3, - }, - { - source: "R2", - target: "C3", - value: 3, - }, - { - source: "R2", - target: "C4", - value: 3, - }, - { - source: "R3", - target: "C5", - value: 3, - }, - { - source: "R3", - target: "C6", - value: 3, - }, - { - source: "R4", - target: "C7", - value: 3, - }, - { - source: "R4", - target: "C8", - value: 3, - }, - { - source: "R4", - target: "C9", - value: 3, - }, - ] as Link[], -}; - export default GlobalView; diff --git a/packages/ott-vis/tsconfig.json b/packages/ott-vis/tsconfig.json index 3b79176e5..3e04c4bff 100644 --- a/packages/ott-vis/tsconfig.json +++ b/packages/ott-vis/tsconfig.json @@ -2,6 +2,8 @@ "extends": "./.config/tsconfig.json", "compilerOptions": { "strict": true, - "typeRoots": ["../../node_modules/@types"] + "typeRoots": ["../../node_modules/@types"], + "esModuleInterop": true, + "jsx": "react" } }