Skip to content

Commit

Permalink
refactor to use a StatusEvent interface; update StatusEvent with poll…
Browse files Browse the repository at this point in the history
…ing instead of websocket
  • Loading branch information
eriktaubeneck committed Jul 17, 2024
1 parent 6ed48ef commit e875265
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 170 deletions.
106 changes: 43 additions & 63 deletions server/app/query/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,17 @@ import Link from "next/link";

import { StatusPill, RunTimePill } from "@/app/query/view/[id]/components";
import {
Status,
StatusEvent,
RemoteServer,
RemoteServerNames,
IPARemoteServers, //hack until the queryId is stored in a DB
StatusByRemoteServer,
StartTimeByRemoteServer,
EndTimeByRemoteServer,
initialStatusByRemoteServer,
initialStartTimeByRemoteServer,
initialEndTimeByRemoteServer,
StatusEventByRemoteServer,
initialStatusEventByRemoteServer,
} from "@/app/query/servers";
import { getQueryByUUID, Query } from "@/data/query";

type QueryData = {
status: StatusByRemoteServer;
startTime: StartTimeByRemoteServer;
endTime: EndTimeByRemoteServer;
statusEvent: StatusEventByRemoteServer;
query: Query;
};
type DataByQuery = {
Expand All @@ -35,8 +29,7 @@ export default function Page() {
const updateData = (
query: Query,
remoteServer: RemoteServer,
key: keyof QueryData,
value: Status | number,
statusEvent: StatusEvent,
) => {
setDataByQuery((prev) => {
let _prev = prev;
Expand All @@ -46,9 +39,7 @@ export default function Page() {
_prev = {
..._prev,
[query.uuid]: {
status: initialStatusByRemoteServer,
startTime: initialStartTimeByRemoteServer,
endTime: initialEndTimeByRemoteServer,
statusEvent: initialStatusEventByRemoteServer,
query: query,
},
};
Expand All @@ -58,9 +49,9 @@ export default function Page() {
..._prev,
[query.uuid]: {
..._prev[query.uuid],
[key]: {
..._prev[query.uuid][key],
[remoteServer.remoteServerName]: value,
statusEvent: {
..._prev[query.uuid].statusEvent,
[remoteServer.remoteServerName]: statusEvent,
},
},
};
Expand All @@ -82,37 +73,25 @@ export default function Page() {

useEffect(() => {
(async () => {
let webSockets: WebSocket[] = [];

// remove queries when no longer running
const filteredDataByQuery = Object.fromEntries(
Object.keys(dataByQuery)
.filter((queryID) => queryIDs.includes(queryID))
.map((queryID) => [queryID, dataByQuery[queryID]]),
);
setDataByQuery(filteredDataByQuery);
setDataByQuery((prev) => {
return Object.fromEntries(
Object.keys(prev)
.filter((queryID) => queryIDs.includes(queryID))
.map((queryID) => [queryID, prev[queryID]]),
);
});

for (const queryID of queryIDs) {
const query: Query = await getQueryByUUID(queryID);

for (const remoteServer of Object.values(IPARemoteServers)) {
const statusWs = remoteServer.openStatusSocket(
queryID,
(status) => updateData(query, remoteServer, "status", status),
(startTime) =>
updateData(query, remoteServer, "startTime", startTime),
(endTime) => updateData(query, remoteServer, "endTime", endTime),
);
webSockets = [...webSockets, statusWs];
const statusEvent: StatusEvent =
await remoteServer.queryStatus(queryID);
updateData(query, remoteServer, statusEvent);
}
}
return () => {
for (const ws of webSockets) {
ws.close();
}
};
})();
}, [queryIDs, dataByQuery]);
}, [queryIDs]);

return (
<>
Expand All @@ -122,10 +101,14 @@ export default function Page() {
Current Queries
</h2>

{Object.keys(dataByQuery).length == 0 && (
<h3 className="text-lg font-bold leading-7 text-gray-900 sm:truncate sm:text-xl sm:tracking-tight">
None currently running.
</h3>
)}

{Object.entries(dataByQuery).map(([queryID, queryData]) => {
const statusByRemoteServer = queryData.status;
const startTimeByRemoteServer = queryData.startTime;
const endTimeByRemoteServer = queryData.endTime;
const statusEventByRemoteServer = queryData.statusEvent;
const query = queryData.query;

return (
Expand All @@ -142,19 +125,16 @@ export default function Page() {
<dl className="mb-2 grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-4">
{Object.values(IPARemoteServers).map(
(remoteServer: RemoteServer) => {
const startTime =
startTimeByRemoteServer[
const statusEvent: StatusEvent | null =
statusEventByRemoteServer[
remoteServer.remoteServerName
];
const endTime =
endTimeByRemoteServer[
remoteServer.remoteServerName
];

const status =
statusByRemoteServer[
remoteServer.remoteServerName
] ?? Status.UNKNOWN;
if (statusEvent === null) {
return <></>;
}
const status = statusEvent.status;
const startTime = statusEvent.startTime;
const endTime = statusEvent.endTime;

return (
<div
Expand All @@ -165,11 +145,7 @@ export default function Page() {
{remoteServer.toString()} Run Time
</dt>
<dd>
<RunTimePill
status={status}
startTime={startTime}
endTime={endTime}
/>
<RunTimePill statusEvent={statusEvent} />
</dd>
</div>
);
Expand All @@ -181,10 +157,14 @@ export default function Page() {
<dl className="grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-4">
{Object.values(IPARemoteServers).map(
(remoteServer: RemoteServer) => {
const status =
statusByRemoteServer[
const statusEvent: StatusEvent | null =
statusEventByRemoteServer[
remoteServer.remoteServerName
] ?? Status.UNKNOWN;
];
if (statusEvent === null) {
return <></>;
}
const status = statusEvent.status;

return (
<div
Expand Down
85 changes: 50 additions & 35 deletions server/app/query/servers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,18 @@ function getStatusFromString(statusString: string): Status {
}
}

export interface StatsDataPoint {
timestamp: string;
memoryRSSUsage: number;
cpuUsage: number;
export interface StatusEvent {
status: Status;
startTime: number;
endTime: number | null;
}

function buildStatusEventFromJSON(statusJSON: any): StatusEvent {
return {
status: getStatusFromString(statusJSON.status),
startTime: statusJSON.start_time,
endTime: statusJSON.end_time ?? null,
};
}

export type StatusByRemoteServer = {
Expand All @@ -50,15 +58,6 @@ export const initialStatusByRemoteServer: StatusByRemoteServer =
Object.values(RemoteServerNames).map((serverName) => [[serverName], null]),
);

export type StatsByRemoteServer = {
[key in RemoteServerNames]: StatsDataPoint[];
};

export const initialStatsByRemoteServer: StatsByRemoteServer =
Object.fromEntries(
Object.values(RemoteServerNames).map((serverName) => [[serverName], []]),
);

export type StartTimeByRemoteServer = {
[key in RemoteServerNames]: number | null;
};
Expand All @@ -67,6 +66,10 @@ export type EndTimeByRemoteServer = {
[key in RemoteServerNames]: number | null;
};

export type StatusEventByRemoteServer = {
[key in RemoteServerNames]: StatusEvent | null;
};

export const initialStartTimeByRemoteServer: StartTimeByRemoteServer =
Object.fromEntries(
Object.values(RemoteServerNames).map((serverName) => [[serverName], null]),
Expand All @@ -77,6 +80,26 @@ export const initialEndTimeByRemoteServer: StartTimeByRemoteServer =
Object.values(RemoteServerNames).map((serverName) => [[serverName], null]),
);

export const initialStatusEventByRemoteServer: StatusEventByRemoteServer =
Object.fromEntries(
Object.values(RemoteServerNames).map((serverName) => [[serverName], null]),
);

export interface StatsDataPoint {
timestamp: string;
memoryRSSUsage: number;
cpuUsage: number;
}

export type StatsByRemoteServer = {
[key in RemoteServerNames]: StatsDataPoint[];
};

export const initialStatsByRemoteServer: StatsByRemoteServer =
Object.fromEntries(
Object.values(RemoteServerNames).map((serverName) => [[serverName], []]),
);

export class RemoteServer {
protected baseURL: URL;
remoteServerName: RemoteServerNames;
Expand All @@ -96,14 +119,24 @@ export class RemoteServer {
throw new Error("Not Implemented");
}

queryStatusURL(id: string): URL {
return new URL(`/start/${id}/status`, this.baseURL);
}

async queryStatus(id: string): Promise<StatusEvent> {
const status_response = await fetch(this.queryStatusURL(id));
const statusJSON = await status_response.json();
return buildStatusEventFromJSON(statusJSON);
}

runningQueriesURL(): URL {
return new URL(`/start/running-queries`, this.baseURL);
}

async runningQueries(): Promise<string[]> {
const queries_response = await fetch(this.runningQueriesURL());
const queriesJSON = await queries_response.json();
return queriesJSON["running_queries"];
return queriesJSON.running_queries;
}

logURL(id: string): URL {
Expand Down Expand Up @@ -197,31 +230,13 @@ export class RemoteServer {

openStatusSocket(
id: string,
setStatus: (status: Status) => void,
setStartTime: (startTime: number) => void,
setEndTime: (endTime: number) => void,
setStatusEvent: (statusEvent: StatusEvent) => void,
): WebSocket {
const ws = this.statusSocket(id);

const updateStatus = (status: Status) => {
setStatus(status);
};

const updateStartTime = (startTime: number) => {
setStartTime(startTime);
};

const updateEndTime = (endTime: number) => {
setEndTime(endTime);
};

ws.onmessage = (event) => {
const eventData = JSON.parse(event.data);
updateStartTime(eventData.start_time);
updateEndTime(eventData.end_time ?? null);
const statusString: string = eventData.status;
const status = getStatusFromString(statusString);
updateStatus(status);
const statusEvent = buildStatusEventFromJSON(JSON.parse(event.data));
setStatusEvent(statusEvent);
};

ws.onclose = (event) => {
Expand Down
29 changes: 13 additions & 16 deletions server/app/query/view/[id]/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useEffect, useState, useRef } from "react";
import { Source_Code_Pro } from "next/font/google";
import clsx from "clsx";
import { ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/24/solid";
import { Status, ServerLog } from "@/app/query/servers";
import { Status, StatusEvent, ServerLog } from "@/app/query/servers";

const sourceCodePro = Source_Code_Pro({ subsets: ["latin"] });

Expand Down Expand Up @@ -64,15 +64,7 @@ function secondsToTime(e: number) {
return h + ":" + m + ":" + s;
}

export function RunTimePill({
status,
startTime,
endTime,
}: {
status: Status;
startTime: number | null;
endTime: number | null;
}) {
export function RunTimePill({ statusEvent }: { statusEvent: StatusEvent }) {
const [runTime, setRunTime] = useState<number | null>(null);
const runTimeStr = runTime ? secondsToTime(runTime) : "N/A";
const intervalId = useRef<ReturnType<typeof setTimeout> | null>(null);
Expand All @@ -83,22 +75,27 @@ export function RunTimePill({
// which runs the timer. if a new one is needed, it's created.
clearTimeout(intervalId.current);
}
if (startTime === null) {
if (statusEvent?.startTime === null) {
setRunTime(null);
} else {
if (endTime !== null) {
setRunTime(endTime - startTime);
if (statusEvent?.endTime !== null) {
setRunTime(statusEvent.endTime - statusEvent.startTime);
} else {
let newIntervalId = setInterval(() => {
setRunTime(Date.now() / 1000 - startTime);
setRunTime(Date.now() / 1000 - statusEvent.startTime);
}, 1000);
intervalId.current = newIntervalId;
}
}
}, [startTime, endTime]);
}, [statusEvent]);

return (
<div className={clsx(`rounded-full px-2`, StatusClassNameMixins[status])}>
<div
className={clsx(
`rounded-full px-2`,
StatusClassNameMixins[statusEvent.status],
)}
>
{runTimeStr}
</div>
);
Expand Down
Loading

0 comments on commit e875265

Please sign in to comment.