Loading src/routes/simulations.$simulationId.console.tsx +9 −40 Original line number Diff line number Diff line import { createFileRoute } from "@tanstack/react-router"; import { useQuery } from "@tanstack/react-query"; import { sortBy } from "lodash"; import { PressureFlowRate } from "../components/simulations/console/pressureFlowRate"; import { LoadingSpinner } from "../components/shared/loadingSpinner"; import { JobQueue } from "../components/simulations/console/jobQueue"; Loading @@ -8,16 +7,12 @@ import { CDUList } from "../components/simulations/console/cduList"; import { Power } from "../components/simulations/console/power"; import { Scheduler } from "../components/simulations/console/scheduler"; import { Message } from "../components/shared/simulation/message"; import { toDate, differenceInSeconds, addSeconds, subSeconds, min as minDate } from "date-fns"; import { toDate } from "date-fns"; import { simulationConfigurationQueryOptions, simulationSystemStatsQueryOptions, simulationCoolingCDUQueryOptions, simulationCoolingCEPQueryOptions, simulationSchedulerJobs, } from "../util/queryOptions"; import { computeJobState } from "../util/jobs"; import { useReplay } from "../util/hooks/useReplay"; import { Job } from "../models/Job.model"; import { floorDate } from "../util/datetime"; import { useReplay, useJobReplay } from "../util/hooks/useReplay"; export const Route = createFileRoute("/simulations/$simulationId/console")({ Loading @@ -31,7 +26,7 @@ function SimulationConsoleView() { const { data: sim } = useQuery(simulationConfigurationQueryOptions(simulationId)) const { maxTimestamp, data: schedulerStatistics } = useReplay({ const { data: schedulerStatistics } = useReplay({ sim: sim, query: (params) => simulationSystemStatsQueryOptions(simulationId, params), timestamp: currentTimestamp, Loading @@ -55,38 +50,12 @@ function SimulationConsoleView() { summarize: !currentTimestamp, }) const jobPageSize = search.playbackInterval * 100; // how long completed jobs will stay in the chart const jobLingerTime = search.playbackInterval * 5 let jobQueryStart: Date|undefined, jobQueryEnd: Date|undefined; if (sim && currentTimestamp && maxTimestamp) { jobQueryStart = subSeconds(floorDate(currentTimestamp, jobPageSize, sim.start), jobLingerTime) jobQueryEnd = minDate([addSeconds(jobQueryStart, jobPageSize + jobLingerTime), maxTimestamp]) } const { data: jobsRaw } = useQuery({ ...simulationSchedulerJobs(simulationId, { start: jobQueryStart?.toISOString(), end: jobQueryEnd?.toISOString(), limit: 1000, fields: [ 'job_id', 'name', 'node_count', 'state_current', 'time_limit', 'time_start', 'time_end', 'time_submission', ], }), placeholderData: (prevData, _prevQuery) => prevData, staleTime: Infinity, // We're capping queries to maxTimestamp so they should always be valid }); let jobs = ( jobsRaw?.results .map(j => ({...j, state_current: computeJobState(j, currentTimestamp)})) // Filter out unsubmitted jobs, and completed jobs after a few steps .filter(j => j.state_current != "UNSUBMITTED" && (!currentTimestamp || !j.time_end || differenceInSeconds(currentTimestamp, j.time_end) > jobLingerTime) ) ) as Job[]; jobs = sortBy(jobs, j => j.state_current != "RUNNING", j => j.state_current, j => j.job_id) const { data: jobs } = useJobReplay({ sim: sim, timestamp: currentTimestamp, stepInterval: search.playbackInterval, summarize: !currentTimestamp, }) return ( <section className="grid min-w-[1024px] grid-cols-12 gap-2 overflow-auto p-2"> Loading src/util/hooks/useReplay.ts +103 −1 Original line number Diff line number Diff line import { useEffect } from "react"; import { useQuery, UseQueryOptions, useQueryClient } from "@tanstack/react-query"; import { toDate, isEqual as isDateEqual, min as minDate, addSeconds } from "date-fns"; import { sortBy } from "lodash"; import { toDate, isEqual as isDateEqual, min as minDate, addSeconds, subSeconds, differenceInSeconds, } from "date-fns"; import { Simulation } from "../../models/Simulation.model"; import { Job } from "../../models/Job.model"; import { TimeSeriesPoint, TimeSeriesResponse, TimeSeriesParams } from "../queryOptions"; import { floorDate, snapDate, DateLike} from "../datetime"; import { simulationSchedulerJobs } from "../queryOptions" import { computeJobState } from "../jobs"; export type UseReplayOptions<T extends TimeSeriesPoint> = { Loading Loading @@ -146,3 +152,99 @@ export const useReplay = <T extends TimeSeriesPoint>({ return { data, maxTimestamp, currentTimestamp, nextTimestamp } } export type UseJobReplayOptions = { sim?: Simulation, timestamp?: Date, /** stepInterval in seconds */ stepInterval: number, /** If set, will return a summary of whole simulation instead of a single time step */ summarize?: boolean, } export type UseJobReplayResult = { data: Job[], maxTimestamp: Date|undefined, currentTimestamp: Date|undefined, nextTimestamp: Date|undefined, } /** * Get a list of jobs for the simulation. */ export const useJobReplay = ({ sim, timestamp, stepInterval, summarize = false, }: UseJobReplayOptions): UseJobReplayResult => { const queryClient = useQueryClient(); const querySize = stepInterval * 100; // how long completed jobs will stay in the chart const jobLingerTime = stepInterval * 5; const fields = [ 'job_id', 'name', 'node_count', 'state_current', 'time_limit', 'time_start', 'time_end', 'time_submission', ] const maxTimestamp = sim ? getMaxTimestamp(sim, stepInterval) : undefined; const { currentTimestamp, nextTimestamp, } = (sim && timestamp && !summarize) ? snapReplayTimestamp(sim, timestamp, stepInterval) : {}; let queryStart: Date, queryEnd: Date; let enabled: boolean if (sim && summarize) { [queryStart, queryEnd] = [toDate(sim.start), maxTimestamp!]; enabled = true; } else if (sim && currentTimestamp) { queryStart = subSeconds(floorDate(currentTimestamp, querySize, sim.start), jobLingerTime) queryEnd = minDate([addSeconds(queryStart, querySize + jobLingerTime), maxTimestamp!]) enabled = true; } else { [queryStart, queryEnd] = [toDate(0), toDate(0)]; // Just a placeholder enabled = false; } const { data: response } = useQuery({ ...simulationSchedulerJobs(sim?.id ?? '', { start: queryStart?.toISOString(), end: queryEnd?.toISOString(), limit: 1000, fields: fields, }), enabled: enabled, placeholderData: (prevData, _prevQuery) => prevData, staleTime: Infinity, // We're capping queries to maxTimestamp so they should always be valid }); let jobs = ( response?.results .map(j => ({...j, state_current: computeJobState(j, currentTimestamp)})) // Filter out unsubmitted jobs, and completed jobs after a few steps .filter(j => j.state_current != "UNSUBMITTED" && (!currentTimestamp || !j.time_end || differenceInSeconds(currentTimestamp, j.time_end) > jobLingerTime) ) ) as Job[]; jobs = sortBy(jobs, j => j.state_current != "RUNNING", j => j.state_current, j => j.job_id); // Prefetch the next query useEffect(() => { if (sim && !summarize && currentTimestamp) { const nextQueryStart = addSeconds(queryStart, querySize) if (nextQueryStart < maxTimestamp!) { const nextQueryEnd = minDate([addSeconds(nextQueryStart, querySize), maxTimestamp!]) queryClient.prefetchQuery({ ...simulationSchedulerJobs(sim?.id ?? '', { start: nextQueryStart?.toISOString(), end: nextQueryEnd?.toISOString(), limit: 1000, fields: fields, }), staleTime: Infinity, }) } } }) return { data: jobs, maxTimestamp, currentTimestamp, nextTimestamp } } No newline at end of file src/util/jobs.ts +1 −1 File changed.Contains only whitespace changes. Show changes Loading
src/routes/simulations.$simulationId.console.tsx +9 −40 Original line number Diff line number Diff line import { createFileRoute } from "@tanstack/react-router"; import { useQuery } from "@tanstack/react-query"; import { sortBy } from "lodash"; import { PressureFlowRate } from "../components/simulations/console/pressureFlowRate"; import { LoadingSpinner } from "../components/shared/loadingSpinner"; import { JobQueue } from "../components/simulations/console/jobQueue"; Loading @@ -8,16 +7,12 @@ import { CDUList } from "../components/simulations/console/cduList"; import { Power } from "../components/simulations/console/power"; import { Scheduler } from "../components/simulations/console/scheduler"; import { Message } from "../components/shared/simulation/message"; import { toDate, differenceInSeconds, addSeconds, subSeconds, min as minDate } from "date-fns"; import { toDate } from "date-fns"; import { simulationConfigurationQueryOptions, simulationSystemStatsQueryOptions, simulationCoolingCDUQueryOptions, simulationCoolingCEPQueryOptions, simulationSchedulerJobs, } from "../util/queryOptions"; import { computeJobState } from "../util/jobs"; import { useReplay } from "../util/hooks/useReplay"; import { Job } from "../models/Job.model"; import { floorDate } from "../util/datetime"; import { useReplay, useJobReplay } from "../util/hooks/useReplay"; export const Route = createFileRoute("/simulations/$simulationId/console")({ Loading @@ -31,7 +26,7 @@ function SimulationConsoleView() { const { data: sim } = useQuery(simulationConfigurationQueryOptions(simulationId)) const { maxTimestamp, data: schedulerStatistics } = useReplay({ const { data: schedulerStatistics } = useReplay({ sim: sim, query: (params) => simulationSystemStatsQueryOptions(simulationId, params), timestamp: currentTimestamp, Loading @@ -55,38 +50,12 @@ function SimulationConsoleView() { summarize: !currentTimestamp, }) const jobPageSize = search.playbackInterval * 100; // how long completed jobs will stay in the chart const jobLingerTime = search.playbackInterval * 5 let jobQueryStart: Date|undefined, jobQueryEnd: Date|undefined; if (sim && currentTimestamp && maxTimestamp) { jobQueryStart = subSeconds(floorDate(currentTimestamp, jobPageSize, sim.start), jobLingerTime) jobQueryEnd = minDate([addSeconds(jobQueryStart, jobPageSize + jobLingerTime), maxTimestamp]) } const { data: jobsRaw } = useQuery({ ...simulationSchedulerJobs(simulationId, { start: jobQueryStart?.toISOString(), end: jobQueryEnd?.toISOString(), limit: 1000, fields: [ 'job_id', 'name', 'node_count', 'state_current', 'time_limit', 'time_start', 'time_end', 'time_submission', ], }), placeholderData: (prevData, _prevQuery) => prevData, staleTime: Infinity, // We're capping queries to maxTimestamp so they should always be valid }); let jobs = ( jobsRaw?.results .map(j => ({...j, state_current: computeJobState(j, currentTimestamp)})) // Filter out unsubmitted jobs, and completed jobs after a few steps .filter(j => j.state_current != "UNSUBMITTED" && (!currentTimestamp || !j.time_end || differenceInSeconds(currentTimestamp, j.time_end) > jobLingerTime) ) ) as Job[]; jobs = sortBy(jobs, j => j.state_current != "RUNNING", j => j.state_current, j => j.job_id) const { data: jobs } = useJobReplay({ sim: sim, timestamp: currentTimestamp, stepInterval: search.playbackInterval, summarize: !currentTimestamp, }) return ( <section className="grid min-w-[1024px] grid-cols-12 gap-2 overflow-auto p-2"> Loading
src/util/hooks/useReplay.ts +103 −1 Original line number Diff line number Diff line import { useEffect } from "react"; import { useQuery, UseQueryOptions, useQueryClient } from "@tanstack/react-query"; import { toDate, isEqual as isDateEqual, min as minDate, addSeconds } from "date-fns"; import { sortBy } from "lodash"; import { toDate, isEqual as isDateEqual, min as minDate, addSeconds, subSeconds, differenceInSeconds, } from "date-fns"; import { Simulation } from "../../models/Simulation.model"; import { Job } from "../../models/Job.model"; import { TimeSeriesPoint, TimeSeriesResponse, TimeSeriesParams } from "../queryOptions"; import { floorDate, snapDate, DateLike} from "../datetime"; import { simulationSchedulerJobs } from "../queryOptions" import { computeJobState } from "../jobs"; export type UseReplayOptions<T extends TimeSeriesPoint> = { Loading Loading @@ -146,3 +152,99 @@ export const useReplay = <T extends TimeSeriesPoint>({ return { data, maxTimestamp, currentTimestamp, nextTimestamp } } export type UseJobReplayOptions = { sim?: Simulation, timestamp?: Date, /** stepInterval in seconds */ stepInterval: number, /** If set, will return a summary of whole simulation instead of a single time step */ summarize?: boolean, } export type UseJobReplayResult = { data: Job[], maxTimestamp: Date|undefined, currentTimestamp: Date|undefined, nextTimestamp: Date|undefined, } /** * Get a list of jobs for the simulation. */ export const useJobReplay = ({ sim, timestamp, stepInterval, summarize = false, }: UseJobReplayOptions): UseJobReplayResult => { const queryClient = useQueryClient(); const querySize = stepInterval * 100; // how long completed jobs will stay in the chart const jobLingerTime = stepInterval * 5; const fields = [ 'job_id', 'name', 'node_count', 'state_current', 'time_limit', 'time_start', 'time_end', 'time_submission', ] const maxTimestamp = sim ? getMaxTimestamp(sim, stepInterval) : undefined; const { currentTimestamp, nextTimestamp, } = (sim && timestamp && !summarize) ? snapReplayTimestamp(sim, timestamp, stepInterval) : {}; let queryStart: Date, queryEnd: Date; let enabled: boolean if (sim && summarize) { [queryStart, queryEnd] = [toDate(sim.start), maxTimestamp!]; enabled = true; } else if (sim && currentTimestamp) { queryStart = subSeconds(floorDate(currentTimestamp, querySize, sim.start), jobLingerTime) queryEnd = minDate([addSeconds(queryStart, querySize + jobLingerTime), maxTimestamp!]) enabled = true; } else { [queryStart, queryEnd] = [toDate(0), toDate(0)]; // Just a placeholder enabled = false; } const { data: response } = useQuery({ ...simulationSchedulerJobs(sim?.id ?? '', { start: queryStart?.toISOString(), end: queryEnd?.toISOString(), limit: 1000, fields: fields, }), enabled: enabled, placeholderData: (prevData, _prevQuery) => prevData, staleTime: Infinity, // We're capping queries to maxTimestamp so they should always be valid }); let jobs = ( response?.results .map(j => ({...j, state_current: computeJobState(j, currentTimestamp)})) // Filter out unsubmitted jobs, and completed jobs after a few steps .filter(j => j.state_current != "UNSUBMITTED" && (!currentTimestamp || !j.time_end || differenceInSeconds(currentTimestamp, j.time_end) > jobLingerTime) ) ) as Job[]; jobs = sortBy(jobs, j => j.state_current != "RUNNING", j => j.state_current, j => j.job_id); // Prefetch the next query useEffect(() => { if (sim && !summarize && currentTimestamp) { const nextQueryStart = addSeconds(queryStart, querySize) if (nextQueryStart < maxTimestamp!) { const nextQueryEnd = minDate([addSeconds(nextQueryStart, querySize), maxTimestamp!]) queryClient.prefetchQuery({ ...simulationSchedulerJobs(sim?.id ?? '', { start: nextQueryStart?.toISOString(), end: nextQueryEnd?.toISOString(), limit: 1000, fields: fields, }), staleTime: Infinity, }) } } }) return { data: jobs, maxTimestamp, currentTimestamp, nextTimestamp } } No newline at end of file