Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,132 changes: 627 additions & 505 deletions core/trino-web-ui/src/main/resources/webapp-preview/package-lock.json

Large diffs are not rendered by default.

46 changes: 24 additions & 22 deletions core/trino-web-ui/src/main/resources/webapp-preview/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,37 +16,39 @@
"check:clean": "npm clean-install && npm run lint && npm run prettier:check"
},
"dependencies": {
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@fontsource/roboto": "^5.1.0",
"@mui/icons-material": "^6.1.7",
"@mui/material": "^6.1.7",
"@mui/x-charts": "^7.22.2",
"axios": "^1.7.7",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@fontsource/roboto": "^5.1.1",
"@mui/icons-material": "^6.4.2",
"@mui/material": "^6.4.2",
"@mui/x-charts": "^7.25.0",
"axios": "^1.7.9",
"lodash": "^4.17.21",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.28.0",
"react-router-dom": "^7.1.5",
"react-syntax-highlighter": "^15.6.1",
"sass": "^1.81.0",
"zustand": "^5.0.1"
"sass": "^1.83.4",
"zustand": "^5.0.3"
},
"devDependencies": {
"@eslint/js": "^9.14.0",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this file have a stable order of elements? :)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is auto generated by npm. Am i missing something?

"@types/eslint__js": "^8.42.3",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@types/lodash": "^4.17.15",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@types/react-syntax-highlighter": "^15.5.13",
"@typescript-eslint/eslint-plugin": "^8.14.0",
"@typescript-eslint/parser": "^8.14.0",
"@vitejs/plugin-react": "^4.3.3",
"eslint": "^9.15.0",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.14",
"globals": "^15.12.0",
"prettier": "^3.3.3",
"typescript": "^5.6.3",
"typescript-eslint": "^8.14.0",
"vite": "^5.4.11"
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.19.0",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.18",
"globals": "^15.14.0",
"prettier": "^3.4.2",
"typescript": "^5.7.3",
"typescript-eslint": "^8.22.0",
"vite": "^6.0.11"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,66 @@ export interface WorkerStatusInfo {
uptime: string
}

export interface QueryStats {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like these typed response classes 👍🏻

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

luckily the auto completion works too in IDEs

analysisTime: string
blockedDrivers: number
completedDrivers: number
createTime: string
cumulativeUserMemory: number
elapsedTime: string
endTime: string
executionTime: string
failedCpuTime: string
failedCumulativeUserMemory: number
failedScheduledTime: string
failedTasks: number
finishingTime: string
fullyBlocked: boolean
internalNetworkInputDataSize: string
peakTotalMemoryReservation: string
peakUserMemoryReservation: string
physicalInputDataSize: string
physicalInputReadTime: string
physicalWrittenDataSize: string
planningTime: string
progressPercentage: number
queuedDrivers: number
queuedTime: string
rawInputDataSize: string
rawInputPositions: number
runningDrivers: number
runningPercentage: number
spilledDataSize: string
totalCpuTime: string
totalDrivers: number
totalMemoryReservation: string
totalScheduledTime: string
userMemoryReservation: string
blockedReasons: string[]
}

export interface QueryInfo {
clientTags: string[]
queryId: string
queryStats: QueryStats
queryTextPreview: string
queryType: string
resourceGroupId: string[]
retryPolicy: string
scheduled: boolean
self: string
sessionPrincipal: string
sessionSource: string
sessionUser: string
state: string
memoryPool: string
queryDataEncoding: string
errorType: string
errorCode: {
name: string
}
}

export async function statsApi(): Promise<ApiResponse<Stats>> {
return await api.get<Stats>('/ui/api/stats')
}
Expand All @@ -89,3 +149,7 @@ export async function workerApi(): Promise<ApiResponse<Worker[]>> {
export async function workerStatusApi(nodeId: string): Promise<ApiResponse<WorkerStatusInfo>> {
return await api.get<WorkerStatusInfo>(`/ui/api/worker/${nodeId}/status`)
}

export async function queryApi(): Promise<ApiResponse<QueryInfo[]>> {
return await api.get<QueryInfo[]>('/ui/api/query')
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,45 +12,59 @@
* limitations under the License.
*/
import { Box, useMediaQuery } from '@mui/material'
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
import { materialLight, materialDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
import { LightAsync as SyntaxHighlighter } from 'react-syntax-highlighter'
import { a11yLight, a11yDark } from 'react-syntax-highlighter/dist/esm/styles/hljs'
import { Theme as ThemeStore, useConfigStore } from '../store'

export interface ICodeBlockProps {
code: string
language: string
height?: string
}

export const CodeBlock = (props: ICodeBlockProps) => {
const config = useConfigStore()
const { code, language } = props
const { code, language, height } = props
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)')

const styleToUse = () => {
if (config.theme === ThemeStore.Auto) {
return prefersDarkMode ? materialDark : materialLight
return prefersDarkMode ? a11yDark : a11yLight
} else if (config.theme === ThemeStore.Dark) {
return materialDark
return a11yDark
} else {
return materialLight
return a11yLight
}
}

return (
<Box
sx={{
display="flex"
flexDirection="column"
flexGrow={1}
sx={(theme) => ({
padding: 0,
borderRadius: 0,
backgroundColor: '#f5f5f5',
overflow: 'auto',
maxHeight: '400px',
border: '1px solid #ddd',
}}
border: `1px solid ${theme.palette.mode === 'dark' ? '#3f3f3f' : '#ddd'}`,
borderBottom: 'none',
width: '100%',
height: {
xs: '100%',
lg: height,
},
})}
>
<SyntaxHighlighter
language={language}
style={styleToUse()}
customStyle={{ padding: '4px', margin: 0, borderRadius: 0 }}
customStyle={{
padding: '4px',
margin: 0,
borderRadius: 0,
height: '100%',
overflow: 'auto',
}}
wrapLongLines
>
{code}
</SyntaxHighlighter>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
*/
import { useEffect, useState } from 'react'
import Typography from '@mui/material/Typography'
import { Box, Grid2 as Grid } from '@mui/material'
import { Box, Divider, Grid2 as Grid } from '@mui/material'
import { MetricCard } from './MetricCard.tsx'
import { QueryList } from './QueryList.tsx'
import { useSnackbar } from './SnackbarContext.ts'
import { ApiResponse } from '../api/base.ts'
import { statsApi, Stats } from '../api/webapp/api.ts'
Expand Down Expand Up @@ -69,9 +70,11 @@ export const Dashboard = () => {
const [error, setError] = useState<string | null>(null)

useEffect(() => {
getClusterStats()
const intervalId = setInterval(getClusterStats, 1000)
return () => clearInterval(intervalId)
const runLoop = () => {
getClusterStats()
setTimeout(runLoop, 1000)
}
runLoop()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

Expand All @@ -89,23 +92,23 @@ export const Dashboard = () => {
setClusterStats((prevClusterStats) => {
let newRowInputRate: number[] = initialFilledHistory
let newByteInputRate: number[] = initialFilledHistory
let newPerWorkerCpuTimeRate: number[] = []
let newPerWorkerCpuTimeRate: number[] = initialFilledHistory
if (prevClusterStats.lastRefresh !== null) {
const rowsInputSinceRefresh = newClusterStats.totalInputRows - prevClusterStats.lastInputRows
const bytesInputSinceRefresh = newClusterStats.totalInputBytes - prevClusterStats.lastInputBytes
const cpuTimeSinceRefresh = newClusterStats.totalCpuTimeSecs - prevClusterStats.lastCpuTime
const secsSinceRefresh = (Date.now() - prevClusterStats.lastRefresh) / 1000.0

newRowInputRate = addExponentiallyWeightedToHistory(
rowsInputSinceRefresh / secsSinceRefresh,
rowsInputSinceRefresh / (secsSinceRefresh || 1),
prevClusterStats.rowInputRate
)
newByteInputRate = addExponentiallyWeightedToHistory(
bytesInputSinceRefresh / secsSinceRefresh,
bytesInputSinceRefresh / (secsSinceRefresh || 1),
prevClusterStats.byteInputRate
)
newPerWorkerCpuTimeRate = addExponentiallyWeightedToHistory(
cpuTimeSinceRefresh / newClusterStats.activeWorkers / secsSinceRefresh,
cpuTimeSinceRefresh / (newClusterStats.activeWorkers || 1) / (secsSinceRefresh || 1),
prevClusterStats.perWorkerCpuTimeRate
)
}
Expand Down Expand Up @@ -151,53 +154,85 @@ export const Dashboard = () => {
<Box sx={{ pb: 2 }}>
<Typography variant="h4">Cluster overview</Typography>
</Box>
<Box>
<>
<Grid container spacing={3}>
<Grid size={{ xs: 12, sm: 12, md: 6, lg: 4 }}>
<MetricCard title="Running queries" values={clusterStats.runningQueries} />
<MetricCard
title="Running queries"
values={clusterStats.runningQueries}
tooltip="Total number of queries currently running"
/>
</Grid>
<Grid size={{ xs: 12, sm: 12, md: 6, lg: 4 }}>
<MetricCard title="Active workers" values={clusterStats.activeWorkers} link="/workers" />
<MetricCard
title="Active workers"
values={clusterStats.activeWorkers}
link="/workers"
tooltip="Total number of active worker nodes"
/>
</Grid>
<Grid size={{ xs: 12, sm: 12, md: 6, lg: 4 }}>
<MetricCard title="rows/s" values={clusterStats.rowInputRate} numberFormatter={formatCount} />
<MetricCard
title="Input rows/s"
values={clusterStats.rowInputRate}
numberFormatter={formatCount}
tooltip="Moving average of input rows processed per second"
/>
</Grid>
<Grid size={{ xs: 12, sm: 12, md: 6, lg: 4 }}>
<MetricCard title="Queued queries" values={clusterStats.queuedQueries} />
<MetricCard
title="Queued queries"
values={clusterStats.queuedQueries}
tooltip="Total number of queries currently queued and awaiting execution"
/>
</Grid>
<Grid size={{ xs: 12, sm: 12, md: 6, lg: 4 }}>
<MetricCard
title="Runnable drivers"
values={clusterStats.runningDrivers}
numberFormatter={precisionRound}
tooltip="Moving average of total running drivers"
/>
</Grid>
<Grid size={{ xs: 12, sm: 12, md: 6, lg: 4 }}>
<MetricCard
title="bytes/s"
title="Input bytes/s"
values={clusterStats.byteInputRate}
numberFormatter={formatDataSizeBytes}
tooltip="Moving average of input bytes processed per second"
/>
</Grid>
<Grid size={{ xs: 12, sm: 12, md: 6, lg: 4 }}>
<MetricCard title="Blocked queries" values={clusterStats.blockedQueries} />
<MetricCard
title="Blocked queries"
values={clusterStats.blockedQueries}
tooltip="Total number of queries currently blocked and unable to make progress"
/>
</Grid>
<Grid size={{ xs: 12, sm: 12, md: 6, lg: 4 }}>
<MetricCard
title="Reserved memory (B)"
title="Reserved memory"
values={clusterStats.reservedMemory}
numberFormatter={formatDataSizeBytes}
tooltip="Total amount of memory reserved by all running queries"
/>
</Grid>
<Grid size={{ xs: 12, sm: 12, md: 6, lg: 4 }}>
<MetricCard
title="Worker parallelism"
values={clusterStats.perWorkerCpuTimeRate}
numberFormatter={precisionRound}
tooltip="Moving average of CPU time utilized per second per worker"
/>
</Grid>
</Grid>
</Box>

<Box sx={{ pt: 2 }}>
<Typography variant="h6">Query details</Typography>
<Divider />
</Box>
<QueryList />
</>
</>
)
}
Loading