From 8717a67d621a4001d0baf8d83f1c3f8cd4d7b2b2 Mon Sep 17 00:00:00 2001 From: CodingInAVan Date: Sat, 28 Mar 2026 19:17:42 -0700 Subject: [PATCH] Adding ptx, source, and coalesce metric --- package-lock.json | 41 +++++++- package.json | 2 + src/components/SourceCorrelationView.tsx | 119 ++++++++++++++++++++++- 3 files changed, 157 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 20a3e75..c3b1e8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "gpufl-portal", "version": "0.1.0", "dependencies": { + "@types/react-resizable": "^3.0.8", "antd": "^5.18.2", "dayjs": "^1.11.13", "keycharm": "^0.4.0", @@ -15,6 +16,7 @@ "propagating-hammerjs": "^3.0.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-resizable": "^3.1.3", "react-router-dom": "^6.28.0", "react-vis-timeline": "^2.0.3", "recharts": "^2.13.3", @@ -1535,14 +1537,12 @@ "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.27", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", - "dev": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -1559,6 +1559,15 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/react-resizable": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/react-resizable/-/react-resizable-3.0.8.tgz", + "integrity": "sha512-Pcvt2eGA7KNXldt1hkhVhAgZ8hK41m0mp89mFgQi7LAAEZiaLgm4fHJ5zbJZ/4m2LVaAyYrrRRv1LHDcrGQanA==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", @@ -2916,6 +2925,20 @@ "react": "^18.3.1" } }, + "node_modules/react-draggable": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.5.0.tgz", + "integrity": "sha512-VC+HBLEZ0XJxnOxVAZsdRi8rD04Iz3SiiKOoYzamjylUcju/hP9np/aZdLHf/7WOD268WMoNJMvYfB5yAK45cw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": ">= 16.3.0", + "react-dom": ">= 16.3.0" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -2932,6 +2955,20 @@ "node": ">=0.10.0" } }, + "node_modules/react-resizable": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.1.3.tgz", + "integrity": "sha512-liJBNayhX7qA4tBJiBD321FDhJxgGTJ07uzH5zSORXoE8h7PyEZ8mLqmosST7ppf6C4zUsbd2gzDMmBCfFp9Lw==", + "license": "MIT", + "dependencies": { + "prop-types": "15.x", + "react-draggable": "^4.5.0" + }, + "peerDependencies": { + "react": ">= 16.3", + "react-dom": ">= 16.3" + } + }, "node_modules/react-router": { "version": "6.30.2", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.2.tgz", diff --git a/package.json b/package.json index a813334..39d4d6c 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "preview": "vite preview" }, "dependencies": { + "@types/react-resizable": "^3.0.8", "antd": "^5.18.2", "dayjs": "^1.11.13", "keycharm": "^0.4.0", @@ -16,6 +17,7 @@ "propagating-hammerjs": "^3.0.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-resizable": "^3.1.3", "react-router-dom": "^6.28.0", "react-vis-timeline": "^2.0.3", "recharts": "^2.13.3", diff --git a/src/components/SourceCorrelationView.tsx b/src/components/SourceCorrelationView.tsx index d9daa31..c50322e 100644 --- a/src/components/SourceCorrelationView.tsx +++ b/src/components/SourceCorrelationView.tsx @@ -1,7 +1,8 @@ -import { useMemo, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { Table, Empty } from 'antd' import type { ColumnsType } from 'antd/es/table' import { useStore } from '@/store/useStore' +import { apiFetch } from '@/api' interface SourceLineRow { key: string @@ -11,6 +12,8 @@ interface SourceLineRow { instExec: number threadExec: number divergencePct: number | null + dominantPc: number | null + coalescingFactor: number | null // actual / ideal sectors; 1.0 = perfectly coalesced } interface FunctionEntry { @@ -34,6 +37,12 @@ function rowStyle(stallShare: number): React.CSSProperties { return {} } +function coalColor(v: number): string { + if (v <= 1.5) return '#16a34a' // near-ideal coalescing + if (v <= 4.0) return '#ca8a04' // partial coalescing + return '#dc2626' // poor coalescing +} + function divColor(pct: number): string { if (pct > 20) return '#dc2626' if (pct > 10) return '#ca8a04' @@ -44,6 +53,8 @@ export default function SourceCorrelationView() { const profileSamples = useStore((s) => s.profileSamples) const currentSessionId = useStore((s) => s.currentSessionId) const [selectedFuncKey, setSelectedFuncKey] = useState(null) + const [sourceLines, setSourceLines] = useState([]) + const [disassembly, setDisassembly] = useState>(new Map()) const functionEntries: FunctionEntry[] = useMemo(() => { const samples = profileSamples.filter((s) => s.sessionId === currentSessionId) @@ -57,6 +68,10 @@ export default function SourceCorrelationView() { stallHits: number instExec: number threadExec: number + dominantPc: number | null + dominantPcHits: number + sectorsGlobal: number + sectorsGlobalIdeal: number } const lineMap = new Map() @@ -70,17 +85,35 @@ export default function SourceCorrelationView() { const lineKey = `${funcKey}::${sourceLine}` if (!lineMap.has(lineKey)) { - lineMap.set(lineKey, { funcKey, displayName, sourceFile, sourceLine, stallHits: 0, instExec: 0, threadExec: 0 }) + lineMap.set(lineKey, { funcKey, displayName, sourceFile, sourceLine, stallHits: 0, instExec: 0, threadExec: 0, dominantPc: null, dominantPcHits: 0, sectorsGlobal: 0, sectorsGlobalIdeal: 0 }) } const agg = lineMap.get(lineKey)! if (s.sampleKind === 'pc_sampling') { - agg.stallHits += s.occurrenceCount + const hits = s.occurrenceCount + agg.stallHits += hits + // PC sampling may have pc_offset=0 (not meaningful); prefer SASS-derived pc + const pc = (s.pcOffset != null && s.pcOffset > 0) ? s.pcOffset : null + if (pc != null && hits > agg.dominantPcHits) { + agg.dominantPc = pc + agg.dominantPcHits = hits + } } else if (s.sampleKind === 'sass_metric') { if (s.metricName === 'smsp__sass_inst_executed') { agg.instExec += s.metricValue ?? 0 + // Use the instruction with the most executions as the representative PC + const pc = s.pcOffset ?? null + const val = s.metricValue ?? 0 + if (pc != null && val > agg.dominantPcHits) { + agg.dominantPc = pc + agg.dominantPcHits = val + } } else if (s.metricName === 'smsp__sass_thread_inst_executed') { agg.threadExec += s.metricValue ?? 0 + } else if (s.metricName === 'smsp__sass_sectors_mem_global') { + agg.sectorsGlobal += s.metricValue ?? 0 + } else if (s.metricName === 'smsp__sass_sectors_mem_global_ideal') { + agg.sectorsGlobalIdeal += s.metricValue ?? 0 } } } @@ -109,6 +142,8 @@ export default function SourceCorrelationView() { instExec: l.instExec, threadExec: l.threadExec, divergencePct, + dominantPc: l.dominantPc, + coalescingFactor: l.sectorsGlobalIdeal > 0 ? l.sectorsGlobal / l.sectorsGlobalIdeal : null, } }) @@ -123,6 +158,32 @@ export default function SourceCorrelationView() { const selected = functionEntries.find((e) => e.funcKey === selectedFuncKey) ?? functionEntries[0] ?? null + // Fetch source lines whenever the selected function changes + useEffect(() => { + setSourceLines([]) + if (!selected?.sourceFile || !currentSessionId) return + apiFetch( + `/api/v1/events/source-content?sessionId=${encodeURIComponent(currentSessionId)}&sourcePath=${encodeURIComponent(selected.sourceFile)}` + ) + .then((r) => (r.ok ? r.json() : Promise.resolve([]))) + .then((lines: string[]) => setSourceLines(lines)) + .catch(() => setSourceLines([])) + }, [selected?.sourceFile, currentSessionId]) + + // Fetch SASS disassembly for the selected function + useEffect(() => { + setDisassembly(new Map()) + if (!selected?.displayName || !currentSessionId) return + apiFetch( + `/api/v1/events/disassembly?sessionId=${encodeURIComponent(currentSessionId)}&functionName=${encodeURIComponent(selected.displayName)}` + ) + .then((r) => (r.ok ? r.json() : Promise.resolve([]))) + .then((entries: { pcOffset: number; sass: string }[]) => + setDisassembly(new Map(entries.map((e) => [e.pcOffset, e.sass]))) + ) + .catch(() => {}) + }, [selected?.funcKey, currentSessionId]) + const dash = const columns: ColumnsType = [ @@ -134,6 +195,45 @@ export default function SourceCorrelationView() { {v != null ? v : dash} ), }, + { + title: 'Code', + dataIndex: 'sourceLine', + key: 'code', + ellipsis: true, + render: (v: number | null) => { + const text = v != null && sourceLines.length >= v ? sourceLines[v - 1] : null + if (!text) return dash + return ( + + {text.trimStart()} + + ) + }, + }, + { + title: 'SASS', + key: 'sass', + ellipsis: true, + render: (_: unknown, record: SourceLineRow) => { + const text = record.dominantPc != null ? disassembly.get(record.dominantPc) : null + if (!text) return dash + return ( + + {text} + + ) + }, + }, { title: 'Stall Hits', dataIndex: 'stallHits', @@ -177,6 +277,19 @@ export default function SourceCorrelationView() { dash ), }, + { + title: 'Coal. ×', + dataIndex: 'coalescingFactor', + width: 90, + align: 'right' as const, + sorter: (a: SourceLineRow, b: SourceLineRow) => (a.coalescingFactor ?? -1) - (b.coalescingFactor ?? -1), + render: (v: number | null) => + v != null ? ( + {v.toFixed(1)}× + ) : ( + dash + ), + }, ] if (functionEntries.length === 0) {